为啥允许向已经实例化的对象添加属性?

Posted

技术标签:

【中文标题】为啥允许向已经实例化的对象添加属性?【英文标题】:Why is adding attributes to an already instantiated object allowed?为什么允许向已经实例化的对象添加属性? 【发布时间】:2012-09-16 03:33:20 【问题描述】:

我正在学习python,虽然我认为我了解了Python的整个概念和概念,但今天我偶然发现了一段我没有完全理解的代码:

假设我有一个应该定义 Circles 但缺少主体的类:

class Circle():
    pass

由于我没有定义任何属性,我该怎么做:

my_circle = Circle()
my_circle.radius = 12

奇怪的是 Python 接受了上述声明。我不明白为什么 Python 不提出undefined name error。我确实明白,通过动态类型,我只是在需要时将变量绑定到对象,但Circle 类中不应该存在属性radius 来允许我这样做吗?

编辑:你的答案中有很多精彩的信息! 感谢大家提供的所有精彩答案!很遗憾,我只能将其中一个标记为答案。

【问题讨论】:

当您在__init__ 初始化self.radius 时,您做的不是完全相同吗? @JBernardo 是的,但是在这种情况下,您正在为类 Circle() 显式定义 radius 属性。就我而言,我没有在类主体中创建任何属性。 @NlightNFotis 不,您正在做同样的事情,因为 self 只是一个变量,就像任何其他变量一样。 @NlightNFotis 另外,Python is not Java 和一种不会影响您对编程的看法的语言不值得了解 - [Alan Perlis](en.wikiquote.org/wiki/Alan_Perlis) @NlightNFotis 不,你不是。您定义一个函数,该函数分配给它的第一个参数的一个属性。碰巧这个函数被一个类的__init__ 属性引用,该属性恰好在对象构造之后被调用。 【参考方案1】:

如何防止创建新属性?

使用类

要控制新属性的创建,您可以覆盖__setattr__ 方法。每次调用my_obj.x = 123 时都会调用它。

见documentation:

class A:
  def __init__(self):
    # Call object.__setattr__ to bypass the attribute checking
    super().__setattr__('x', 123)

  def __setattr__(self, name, value):
    # Cannot create new attributes
    if not hasattr(self, name):
      raise AttributeError('Cannot set new attributes')
    # Can update existing attributes
    super().__setattr__(name, value)

a = A()
a.x = 123  # Allowed
a.y = 456  # raise AttributeError

请注意,如果用户直接调用object.__setattr__(a, 'attr_name', attr_value),仍然可以绕过检查。

使用数据类

使用dataclasses,您可以使用frozen=True 禁止创建新属性。它还会阻止更新现有属性。

@dataclasses.dataclass(frozen=True)
class A:
  x: int


a = A(x=123)
a.y = 123  # Raise FrozenInstanceError
a.x = 123  # Raise FrozenInstanceError

注意:dataclasses.FrozenInstanceError 是 AttributeError 的子类

【讨论】:

【参考方案2】:

正如 delnan 所说,您可以使用 __slots__ 属性获得此行为。但它是一种节省内存空间和访问类型的方法这一事实并没有放弃它(也)是禁用动态属性的手段这一事实。

禁用动态属性是一个合理的做法,如果只是为了防止由于拼写错误导致的细微错误。 “测试和纪律”很好,但依赖自动验证当然也没有错——也不一定是不符合标准的。

此外,自从 attrs 库在 2016 年达到第 16 版(显然是在最初的问答之后),创建一个带有插槽的封闭类从未如此简单。

>>> import attr
...
... @attr.s(slots=True)
... class Circle:
...   radius = attr.ib()
...
... f = Circle(radius=2)
... f.color = 'red'
AttributeError: 'Circle' object has no attribute 'color'

【讨论】:

另一种禁用不使用槽的动态属性的机制,因此不会破坏继承:from pystrict import strict \n @strict \n class Circle: ...【参考方案3】:

Python 允许您在几乎任何实例(或类)上存储任何名称的属性。可以通过在 C 中编写类(如内置类型)或使用仅允许某些名称的 __slots__ 来阻止这种情况。

它起作用的原因是大多数实例将它们的属性存储在字典中。是的,就像您使用 定义的常规 Python 字典一样。字典存储在名为__dict__ 的实例属性中。事实上,有人说“类只是字典的语法糖”。也就是说,你可以用字典做你能做的一切;类只是让事情变得更容易。

您习惯于必须在编译时定义所有属性的静态语言。在 Python 中,类定义是执行,而不是编译;类和其他对象一样是对象;添加属性就像在字典中添加项目一样简单。这就是为什么 Python 被认为是一种动态语言。

【讨论】:

嗨,你是说在 Python 中,类的目的不是捆绑数据和行为 (OOP),而只是通过修饰来确定给定字典的用途有一些人类可读的语法? 您可以使用它们来捆绑数据和行为,并且语法鼓励这样做,并且它基本上就像您期望的 OOP 一样工作(尽管不是 OOP 的强大版本——Python 中的封装非常弱,因为没有私有属性)。但是,在类语法之下,类基本上是字典,顶部有一些额外的(非常有用的)行为。如果您正在编写编译器,您可能会使用字典(哈希)在定义期间跟踪类的成员; Python 只是在运行时执行此操作。 感谢您的澄清!【参考方案4】:

一个主要原则是没有声明这样的东西。也就是说,你永远不会声明“这个类有一个方法 foo”或“这个类的实例有一个属性 bar”,更不用说声明要存储在那里的对象的类型了。您只需定义一个方法、属性、类等,它就会被添加。正如 JBernardo 指出的那样,任何__init__ 方法都做同样的事情。将新属性的创建任意限制为名称为__init__ 的方法没有多大意义。有时将函数存储为__init__,但实际上并没有该名称(例如装饰器),这样的限制会破坏这一点。

现在,这并非普遍适用。内置类型省略了此功能作为优化。通过__slots__,您还可以在用户定义的类上防止这种情况。但这只是空间优化(不需要为每个对象提供字典),而不是正确性。

如果你想要一个安全网,那就太糟糕了。 Python 不提供,而且你不能合理地添加一个,最重要的是,拥抱该语言的 Python 程序员会避开它(阅读:几乎所有你想与之合作的人)。测试和纪律,对于确保正确性还有很长的路要走。不要随意在__init__以外的地方补属性如果可以避免,做自动化测试。由于这样的诡计,我很少遇到AttributeError 或逻辑错误,而在这些发生的情况中,几乎所有都被测试捕获了。

【讨论】:

它的方式很好,但灵活性,也引发了可读性问题,在维护其他人的代码时,我经常忘记对象在某个点具有什么属性。【参考方案5】:

只是为了澄清这里讨论中的一些误解。这段代码:

class Foo(object):
    def __init__(self, bar):
        self.bar = bar

foo = Foo(5)

还有这段代码:

class Foo(object):
    pass

foo = Foo()
foo.bar = 5

完全等价。真的没有区别。它做同样的事情。这种区别在于,在第一种情况下,它被封装了,很明显 bar 属性是 Foo 类型对象的正常部分。在第二种情况下,情况是否如此尚不清楚。

在第一种情况下,您不能创建没有 bar 属性的 Foo 对象(嗯,您可能可以,但不容易),在第二种情况下,除非您设置 Foo 对象没有 bar 属性它。

因此,尽管代码在编程上是等效的,但它用于不同的情况。

【讨论】:

第二种的用例是什么?它有点破坏 OOP,这当然很好......但是如果你不编程 OOP,你为什么还要关心上课呢?这些不是反问,我真的很好奇! 它不会因为你不教条就停止成为 OOP。第二种情况还是OOP。【参考方案6】:

Python 中有两种类型的属性 - Class Data AttributesInstance Data Attributes

Python 让您可以灵活地动态创建Data Attributes

由于实例数据属性与实例相关,您也可以在__init__ 方法中执行此操作,也可以在创建实例后执行此操作..

class Demo(object):
    classAttr = 30
    def __init__(self):
         self.inInit = 10

demo = Demo()
demo.outInit = 20
Demo.new_class_attr = 45; # You can also create class attribute here.

print demo.classAttr  # Can access it 

del demo.classAttr         # Cannot do this.. Should delete only through class

demo.classAttr = 67  # creates an instance attribute for this instance.
del demo.classAttr   # Now OK.
print Demo.classAttr  

所以,您看到我们创建了两个实例属性,一个在__init__ 内部,一个在外部,在创建实例之后..

但不同的是,在__init__内部创建的实例属性将被设置为所有实例,而如果在外部创建,则可以为不同的实例设置不同的实例属性..

这与 Java 不同,Java 中每个类的实例都有相同的实例变量集..

注意: - 虽然您可以通过实例访问类属性,但不能删除它。 此外,如果您尝试通过实例修改类属性,您实际上会创建一个隐藏类属性的实例属性..

【讨论】:

不,你也没有声明类属性。你定义它们。这些定义是可执行语句并且非常普通,它们不是操纵某些函数的范围,而是操纵类的属性。并且类属性也不是一成不变的:添加、替换和删除类属性是微不足道的。 我还是不明白你为什么一开始就区分类和实例属性。两者都是在运行时显式定义的,在这两种情况下,这些定义和重新定义都可以随时发生。【参考方案7】:

不,python 就是这样灵活的,它不强制您可以在用户定义的类中存储哪些属性。

但是有一个技巧,在类定义上使用__slots__ attribute 会阻止您创建未在__slots__ 序列中定义的其他属性:

>>> class Foo(object):
...     __slots__ = ()
... 
>>> f = Foo()
>>> f.bar = 'spam'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'bar'
>>> class Foo(object):
...     __slots__ = ('bar',)
... 
>>> f = Foo()
>>> f.bar
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: bar
>>> f.bar = 'spam'

【讨论】:

@NlightNFotis 你没有。 Python 不是 Java,您不应该尝试用 Python 编写 Java。如果您想这样做,请编写 Java。 分享没有安全网的编程乐趣:) @NlightNFotis:Java 的类型安全是一种错觉。它感觉更安全,更安全,你可以更信任代码,但你真的不能。蛋糕是个谎言。 @NlightNFotis:Python 的哲学被描述为“我们在这里都是同意的成年人”。这意味着如果您想打破惯例并直接更改foo._variable - 也许是为了调试,或者为了避免 foo 中的一些错误 - 您可以。但是如果它破坏了某些东西,你就不能向 foo 的作者抱怨。 __slots__ 是为了节省内存;它不应该被用作锁定课程的一种方式。【参考方案8】:

它创建my_circleradius 数据成员。

如果你要求它提供my_circle.radius,它会抛出异常:

>>> print my_circle.radius # AttributeError

有趣的是,这并没有改变类;仅此一例。所以:

>>> my_circle = Circle()
>>> my_circle.radius = 5
>>> my_other_circle = Circle()
>>> print my_other_circle.radius # AttributeError

【讨论】:

虽然你可以做Circle.xyz = 5 并更改类而不是实例...

以上是关于为啥允许向已经实例化的对象添加属性?的主要内容,如果未能解决你的问题,请参考以下文章

C#winform中为啥一个窗体的对象可以调用在另一个窗体中创建的一个类未实例化下

为啥布局组上实例化的最后一个元素显示为第一个且错误?

Python_对象(实例)

C#为啥接口可以实例化一个实现该接口的类?

c#为啥实例化对象?啥情况下实例化对象?

C#怎么实例化对象?具体是实例化啥?