正确的方法来使用类级原语

时间:2013-06-12 20:28:12

标签: python

处理Python中的类变量的方式对我没有任何意义。看来类变量的范围取决于它的类型!原始类型被视为实例变量,复杂类型被视为类变量:

>>> class A(object):
...   my_class_primitive = True
...   my_class_object = ['foo']
... 
>>> a = A()
>>> a.my_class_primitive, a.my_class_object
(True, ['foo'])
>>> b = A()
>>> b.my_class_primitive, b.my_class_object
(True, ['foo'])
>>> a.my_class_object.append('bar')
>>> b.my_class_primitive, b.my_class_object
(True, ['foo', 'bar'])
>>> a.my_class_primitive = False
>>> b.my_class_primitive, b.my_class_object
(True, ['foo', 'bar'])
>>> a.my_class_primitive, a.my_class_object
(False, ['foo', 'bar'])

有人可以解释以下内容:

  1. 为什么存在此功能?它背后的逻辑是什么?
  2. 如果我想使用基本类型(例如bool)作为类变量,我该怎么办?

5 个答案:

答案 0 :(得分:1)

该功能作为Python类定义的缓存形式存在。

类定义中定义的属性本身被视为类的static属性。例如它们不应该被修改。

我确信假设您遵循修改静态属性的最佳实践做出了决定;)

答案 1 :(得分:1)

你应该做的正确比较是:

>>> class A(object):
    my_class_primitive = True
    my_class_object = ['foo']


>>> a = A()
>>> b = A()
>>> a.my_class_primitive = False
>>> a.my_class_object = ['bar']
>>> b.my_class_primitive, b.my_class_object
(True, ['foo'])

这不是你所想的。 “奇怪”行为是由可变性的概念引起的。也就是说,当您将不同的对象分配给my_class_object时,您正在更改分配给my_class_primitive属性的对象。在第一种情况下,它适用于所有实例(因为这是一个对象),而在第二种情况下,它适用于单个实例(您正在更改其属性)。

在这种困惑中,你并不孤单。要查看可变性如何影响代码的另一个示例,您可以查看:"Least Astonishment" and the Mutable Default Argument

答案 2 :(得分:1)

>>> class A(object):
...   my_class_primitive = True
...   my_class_object = ['foo']

类属性存储在A.__dict__

>>> A.__dict__
>>> <dictproxy {'__dict__': <attribute '__dict__' of 'A' objects>,
 '__doc__': None,
 '__module__': '__main__',
 '__weakref__': <attribute '__weakref__' of 'A' objects>,
 'my_class_object': ['foo'],
 'my_class_primitive': True}>

>>> a = A()
>>> a.my_class_primitive, a.my_class_object
(True, ['foo'])
>>> b = A()
>>> b.my_class_primitive, b.my_class_object
(True, ['foo'])

声明a.my_class_object.append('bar') 变异列表A.my_class_object。它检索现有列表a.my_class_object,然后调用其append方法:

>>> a.my_class_object.append('bar')

所以b.my_class_object也会受到影响:

>>> b.my_class_primitive, b.my_class_object
(True, ['foo', 'bar'])

作业a.my_class_primitive = False a添加新的实例属性。它将键值对放在a.__dict__

>>> a.my_class_primitive = False

>>> a.__dict__
>>> {'my_class_primitive': False}

所以b不受影响:

>>> b.my_class_primitive, b.my_class_object
(True, ['foo', 'bar'])

Python's attribute lookup rules导致a.my_class_primitive返回a.__dict__中找到的密钥的值,而不是A.__dict__中找到的密钥的值:

>>> a.my_class_primitive, a.my_class_object
(False, ['foo', 'bar'])

答案 3 :(得分:1)

这不是原始的和复杂的。当您追加到a.my_class_object时,您将修改现有对象。分配给变量时,不要对其进行修改。如果您像对待布尔值那样对待它们,则列表存在相同的“问题”:

>>> class Foo(object):
...     x = []
...
>>> i1 = Foo()
>>> i2 = Foo()
>>>
>>> i1.x = 5
>>>
>>> print(i2.x)
[]

获取 i1.x时,Python会查看i1.__dict__的属性。如果它在那里找不到它,它会查看该对象的每个父类的__dict__,直到它(或抛出AttributeError)。返回的对象根本不必是i1的属性。

当您分配给i1.x时,您专门指定给i1.x

要修改类属性,请参阅类,而不是实例:

>>> class Foo(object):
...     x = 2
...
>>> i1 = Foo()
>>>
>>> Foo.x = 5
>>>
>>> print(i1.x)
5

答案 4 :(得分:1)

原语和复杂类型之间的区别不在于,分配和修改变量之间存在差异。

如果使用instance.name = value,即使已存在同名的类属性,也始终会分配新的实例变量。

例如:

>>> class A(object):
...   my_class_primitive = True
...   my_class_object = ['foo']
...
>>> a = A()
>>> a.my_class_primitive = False
>>> a.__class__.my_class_primitive
True
>>> a.__dict__
{'my_class_primitive': False}

此处与附加到列表的行为的不同之处在于,当您对类实例执行属性查找时,它将首先查找实例变量,如果没有具有该名称的实例变量,它将查找类属性。例如:

>>> a.my_class_object is a.__class__.my_class_object
True

因此,当您执行a.my_class_object.append(x)时,您正在修改任何实例可以访问的class属性。如果您要执行a.my_class_object = x,那么它不会以任何方式修改类属性,它只会创建一个新的实例变量。