144

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

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

对比

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

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

4

5 回答 5

158

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

  • 当在实例上定义属性时(这是我们通常所做的),可以引用多个对象。每个都获得该属性的一个完全独立的版本
  • 当在 class 上定义属性时,只有一个底层对象被引用,因此如果对该类的不同实例的操作都尝试设置/(追加/扩展/插入/等)该属性,则:
    • 如果属性是内置类型(如 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    
[]
于 2008-10-16T01:26:53.000 回答
42

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

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

于 2008-10-16T08:16:28.370 回答
37

这是一篇非常好的帖子,总结如下。

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 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
    
于 2015-12-07T03:59:08.480 回答
21

由于此处评论中的人和标记为 dups 的其他两个问题中的人们似乎都以同样的方式对此感到困惑,我认为值得在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那是为了避免遇到我不想在这里讨论的另外两个问题;由于两个不同的原因,完全单独创建5的 s 最终可能成为number 的相同实例5。但完全单独创建object()的 s 不能。)


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

a.foo = 5只是成为a.foo一个名字5。这不会影响b.foo, 或任何其他a.foo用于引用旧值的名称。* 我们正在创建一个隐藏类属性的实例属性有点棘手,** 但是一旦你明白了,没有什么复杂的了发生在这里。


希望现在 Alex 使用列表的原因很明显:您可以更改列表这一事实意味着更容易显示两个变量命名同一个列表,也意味着在实际代码中了解您是否有两个列表或同一个列表的两个名称。


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

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

于 2014-10-29T23:32:51.803 回答
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 行)。

于 2017-06-22T08:27:20.127 回答