Python类设置器 - 每次创建新实例时都会重新分配类属性吗?
Posted
技术标签:
【中文标题】Python类设置器 - 每次创建新实例时都会重新分配类属性吗?【英文标题】:Python class setter - are class attributes reassigned every time a new instance is created? 【发布时间】:2021-07-17 08:46:57 【问题描述】:我正在尝试创建一个具有由所有实例共享的基本属性的类,该类在初始化实例属性时用作模板。理想情况下,它将是最终的/不可变的,但这是 Python,所以我只是不打算覆盖它。
class Foo:
class_template_attr = some_resource_intensive_operation()
def __init__(self, *args, **kwargs):
self.instance_attr = method_using_class_attribute()
def method_using_class_attribute():
# determine instance_specific_extras
return self.class_template_attr + instance_specific_extras
我想避免在每次实例化新的Foo
时重新生成class_template_attr
,主要是因为它是一个资源密集型操作,但也因为它应该对每个Foo
都相同,所以它应该' t被再生。高高在上,看低了,一直无法确定创建新实例时是否重新计算类属性。
如果它们被重新计算,我会假设我可以定义一个 setter 来检查 class_template_attr
是否存在,然后创建它或传递该属性(如果它已经存在)。我目前正在使用元类来执行此操作,但我想知道这是否是最 Pythonic 的事情(或者我是否一开始就做对了)。
class MetaFoo(type):
def __init__(cls, *args, **kwargs):
super.__init__(*args, **kwargs)
cls.class_template_attr = some_resource_intensive_operation()
@property
def class_template_attr(cls):
return cls.__class_template_attr
@class_template_attr.setter
def class_template_attr(cls, value):
if cls.__class_template_attr is None:
cls.__class_template_attr = value
else:
pass
def bar(input):
value = some_operation
return value
class Foo(metaclass=MetaFoo):
def __init__(self, object_specific_input):
self.instance_attr = bar(object_specific_input)
@property
def instance_attr(self):
return self.__instance_attr
@instance_attr.setter
def instance_attr(self, value):
self.__instance_attr = value
注意:我意识到实例属性的 getter/setter 在这里是多余的,但我将它们用于特定目的,我不会去为了 MRE 的缘故。
【问题讨论】:
【参考方案1】:我想知道为什么你认为如果你没有在类和实例中获得最基本的属性,你就可以成功编写元类。
事实上,你所描述的你所需要的是 Python 中对象的基本行为:你的属性是在类体执行时(通常在模块导入时)计算一次,并且在所有实例中都可用。
此外,如果在一个实例中有代码执行如下操作:self.class_template_attr = "new value"
,则只会在该实例中创建一个新属性,这将隐藏类属性 - 但仅针对该实例。原始值将保持所有其他实例的默认可见值。
现在,关于您的元类:您尝试在元类本身上创建一个属性,并将其级联到类 - 这肯定会有点过头了。
您的实现也存在一些问题 - 但从非错误开始:虽然“@property”可以在元类上工作并且可以作为类对象的属性使用,但它可以工作,但你赢了'在任何文档中都找不到这样的例子:因为它们不打算以这种方式使用。它们之所以起作用,是因为 Python 在其规则上非常一致,然后,作为元类的对象和实例的类也不例外,描述符协议是“使property
工作的原因”:将使用元类上的描述符用于类的属性检索。但是,元类上的相同描述符不会用于从该类的实例中检索属性。
在实现上还有一些其他的问题,但压倒性的冗余是最糟糕的部分。我要添加一个旁注,不要过多地依赖“私有”属性的双下划线前缀。有时它可能很有用,但它肯定比具有适当public
、private
和protected
修饰符的语言要少得多——它们更用于防止使用多重继承和复杂类层次结构中的名称冲突mixins,否则迟早会妨碍你。
Python 中的属性检索
关于制作myinstance.myattr
:
-
Python 将首先检查属性是否存在于 myinstance 的类中,如果存在,则检查它是否是描述符(属性值的类应该具有
__get__
方法。property
实现了该方法)。如果是,则调用它,并返回结果值;
Python 将检查实例本身的属性。通常,作为实例的__dict__
上的条目。如果是,则返回该值。
Python 在实例的类中查找属性,不管它是描述符。
如果未找到,它将检查该类是否具有 __getattr__
方法 - 将使用预期的属性名称调用该方法,并返回一个值或引发 AttributeError。
如果到目前为止没有找到任何东西,则搜索失败,并引发 AttributeError。
请注意,对于上面的步骤 (1)、(3) 和 (4),搜索的是类,而不是它自己的元类 - 相反,搜索是通过继承链进行的:在每个步骤中,超类(在它们在myinstance.__class__.__mro__
) 中显示的顺序被搜索以查找该属性。
将property
用于只读类属性
如果设置一个普通的类属性还不够,并且您想防止实例意外地用self.myattr = "foo"
覆盖实例值,可以在类主体上创建一个只读属性。无需求助于元类。
请注意,这仍然不会阻止属性在类本身中被覆盖(使用self.__class__.myattr = "new value"
- 如果您想阻止,那么您需要一个元类并自定义它__setattr__
方法。这就是 Python 中“同意的成年人”的概念:如果一个人一直写 self.__class__.myattr = "new value"
,他们非常担心更改属性,而不管课程作者的意图如何,并愿意为此承担责任)。
无论如何,例如不可变的属性,并且,作为额外的,你得到你的昂贵值延迟计算:它将在第一次实际需要它时计算,而不是在模块导入时计算:有一个权衡:您在应用启动时获得敏捷性,并在实际第一次使用该计算时支付费用:
class Foo:
@property
def my_attribute(self):
if not hasattr(self, "_my_attribute"):
self._my_attribute = some_resource_intensive_operation()
return self._my_attribute
现在 - 这就是我们所需要的。没有“setter”:第一次读取值时,计算该值并将其存储在“秘密”私有位置中。如果没有设置器,尝试在实例中设置其值时会出现属性错误。不需要元类。
【讨论】:
感谢您抽出宝贵时间回复! 如果我理解正确,类属性是在定义/导入类时计算的。即使不是,将属性定义为属性并在分配之前使用hasattr
检查它的存在会更简单/pythonic。尽管元类示例有效,但它非常不符合 Python 风格,而且还有些矫枉过正。
是的。 (此文本达到最小评论长度)【参考方案2】:
我意识到我可以轻松地测试我自己的问题,如下所示:
import random, string
def get_random_string():
return ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))
random.seed(10)
class TestClassAttributeInstantiation:
class_attr = get_random_string()
def __init__(self):
self.inst_attr = get_random_string()
然后,在解释器中:
>>> TestClassAttributeInstantiation.class_attr
'OLPFVV'
>>> foo = TestClassAttributeInstantiation()
>>> foo.class_attr
'OLPFVV'
>>> foo.inst_attr
'QENIGY'
>>> bar = TestClassAttributeInstantiation()
>>> bar.class_attr
'OLPFVV'
>>> bar.inst_attr
'ZBWPJH'
如您所见,class_attr
是在定义类而不是实例化时设置的。这一点很明显,所有实例化都有一个具有相同值的class_attr
,并且对类的引用也具有该值。
【讨论】:
以上是关于Python类设置器 - 每次创建新实例时都会重新分配类属性吗?的主要内容,如果未能解决你的问题,请参考以下文章
Python 类为每次执行的@property 提供不同的输出
每次代码更改时都会重新生成 DataBindingInfo.java