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

时间:2012-09-24 16:20:52

标签: python attributes python-3.x declaration

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

假设我有一个应该定义Circles但没有身体的类:

class Circle():
    pass

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

my_circle = Circle()
my_circle.radius = 12

奇怪的是Python接受上述语句。我不明白为什么Python不会引发undefined name error。我明白通过动态类型我只需要随时将变量绑定到对象,但radius类中不应存在属性Circle以允许我这样做?

编辑:您的答案中有很多精彩的信息! 感谢大家所有这些精彩的答案!很遗憾,我只能将其作为答案。

8 个答案:

答案 0 :(得分:47)

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

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

如果你想要一个安全网,那么太糟糕了。 Python没有提供一个,你不能合理地添加一个,最重要的是,它会被拥抱该语言的Python程序员所避开(阅读:几乎所有你想要使用的程序员)。测试和纪律,仍然有很长的路要走,以确保正确性。如果可以避免,则不要使用自由构成__init__ 之外的属性,并进行自动化测试。由于这样的欺骗,我很少有AttributeError或逻辑错误,并且在那些发生的情况下,几乎所有都被测试捕获。

答案 1 :(得分:34)

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

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属性它

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

答案 2 :(得分:18)

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

它的工作原理是大多数实例将其属性存储在字典中。是的,像您使用{}定义的常规Python字典。字典存储在名为__dict__的实例属性中。事实上,有些人说“课程只是词典的语法糖”。也就是说,您可以使用带有字典的类来完成所有操作;课程只是让它变得更容易。

您已经习惯了静态语言,您必须在编译时定义所有属性。在Python中,类定义是执行,而不是编译;类就像任何其他类一样是对象;添加属性就像在字典中添加项一样简单。这就是Python被认为是动态语言的原因。

答案 3 :(得分:16)

不,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'

答案 4 :(得分:6)

它会创建radius的{​​{1}}数据成员。

如果您曾向my_circle提出要求,则会引发异常:

my_circle.radius

有趣的是,这不会改变班级;只是那个例子。所以:

>>> print my_circle.radius # AttributeError

答案 5 :(得分:4)

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不同,其中每个类的实例都具有相同的实例变量集。

  • 注意: - 虽然您可以通过实例访问类属性,但您无法删除它。 此外,如果您尝试通过实例修改类属性,您实际上创建了一个影响类属性的实例属性。

答案 6 :(得分:1)

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

禁用动态属性是一件合理的事情,只是为了防止因拼写错误而产生的细微错误。 “测试和纪律”很好,但依靠自动验证肯定也没有错 - 也不一定是unpythonic。

此外,由于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'

答案 7 :(得分:0)

要控制新属性的创建,可以覆盖__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),仍然可以绕过检查。