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


132

之间是否有有意义的区别:

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

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

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


1
我刚刚意识到,有人在这里询问并回答了类似的问题:stackoverflow.com/questions/206734/… 我应该删除此问题吗?
Dan Homerick

2
这是您的问题,请随时删除。既然是你的,为什么要问别人的意见?
S.Lott

Answers:


146

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

>>> 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
仅共享可变类型。像这样intstr它们仍然附加在每个实例上,而不是类上。
2014年

11
@Babu:没有,int并且str共享,在完全相同的方式。您可以使用is或轻松检查id。或者只是查看每个实例__dict__和类的__dict__。是否共享不可变类型通常并不重要。
2014年

17
但是,请注意,如果这样做a.foo = 5,则在两种情况下都将看到b.fooreturn []。这是因为在第一种情况下,您将a.foo用同名的新实例属性覆盖class属性。
康斯坦丁·舒伯特

39

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

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


1
不仅仅是共享的可变类型吗?接受的答案显示了一个有效的列表,但如果它是一个int,则它似乎与实例attr相同: >>> class A(object): foo = 5 >>> a, b = A(), A() >>> a.foo = 10 >>> b.foo 5
Rafe

6
@Rafe:不,所有类型都是共享的。您感到困惑的原因是,是什么a.foo.append(5)使所a.foo引用的值发生变异,同时a.foo = 5又使a.foo该值变成一个新名称5。因此,最终得到一个隐藏类属性的实例属性。a.foo = 5在Alex的版本中尝试相同的操作,您会发现它b.foo没有变化。
2014年

30

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

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

并以视觉形式

在此处输入图片说明

类属性分配

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

    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

19

由于此处评论中的人们以及其他两个标记为重复的问题似乎都以相同的方式引起了混淆,因此我认为有必要在Alex Coventry的基础上再增加一个答案。

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

>>> 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那是为了避免遇到两个我不想讨论的其他问题;由于两个不同的原因,完全独立创建的5s最终可能是相同的数字实例,5但完全不能单独创建object())。


那么,为什么a.foo.append(5)在Alex的示例中会影响b.foo,但a.foo = 5在我的示例中却没有呢?那么,尝试a.foo = 5在Alex的例子,并注意不影响b.foo两种

a.foo = 5只是a.foo为...而出名5。这不会影响b.foo,也不会影响以前a.foo引用的旧值的任何其他名称。*我们正在创建一个隐藏类属性的实例属性,这有点棘手,但是一旦得到,就没有什么复杂的了发生在这里。


希望现在可以清楚地知道Alex使用列表的原因:您可以对列表进行变异的事实意味着,更容易显示两个变量命名相同的列表,并且这也意味着在现实生活中的代码中更重要的是要知道您是否具有两个列表或同一列表的两个名称。


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

**有些人通过将class属性用作实例属性的“默认值”来利用此属性,实例属性可以设置也可以不设置。在某些情况下这可能很有用,但也可能造成混淆,因此请谨慎使用。


0

还有另一种情况。

类和实例属性是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,然后检查要查找的属性是否指向数据描述符(这仅是同时实现__get____set__方法的类)。如果是这样,请通过调用__get__数据描述符的方法来解析属性查找(第28-33行)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.