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

时间:2008-10-16 00:23:42

标签: python attributes member-variables

是否有任何有意义的区别:

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

VS

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

如果您要创建大量实例,那么这两种样式的性能或空间要求是否存在差异?当您阅读代码时,您是否认为这两种风格的含义有很大不同?

6 个答案:

答案 0 :(得分:137)

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

>>> 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    
[]

答案 1 :(得分:36)

不同之处在于类的属性由所有实例共享。实例上的属性对于该实例是唯一的。

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

答案 2 :(得分:26)

这是一个非常好的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

以视觉形式

enter image description here

类属性分配

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

    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
    

答案 3 :(得分:19)

由于此处的评论和其他两个标记为重复的问题的人都以同样的方式对此感到困惑,我认为值得在Alex Coventry's之上添加额外的答案。

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


那么,为什么Alex的示例中的a.foo.append(5)会影响b.foo,但我的示例中的a.foo = 5却不会?好吧,在Alex的示例中尝试a.foo = 5,并注意它不会影响b.foo那里的

a.foo = 5只是将a.foo变为5的名称。这不会影响b.fooa.foo过去引用的旧值的任何其他名称。*我们创建一个隐藏类属性的实例属性,这有点棘手* *但是一旦你得到了,这里就不会发生任何复杂的事了。


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


*来自像C ++这样的语言的人的困惑在于,在Python中,值不存储在变量中。值存在于value-land中,变量只是值的名称,赋值只是为值创建一个新名称。如果有帮助,请将每个Python变量视为shared_ptr<T>而不是T

**有些人通过将类属性用作实例可能设置或未设置的实例属性的“默认值”来利用此功能。这在某些情况下很有用,但也可能令人困惑,所以要小心。

答案 4 :(得分:2)

只是详细阐述了Alex Coventry所说的话,另一位Alex(Martelli)几年前在comp.lang.python新闻组发表了类似的问题。他检查了一个人的意图与他得到的内容之间的语义差异(通过使用实例变量)。

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

答案 5 :(得分:0)

还有一种情况。

类和实例属性是描述符

# -*- 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 definition找到了一个很棒的post

解释

  

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