0

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)作为类变量,我该怎么做?
4

5 回答 5

1

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

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

我确信这个决定是假设您在修改静态属性方面遵循最佳实践;)

于 2013-06-12T20:31:00.853 回答
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'])

这不是你以为你看到的。“奇怪”的行为是由mutability的概念引起的。即,您正在更改分配给该my_class_object属性的对象,而您正在将不同的对象分配给my_class_primitive. 在第一种情况下,它适用于所有实例(因为这是一个对象),而在第二种情况下,它适用于单个实例(您正在更改其属性)。

在这种混乱中,你并不孤单。要查看可变性如何影响您的代码的另一个示例,您可以检查以下内容:“Least Astonishment”和 Mutable Default Argument

于 2013-06-12T20:34:23.370 回答
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') 改变了 list 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 的属性查找规则导致a.my_class_primitive返回在 中找到的键a.__dict__的值,而不是在 中找到的键的值A.__dict__

>>> a.my_class_primitive, a.my_class_object
(False, ['foo', 'bar'])
于 2013-06-12T20:34:45.520 回答
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
于 2013-06-12T20:36:15.780 回答
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)您正在修改任何实例都可以访问的类属性。如果你这样做,a.my_class_object = x那么它不会以任何方式修改类属性,它只会创建一个新的实例变量。

于 2013-06-12T20:40:48.047 回答