类属性和实例属性有什么区别?

What is the difference between class and instance attributes?

提问人:Dan Homerick 提问时间:10/16/2008 最后编辑:martineauDan Homerick 更新时间:6/15/2023 访问量:74418

问:

两者之间是否有任何有意义的区别:

class A(object):
    foo = 5   # some default value

与。

class B(object):
    def __init__(self, foo=5):
        self.foo = foo

如果要创建大量实例,这两种样式在性能或空间要求方面是否有任何差异?当您阅读代码时,您是否认为这两种样式的含义有显着不同?

Python 属性 成员变量

评论

1赞 Dan Homerick 10/16/2008
我刚刚意识到这里提出了一个类似的问题并得到了回答:stackoverflow.com/questions/206734/......我应该删除这个问题吗?
3赞 S.Lott 10/17/2008
这是你的问题,请随时删除它。既然是你的,为什么要问别人的意见呢?

答:

168赞 Alex Coventry 10/16/2008 #1

存在显著的语义差异(超出性能考虑):

  • 当在实例上定义属性时(这是我们通常的做法),可以引用多个对象。每个属性都有一个完全独立的版本
  • 当在类上定义属性时,只引用一个基础对象,因此,如果对该类的不同实例的操作都尝试设置/(追加/扩展/插入/等)该属性,则:
    • 如果属性是内置类型(如 int、float、boolean、string),则对一个对象的操作将覆盖 (clobber) 该值
    • 如果属性是可变类型(如列表或字典),我们将得到不必要的泄漏。

例如:

>>> class A: foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo
[5]
>>> class A:
...  def __init__(self): self.foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo    
[]

评论

4赞 Babu 7/17/2014
只有可变类型是共享的。喜欢 for,他们仍然附加到每个实例而不是类。intstr
14赞 abarnert 10/30/2014
@Babu:不,以完全相同的方式共享。您可以使用 或 轻松检查。或者只看每个实例和类的.通常,不可变类型是否共享并不重要。intstrisid__dict____dict__
21赞 Konstantin Schubert 4/27/2015
但是,请注意,如果这样做,那么在这两种情况下,您都会看到 return 。这是因为在第一种情况下,您正在用同名的新实例属性覆盖 class 属性。a.foo = 5b.foo[]a.foo
0赞 NeilG 7/24/2023
“泄漏”实际上可能是设计想要的
45赞 Peter Shinners 10/16/2008 #2

区别在于类的属性由所有实例共享。实例上的属性对于该实例是唯一的。

如果来自 C++,则类上的属性更像是静态成员变量。

评论

1赞 Rafe 6/27/2014
难道不是只有可变类型是共享的吗?接受的答案显示一个列表,该列表有效,但如果它是 int,它似乎与实例 attr 相同:>>> class A(object): foo = 5 >>> a, b = A(), A() >>> a.foo = 10 >>> b.foo 5
8赞 abarnert 10/30/2014
@Rafe:不,所有类型都是共享的。您感到困惑的原因是,什么正在改变所引用的值,而正在使值成为新名称。因此,您最终会得到一个隐藏 class 属性的实例属性。在 Alex 的版本中尝试相同的方法,您会发现它没有变化。a.foo.append(5)a.fooa.foo = 5a.foo5a.foo = 5b.foo
22赞 abarnert 10/30/2014 #3

由于这里的评论和另外两个标记为重复的问题中的人们似乎都以同样的方式对此感到困惑,我认为值得在亚历克斯考文垂的答案之上添加一个额外的答案。

Alex 分配可变类型的值(如列表)这一事实与事物是否共享无关。我们可以通过函数或运算符看到这一点:idis

>>> class A: foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
True
>>> class A:
...     def __init__(self): self.foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
False

(如果你想知道为什么我用 object() 而不是 5,那是为了避免遇到另外两个我不想在这里讨论的问题;出于两个不同的原因,完全单独创建的 5 最终可能是数字 5的相同实例。但是完全单独创建的 object() 不能。


那么,为什么在亚历克斯的例子中会影响,而在我的例子中没有呢?好吧,尝试 Alex 的例子,并注意到它也不会影响那里。a.foo.append(5)b.fooa.foo = 5a.foo = 5b.foo

a.foo = 5只是做了一个名字。这不会影响 ,也不会影响过去引用的旧值的任何其他名称。* 我们正在创建一个隐藏类属性的实例属性,这有点棘手,** 但一旦你明白了这一点,这里就不会发生任何复杂的事情了。a.foo5b.fooa.foo


希望现在 Alex 使用列表的原因很明显了:你可以改变列表这一事实意味着更容易显示两个变量命名同一个列表,也意味着在现实生活中的代码中,知道你是有两个列表还是同一个列表的两个名称更为重要。


* 对于来自C++等语言的人来说,困惑在于,在Python中,值不会存储在变量中。值本身就存在于价值领域,变量只是值的名称,赋值只是为值创建一个新名称。如果有帮助,请将每个 Python 变量视为 shared_ptr<T>而不是 T

** 有些人利用这一点,将类属性用作实例属性的“默认值”,实例可能会设置也可能不设置。这在某些情况下可能很有用,但也可能令人困惑,因此请小心。

43赞 zangw 12/7/2015 #4

这是一篇非常好的文章,总结如下。

class Bar(object):
    ## No need for dot syntax
    class_var = 1

    def __init__(self, i_var):
        self.i_var = i_var

## Need dot syntax as we've left scope of class namespace
Bar.class_var
## 1
foo = Bar(2)

## Finds i_var in foo's instance namespace
foo.i_var
## 2

## Doesn't find class_var in instance namespace…
## So look's in class namespace (Bar.__dict__)
foo.class_var
## 1

并以视觉形式

enter image description here

类属性赋值

  • 如果通过访问类设置了类属性,它将覆盖所有实例的值

      foo = Bar(2)
      foo.class_var
      ## 1
      Bar.class_var = 2
      foo.class_var
      ## 2
    
  • 如果通过访问实例来设置类变量,则它将仅覆盖该实例的值。这实质上覆盖了类变量,并将其转换为实例变量,直观地说,仅适用于该实例

      foo = Bar(2)
      foo.class_var
      ## 1
      foo.class_var = 2
      foo.class_var
      ## 2
      Bar.class_var
      ## 1
    

什么时候会使用 class 属性?

  • 存储常量。由于类属性可以作为类本身的属性进行访问,因此使用它们来存储类范围的类特定常量通常很好

      class Circle(object):
           pi = 3.14159
    
           def __init__(self, radius):
                self.radius = radius   
          def area(self):
               return Circle.pi * self.radius * self.radius
    
      Circle.pi
      ## 3.14159
      c = Circle(10)
      c.pi
      ## 3.14159
      c.area()
      ## 314.159
    
  • 定义默认值。举个简单的例子,我们可以创建一个有界列表(即,一个只能包含一定数量或更少元素的列表),并选择默认上限为 10 个项目

      class MyClass(object):
          limit = 10
    
          def __init__(self):
              self.data = []
          def item(self, i):
              return self.data[i]
    
          def add(self, e):
              if len(self.data) >= self.limit:
                  raise Exception("Too many elements")
              self.data.append(e)
    
       MyClass.limit
       ## 10
    

评论

0赞 Vipul 1/22/2022
您链接的帖子太棒了!
0赞 SimonShyu 6/22/2017 #5

还有一种情况。

类和实例属性是 Descriptor

# -*- encoding: utf-8 -*-


class RevealAccess(object):
    def __init__(self, initval=None, name='var'):
        self.val = initval
        self.name = name

    def __get__(self, obj, objtype):
        return self.val


class Base(object):
    attr_1 = RevealAccess(10, 'var "x"')

    def __init__(self):
        self.attr_2 = RevealAccess(10, 'var "x"')


def main():
    b = Base()
    print("Access to class attribute, return: ", Base.attr_1)
    print("Access to instance attribute, return: ", b.attr_2)

if __name__ == '__main__':
    main()

上面将输出:

('Access to class attribute, return: ', 10)
('Access to instance attribute, return: ', <__main__.RevealAccess object at 0x10184eb50>)

通过类或实例访问相同类型的实例返回不同的结果!

我在c.PyObject_GenericGetAttr定义中找到了一个很棒的帖子

解释

如果该属性在组成类的字典中找到。 对象 MRO,然后检查正在查找的属性是否指向数据描述符(这只不过是一个同时实现 和 方法的类)。 如果是这样,请通过调用数据描述符的方法(第 28-33 行)来解析属性查找。__get____set____get__