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

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

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

对。

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

如果要创建大量实例,那么这两种样式对性能或空间的要求是否有所不同?当您阅读代码时,您是否认为这两种样式的含义有显著的不同?

71873 次浏览

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

  • 当属性在实例 上定义时(这是我们通常做的) ,可以引用多个对象。每个属性都有一个完全独立的版本.
  • 当属性在类 上定义时,只有一个底层对象被引用,所以如果对该类的不同实例的操作都试图设置/(append/tended/insert/etc)属性,那么:
    • 如果属性是 内置式(如 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
[]

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

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

因为人们在这里的评论和其他两个被标记为 dup 的问题中似乎都以同样的方式对此感到困惑,我认为值得在 亚历克斯 · 考文垂的之上添加一个额外的答案。

事实上,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.foo,或 a.foo用来引用的旧值的任何其他名称。我们创建了一个隐藏 class 属性的实例属性,这有点复杂,但是一旦你明白了这一点,就不会有什么复杂的事情发生了。


希望 Alex 使用列表的原因现在已经很明显了: 可以对列表进行变异这一事实意味着更容易显示两个变量命名同一列表,也意味着在实际代码中更重要的是要知道是否有两个列表或两个名称用于同一列表。


* 对于使用 C + + 这样的语言的人来说,令人困惑的是,在 Python 中,值并不存储在变量中。值存在于值中——变量本身只是值的名称,赋值只是为值创建一个新名称。如果有帮助的话,可以将每个 Python 变量看作 shared_ptr<T>,而不是 T

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

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

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 属性,它将覆盖 < em > 所有实例的值

    foo = Bar(2)
    foo.class_var
    ## 1
    Bar.class_var = 2
    foo.class_var
    ## 2
    
  • If a class variable is set by accessing an instance, it will override the value only for that instance. This essentially overrides the class variable and turns it into an instance variable available, intuitively, only for that instance.

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

When would you use class attribute?

  • Storing constants. As class attributes can be accessed as attributes of the class itself, it’s often nice to use them for storing Class-wide, Class-specific constants

    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
    
  • Defining default values. As a trivial example, we might create a bounded list (i.e., a list that can only hold a certain number of elements or fewer) and choose to have a default cap of 10 items

    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
    

还有一个情况。

类和实例属性是 描述符

# -*- 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 定义和一个很好的 邮寄

解释一下

如果属性在组成。 然后检查查找的属性是否指向数据描述符(仅仅是实现 __get____set__方法的类)。 如果是,通过调用 Data Descriptor 的 __get__方法解决属性查找(第28-33行)。