实例属性/属性和类属性/属性有什么区别?在 Python 中什么时候使用一个,什么时候使用另一个?
也就是说,如果我们有:
class Foo:
a = 5
def print_a(self):
print(self.a)
是的:
class Foo:
def __init__(self):
self.a = 5
def print_a(self):
print(self.a)
属性有什么区别a
?
根本区别在于类属性由该类的所有实例共享,而实例属性特定于使用该类创建的每个对象。因此,实例变量用于每个对象唯一的数据,而类变量用于必须由该类的所有实例共享的属性。
实例属性
instance.attr
是使用or语法创建的self.attr
(self
这实际上是一种约定,它只是一个引用实例本身的标识符)。如前所述,它们是该实例本地的,因此只能从其中一个访问。作为一般规则,它们是在方法中创建的。实例属性可以在任何实例方法中创建,但通常最好在类的初始化程序 (
__init__
) 或构造函数 (__new__
) 中创建,或者最多在直接从其中一个调用的方法中创建。如果不这样做(即使只是将它们初始化为空值),代码将失去可读性,更重要的是,该属性在创建它的方法被调用之前不存在,这会导致不确定实例是否具有在给定时间的特定属性。至于它们的实用性,我们可以笼统地说它们允许为该特定对象定义某种“状态”,并将其与同类的其他对象区分开来。例如“汽车”类中的“车牌”属性。
它们存储在字典(实例或对象字典)中,您可以通过
NombreInstancia.__dict__
.类属性是在任何类方法之外定义的,通常就在文档字符串的下方。它们可以直接从类中引用,也可以从类的任何实例中引用。常见用途是创建枚举常量,作为Singleton中的控制变量,为实例变量设置默认值,定义常量等。
它们存储在不同的字典(类字典)中,并且独立于用于实例属性的字典,您可以
NombreClase.__dict__
使用NombreInstancia.__class__.__dict__
. 属性和描述符通常也在类级别,而不是实例级别。了解解析属性访问的顺序很重要,Python 在从对象中查找属性时遵循以下顺序:
__dict__
、__slots__
、__weakref__
等)。__set__
)。__get__
但不实现__set__
)。__getattr__
.如果我们忽略数据描述符(因此忽略setter的属性),Python 首先在实例属性中查找属性,如果没有找到,则在类属性中查找。这意味着如果您创建一个与已经存在的类属性同名的实例属性,则如果路径
self.atributo
或试图访问该类属性,则该类属性是“隐藏的”instancia.atributo
。在赋值的情况下,顺序是:
__setattr__
上面基本回答了问题,下面稍微长一些(主要是例子),并试图解释一个常见的错误,它是由从其类或从实例分配给类属性的差异引起的,与解释的解析顺序直接相关以上。
如果有人不想阅读更多ツ,道德是:
如上所述,我们可以通过两种方式访问类属性:
当我们不只是访问我们想要执行赋值的值时,事情会发生变化:
如果我们通过类分配行为符合预期,则该类的所有实例的值都会更改:
您可以看到
n
使用Foo.n
或通过类方法(其中equalscls
指代)修改值是如何反映在类的所有实例中的。Foo
__class__
如果我们使用实例为类变量分配一个新值来引用它,我们会感到惊讶:
instancia.atributo
尝试使用或从通过实例方法将新对象分配给类属性self.atributo
(只要我们不处理我们看到的具有优先级并且会按预期运行的数据描述符)不会修改类属性,它创建具有相同名称的实例属性并分配新值。让我们记住,首先搜索属性(再次分离数据描述符)
__dict__
,然后__class__.__dict__
如果找不到,这会导致类属性隐藏在实例后面,我们可以通过__dict__
and的内容看到它__class__.__dict__
:最后,请记住,在 Python 中有可变对象,例如 (
list
,dict
,set
) 和不可变对象 (int
,float
,bool
,tuple
,frozenset
,str
)。不可变对象一旦创建就不能修改,当我们连接一个字符串时会创建一个新对象,而列表不会发生这种情况:Python 通过引用处理赋值,变量或属性只不过是一个标识符,一个名称,它引用内存中的一个对象。类属性在所有实例之间共享,因为它们引用内存中的同一个对象,所以当我们修改可变类属性时,
instancia.atributo
如果self.atributo
这反映在所有实例中:实例属性不会发生这种情况,因为它特定于每个对象:
如果类属性引用了一个不可变对象,尽管我们尝试在一个实例中“修改”它,但这并不意味着在其余部分中这样做,因为“修改”一个不可变对象总是意味着在两者之间进行赋值,如前所述,来自实例的赋值会导致创建新的实例属性,而不是修改类属性(除非它是数据描述符):
与列表不同,字符串连接(或整数、浮点数等操作)意味着创建一个新对象并尝试将其分配给属性,这意味着创建一个实例属性,就像我们之前看到的那样不是类属性的改变。
尽管描述符与所有这些密切相关并且被反复提及,但它们并没有被输入,因为解释描述符协议将意味着在响应已经可能太长的情况下过多地扩展响应,我们可以在文档本身中看到关于此的一般指南:描述符操作指南
类变量由派生自它的所有实例继承。如果你看这个例子:
pancho
并且fido
是两个Perro
继承了变量值的实例numero_patas
。现在我们改变实例中变量的值fido
现在我们向 fido 添加一个新的实例变量:
在 Python 中,类和实例变量存储在不同的字典中。如果我们查看每个创建实例的字典:
我们看到
pancho
它没有实例变量。但是,当我们要求它时,它numero_patas
已经退回了我们4
。这是因为存在第二个字典,即类字典,当在其字典中找不到实例变量时会访问该字典。如果我们访问它:你会看到它们是相同的。如果你要派生一个类的单个实例,你应该使用实例变量,因为首先访问实例变量字典会快一点。
类属性和实例属性之间的区别在于访问它们的方式,因此对于实例属性,只能通过对象访问,对于类属性,它可以直接从类和/或通过目的。