关于python:类属性和实例属性有什么区别?

What is the difference between class and instance attributes?

以下两者之间是否存在任何有意义的区别:

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

VS

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

如果您要创建很多实例,那么这两种样式在性能或空间要求上有什么不同吗?当您阅读代码时,您是否认为这两种样式的含义有显著差异?


除了性能考虑之外,还有一个显著的语义差异。在类属性的情况下,只有一个对象被引用。在实例化时设置的实例属性中,可以引用多个对象。例如

1
2
3
4
5
6
7
8
9
10
11
>>> 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    
[]


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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 = MyClass(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

类属性分配

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

    1
    2
    3
    4
    5
    6
    foo = Bar(2)
    foo.class_var
    ## 1
    Bar.class_var = 2
    foo.class_var
    ## 2
  • 如果通过访问实例来设置类变量,它将仅覆盖该实例的值。这基本上覆盖了类变量,并将其转换为一个仅对该实例可用的实例变量。

    1
    2
    3
    4
    5
    6
    7
    8
    foo = Bar(2)
    foo.class_var
    ## 1
    foo.class_var = 2
    foo.class_var
    ## 2
    Bar.class_var
    ## 1

什么时候使用类属性?

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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个项目的上限。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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

由于这里的评论和另外两个被标记为dup的问题中的人似乎都以同样的方式对此感到困惑,我认为值得在alex coventry的基础上再加一个答案。

事实上,亚历克斯正在分配一个可变类型的值,比如一个列表,这与事物是否共享无关。我们可以通过id函数或is运算符看到:

1
2
3
4
5
6
7
8
9
>>> 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()s cannot.)。

那么,为什么在亚历克斯的例子中,a.foo.append(5)会影响b.foo,而在我的例子中,a.foo = 5不会影响?好吧,在亚历克斯的例子中,试一下a.foo = 5,注意到它也不影响b.foo

a.foo = 5只是把a.foo改为5的名字。这不会影响b.foo,或者a.foo所指的旧值的任何其他名称。*创建一个隐藏类属性的实例属性有点困难,**但是一旦您了解到这一点,这里就不会发生复杂的事情。

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

*-来自C++语言的人的困惑是,在Python中,值不存储在变量中。价值生活在有价值的土地上,变量本身就是价值的名称,而赋值只是为价值创建一个新的名称。如果有帮助,可以将每个python变量都看作一个shared_ptr,而不是一个T

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


仅仅是对亚历克斯考文垂所说的一个阐述,另一个亚历克斯(马泰利)多年前在comp.lang.python新闻组上提出了类似的问题。他检查了一个人的意图和他得到的(通过使用实例变量)之间的语义差异。

http://groups.google.com/group/comp.lang.python/msg/5914d297aff35fae?HL=EN


还有一种情况。

类和实例属性是描述符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# -*- 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()

以上将输出:

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

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

我在c.pyobject的genericgetattr定义中找到了一个很好的帖子。

解释

If the attribute is found in the dictionary of the classes which make up.
the objects MRO, then check to see if the attribute being looked up points to a Data Descriptor (which is nothing more that a class implementing both the __get__ and the __set__ methods).
If it does, resolve the attribute lookup by calling the __get__ method of the Data Descriptor (lines 28–33).