Python:类和实例属性之间的区别

有什么有意义的区别:

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

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

如果您创build了很多实例,那么这两种样式的性能或空间要求是否有所不同? 当你阅读代码时,你认为这两种风格的意义有很大的不同吗?

除了性能考虑之外,还有一个重要的语义差异。 在类属性的情况下,只有一个对象被引用。 在instance-set-instance-instance中,可以引用多个对象。 例如

 >>> 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 ++,类的属性更像静态成员variables。

因为在这里的评论以及另外两个被称为dups的问题似乎都以同样的方式混淆了这个问题,所以我认为值得在Alex Coventry的基础上增加一个额外的答案。

Alex分配一个可变types值的事实,如列表,与事物是否共享无关。 我们可以用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 ,那就是为了避免遇到两个我不想在这里讨论的其他问题;由于两个不同的原因,完全分开创build的5 s最终可能是数字5的同一个实例,但是完全独立创build的object()不能)。


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

a.foo = 5只是把a.foo变成5的名字。 这并不影响b.foo或者其他的a.foo用来引用的旧值。*我们创build一个隐藏类属性**的实例属性有点棘手,但是一旦你明白了,这里没有什么复杂的事情发生


希望现在很明显,为什么Alex使用了一个列表:事实上,你可以改变一个列表意味着更容易显示两个variables名称相同的列表,也意味着在现实生活中的代码更重要的是知道你是否有两个列表或两个名字相同的名单。


*来自像C ++这样的语言的人们的困惑是,在Python中,值不是存储在variables中的。 价值生活在价值的土地上,variables本身只是价值观的名称,赋值只是为价值创造了一个新名字。 如果有帮助的话,把每个Pythonvariables看作是一个shared_ptr<T>而不是T

**有些人通过使用类属性作为实例属性的“默认值”来利用这一点,实例可能会或可能不会设置。 这在某些情况下可能有用,但也可能会造成混淆,所以要小心。

这里是一个非常好的post ,并总结如下。

 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 

并以视觉forms

在这里输入图像描述

类属性分配

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

     foo = Bar(2) foo.class_var ## 1 Bar.class_var = 2 foo.class_var ## 2 
  • 如果一个类variables是通过访问一个实例来设置的,那么它只会覆盖该实例的值。 这基本上覆盖了类variables,并将其变为可用的实例variables,直观地说, 仅针对该实例

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

你什么时候会使用class属性?

  • 存储常量 。 由于类属性可以作为类本身的属性来访问,因此使用它们来存储Class-Class,Class-specific常量

     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 
  • 定义默认值 。 作为一个微不足道的例子,我们可以创build一个有界列表(即只能包含一定数量或更less的元素的列表),并且select一个默认的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 

另一个Alex(Martelli)在comp.lang.python新闻组回复了一个类似的问题。 他检查一个人的意图与他所得到的语义差异(通过使用实例variables)。

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

还有一个情况。

类和实例属性是描述符

 # -*- 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>) 

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

我发现在c.PyObject_GenericGetAttr的定义 ,和一个伟大的职位 。

说明

如果在组成的类的字典中find该属性。 对象MRO,然后检查被查找的属性是否指向一个Data Descriptor(这不过是一个实现__get____set__方法的类)。 如果是这样,则通过调用数据描述符的__get__方法(第28-33行)来parsing属性查找。