【问题标题】:Can't set attributes on instance of "object" class无法在“对象”类的实例上设置属性
【发布时间】:2010-12-04 11:31:58
【问题描述】:

所以,我在回答 this question 时正在玩 Python,我发现这是无效的:

o = object()
o.attr = 'hello'

由于AttributeError: 'object' object has no attribute 'attr'。但是,对于从对象继承的任何类,它都是有效的:

class Sub(object):
    pass

s = Sub()
s.attr = 'hello'

打印s.attr 会按预期显示“你好”。为什么会这样? Python 语言规范中有什么规定不能将属性分配给普通对象?

【问题讨论】:

  • 纯猜测:object 类型是不可变的,不能添加新属性?这似乎是最有意义的。
  • @S.Lott:请看这个问题的第一行。纯属好奇。
  • 您的标题具有误导性,您试图在object 类实例上设置属性,而不是在object 类上。

标签: python attributes language-design


【解决方案1】:

为了支持任意属性分配,一个对象需要一个__dict__:一个与对象关联的字典,可以存储任意属性。否则,无处可放置新属性。

object 的实例 携带 __dict__ ——如果确实如此,那么在可怕的循环依赖问题之前(因为 dict,像大多数其他东西一样,继承自 @ 987654329@;-),这将使 Python 中的 每个 对象都带有一个 dict,这意味着每个当前没有或不需要dict(本质上,所有没有可任意分配属性的对象都没有或不需要dict)。

例如,使用优秀的pympler项目(你可以通过svn从here得到它),我们可以做一些测量...:

>>> from pympler import asizeof
>>> asizeof.asizeof({})
144
>>> asizeof.asizeof(23)
16

您不会希望每个 int 占用 144 个字节而不是 16 个字节,对吧?-)

现在,当你创建一个类(从任何东西继承)时,事情就会改变......:

>>> class dint(int): pass
... 
>>> asizeof.asizeof(dint(23))
184

...__dict__ is 现在添加了(加上,更多的开销)——所以dint 实例可以有任意属性,但你为此付出了相当大的空间成本灵活性。

如果你想要ints 只需要一个 额外属性foobar... 怎么办?这是一种罕见的需求,但 Python 确实为此目的提供了一种特殊的机制......

>>> class fint(int):
...   __slots__ = 'foobar',
...   def __init__(self, x): self.foobar=x+100
... 
>>> asizeof.asizeof(fint(23))
80

...不像int那么小相当,请注意! (甚至是两个ints,一个是self,一个是self.foobar——第二个可以重新分配),但肯定比dint好得多。

当类具有 __slots__ 特殊属性(字符串序列)时,class 语句(更准确地说,默认元类 type装备每个实例该类具有__dict__(因此具有任意属性的能力),只是一组有限的、刚性的“槽”(基本上每个位置都可以包含对某个对象的一个​​引用)以及给定的名称。

为了换取失去的灵活性,您在每个实例中获得了大量字节(只有当您有无数个实例在运行时才有意义,但是,用例)。 p>

【讨论】:

  • 这解释了该机制是如何实现的,但没有解释为什么以这种方式实现。我可以想到至少有两种或三种方法来实现动态添加 dict 不会有开销的缺点,但会增加一些简单性。
  • 请注意,非空 __slots__ 不适用于可变长度类型,例如 strtuple,在 Python 3 中也适用于 int
  • 这是一个很好的解释,但仍然没有回答为什么(或如何)Sub 具有 __dict__ 属性而对象没有,因为 Sub 继承自 object,该属性(以及其他属性,如__module__)是如何添加到继承中的?可能这可能是一个新问题
  • 一个对象的__dict__ 仅在第一次需要时创建,因此内存成本情况并不像asizeof 输出看起来那么简单。 (asizeof 不知道如何避免__dict__ 物化。)您可以在this example 中看到dict 直到需要才被物化,并且您可以看到负责__dict__ 物化here 的代码路径之一.
【解决方案2】:

正如其他回答者所说,object 没有__dict__objectall 类型的基类,包括 intstr。因此,object 提供的任何东西都将成为他们的负担。即使是像 optional __dict__ 这样简单的东西,每个值都需要一个额外的指针;这会为系统中的每个对象浪费额外的 4-8 字节内存,而实用性非常有限。


在 Python 3.3+ 中,您可以(并且应该)为此使用 types.SimpleNamespace,而不是创建一个虚拟类的实例。

【讨论】:

    【解决方案3】:

    这仅仅是因为优化。

    字典比较大。

    >>> import sys
    >>> sys.getsizeof((lambda:1).__dict__)
    140
    

    在 C 中定义的大多数(可能是所有)类都没有用于优化的 dict。

    如果您查看source code,您会看到有许多检查对象是否有字典。

    【讨论】:

      【解决方案4】:

      因此,在调查我自己的问题时,我发现了有关 Python 语言的这一点:您可以从 int 之类的东西继承,并且您会看到相同的行为:

      >>> class MyInt(int):
             pass
      
      >>> x = MyInt()
      >>> print x
      0
      >>> x.hello = 4
      >>> print x.hello
      4
      >>> x = x + 1
      >>> print x
      1
      >>> print x.hello
      Traceback (most recent call last):
        File "<interactive input>", line 1, in <module>
      AttributeError: 'int' object has no attribute 'hello'
      

      我认为最后的错误是因为 add 函数返回一个 int,所以我必须覆盖 __add__ 等函数以保留我的自定义属性。但是当我想到像“int”这样的“对象”时,这一切现在对我来说都是有意义的(我认为)。

      【讨论】:

        【解决方案5】:

        https://docs.python.org/3/library/functions.html#object

        注意object 确实没有__dict__,因此您不能为object 类的实例分配任意属性。 p>

        【讨论】:

          【解决方案6】:

          这是因为 object 是“类型”,而不是类。通常,在 C 扩展中定义的所有类(如所有内置数据类型,以及诸如 numpy 数组之类的东西)都不允许添加任意属性。

          【讨论】:

          • 但是 object() 是一个对象,就像 Sub() 是一个对象一样。我的理解是 s 和 o 都是对象。那么s和o之间的根本区别是什么?是不是一个是实例化类型,另一个是实例化类?
          • 宾果游戏。这正是问题所在。
          • 在 Python 3 中,类型和类之间的区别实际上并不存在。所以“类型”和“类”现在是相当同义的。但是由于 Alex Martelli 给出的原因,您仍然无法向那些没有 __dict__ 的类添加属性。
          【解决方案7】:

          这是 (IMO) Python 的基本限制之一 - 您无法重新打开课程。不过,我认为实际问题是由于不能在运行时修改用 C 实现的类……子类可以,但基类不能。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2018-03-04
            • 1970-01-01
            • 2014-11-29
            • 1970-01-01
            • 1970-01-01
            • 2020-01-02
            相关资源
            最近更新 更多