class 属性和具有默认值的实例变量之间的区别

Difference between class attribute and instance variable with default value

  1. class 变量和具有默认值的实例变量之间有什么区别吗?

(特别是在 "normal use" 下的行为方面,在内部我认为它们很可能以不同的方式实现)

  1. 在什么情况下应该使用哪个版本?

以这两个class为例:

class A:
    d = 4

class A:
    def __init__(self, d=4):
        self.d = d

无论你选择什么版本,当你运行下面的代码时,你会得到相同的结果:

a2 = A()

a = A()
print(a.d)   # 4
a.d = 2
print(a.d)   # 2

print(a2.d)  # 4

看完之后想到了这个问题:

  1. class attribute behavior

Are there any differences between a class variable and an instance variable with a default value?

嗯,显然是的:一个 class 属性(不是 "variable")属于 class,一个实例属性属于实例。

In what context should I use which version?

当您希望属性由 class 的所有实例共享时使用 class 属性,当您希望属性特定于该实例时使用实例属性。实际上,您很少需要 class 属性。

请注意,如果您为 class 和实例定义相同的属性,则实例上的属性将隐藏 class 的属性。

nb: 以上是非常非常粗略的简化,否则我需要解释整个 Python 对象模型,这值得写一本书

Take these two classes as an example (...) no matter what version you choose, when you run the code below, you'll get the same result

是的,这是此代码段的预期结果。

对于a

在第一种情况下,当您第一次打印 a.d 时,a 没有实例属性 d 所以您得到的是 class 属性值.然后通过分配给它来创建实例属性 a.d,然后它会隐藏 class 属性。

在第二种情况下,a.d 最初有它的默认值,然后你将它重新绑定到另一个值...很普通的东西。

对于a2

在第一种情况下,a2.a 将始终是 4,因为您没有使用实例属性对其进行隐藏,因此它从 class 获取值。

在第二种情况下,它将始终是 4,因为您没有重新绑定实例属性,所以它仍然是默认值。

现在用列表作为属性尝试同样的事情,并附加到列表而不是重新绑定它:

class A:
    d = []

class B:
    def __init__(self):
        self.d = []


def test(cls):
    print("test {}".format(cls.__name__))
    a = cls()
    print(a.d)   
    a.d.append(2)
    print(a.d)   
    a2 = cls()
    print(a2.d)  

if __name__ == "__main__":
    test(A)
    test(B)

最后一点:您可能已经看到(或者有一天您可能会看到)代码使用 class 属性作为实例的默认值 - 或者您可能想自己这样做(因此提到'default' 实例属性的值)-,就像您的第一个示例一样。 这是不好的做法。它充其量是令人困惑的,如果属性是可变类型,则可能导致错误行为。

TLDR:差异对可见性和特殊 class 属性(例如描述符)很重要。它还会影响 class 签名。


相似之处

定义 class 属性时,它存储在 class 上。同样,当您为方法定义默认值时,it is stored on the method 并且该方法又存储在class 上。最后,class 属性和方法默认值都存储在 class 上——后者只是增加了一个间接级别。

class A:
    # store d on class
    d = 4

class B:
    # store init on class
    def __init__(self, d=4):  # store d on method
        self.d = d

这两个值都是可访问和可写的。它们在可变性方面具有相同的属性,例如如果值为 list.

>>> A.d
4
>>> B.__init__.__defaults__[0]
4
>>> A.d = 3
>>> B.__init__.__defaults__ = (3,)
>>> A.d
3
>>> B.__init__.__defaults__[0]
3

差异

与 class 或实例属性(即函数等描述符)表现不同的值存在差异。

class AD:
    d = lambda x='Nothing': x

class BD:
    def __init__(self, d=lambda x='Nothing': x):
        self.d = d

查找将调用或跳过描述符协议,导致不同的行为:

>>> AD().d()  # class attribute
<__main__.AD at 0x10d4c7c10>
>>> BD().d()  # instance attribute
'Nothing'

在 class 上存储默认值本质上与该属性的描述符不兼容。例如,__slots__property 需要 __init__ 上的默认值。

class BS:
    __slots__ = 'd',

    def __init__(self, d=4):
        self.d = 4

申请

最重要的区别是 __init__ 默认值意味着一个参数。存储在 class 上的默认值不是 class 签名的一部分。

>>> B(d=42)
<__main__.B at 0x10d4dac90>
>>> A(d=42)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: A() takes no arguments

因此,当属性应该是可定制的时,总是使用 __init__ 默认值。相反,当属性始终以相同常量开头时,请考虑 class 默认值。

请注意,如果一个属性始终以相同的值开头但并非 不可变,请在__init__ 中对其进行初始化。如果您需要可变属性的默认值,请使用占位符并在方法中创建默认值。

class C:
    def __init__(self, e=None):
        self.d = [1, 2, 3, 4]
        self.e = e if e is not None else []