微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Python 类设置器 - 每次创建新实例时都会重新分配类属性吗?

如何解决Python 类设置器 - 每次创建新实例时都会重新分配类属性吗?

我正在尝试创建一个类,其基属性由所有实例共享,在初始化实例属性时用作模板。理想情况下,它将是最终的/不可变的,但这是 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
        

我想避免每次实例化新的 class_template_attr 时都重新生成 Foo,主要是因为这是一个资源密集型操作,但也因为每个 {{1} 都应该相同}} 所以它不应该被重新生成。看了高低,一直没能确定新建实例时是否重新计算了类属性

如果它们被重新计算,我会假设我可以定义一个 setter 来检查 Foo 是否存在,然后创建它或传递属性(如果它已经存在)。我目前正在使用元类来做到这一点,但我想知道这是否是最 Pythonic 的事情(或者我是否一开始就做对了)。

class_template_attr

注意:我意识到实例属性的getter/setter在这里有点过分,但我将它们用于特定目的,我不会去为了 MRE。

解决方法

我想知道为什么您认为如果您没有获得类和实例中最基本的属性,您会成功编写元类。

实际上,您所描述的需求是 Python 中对象的基本行为:您的属性在类主体执行时(通常在模块导入时)计算一次,并且在所有实例中都可用。

此外,如果一个实例中有代码执行类似:self.class_template_attr = "new value" 的操作,则只会在该实例中创建一个新属性,该属性会隐藏类属性 - 但仅针对该实例。原始值将保留为所有其他实例的默认可见值。

现在,关于您的元类:您尝试在元类本身上创建一个属性,并将其级联到类 - 这肯定会过分矫枉过正。

你的实现也有一些问题——但从一个非错误开始:虽然“@property”可以在元类上工作并且可以作为类对象上的属性可用,但它可以工作,但你赢了没有在任何文档中找到一个例子:由于它们不打算以这种方式使用。它们之所以起作用是因为 Python 的规则极其一致,然后,作为元类的对象和实例的类也不例外,描述符协议是“使 property 起作用的原因”:元类上的描述符将用于类的属性检索。但是,元类上的相同描述符不会用于从该类的实例中检索属性。

在实现上还有一些其他问题,但压倒性的冗余是最糟糕的部分。我会添加一个旁注,不要过多地依赖“私有”属性的双下划线前缀。它有时很有用,但它肯定比具有适当的 publicprivateprotected 修饰符的语言要少得多——它们更多地用于防止复杂的名称冲突使用多重继承和混合的类层次结构,如果使用其他方式迟早会妨碍您。

Python 中的属性检索

关于制作myinstance.myattr

  1. Python 将首先检查属性是否存在于 myinstance 的类中,如果存在,则检查它是否是描述符(属性值的类应该具有 __get__ 方法。property 实现了那个)。如果是,则调用它,并返回结果值;
  2. Python 将检查实例本身的属性。通常,作为实例的 __dict__ 上的条目。如果是,则返回该值。
  3. Python 在实例的类中查找属性,而不管它是一个描述符。
  4. 如果未找到,它将检查该类是否具有 __getattr__ 方法 - 将使用预期的属性名称调用该方法,并返回一个值或引发 AttributeError。
  5. 如果到目前为止没有找到任何内容,搜索将失败,并引发 AttributeError。

请注意,对于上面的步骤 (1)、(3) 和 (4),搜索的是类,而不是它自己的元类 - 相反,搜索遵循继承链:在这些步骤中的每一步中,超类(在搜索它们在 myinstance.__class__.__mro__) 中显示的顺序。

使用 property 作为只读类属性

如果设置一个简单的类属性还不够,并且您想防止实例意外地用 self.myattr = "foo" 覆盖实例值,可以在类主体上创建只读属性。无需求助于元类。

请注意,这仍然不会阻止属性在类本身中被覆盖(使用 self.__class__.myattr = "new value" - 如果您想阻止那个,那么您需要一个元类和自定义它的 __setattr__ 方法。这就是 Pythonic 的“同意大人”的概念:如果一个人一直写 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”:当第一次读取值时,该值被计算并存储在一个“秘密”的私人位置。如果没有 setter,尝试在实例中设置其值时会出现属性错误。不需要元类。

,

我意识到我可以轻松地测试我自己的问题,如下所示:

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,并且对类的引用也具有该值。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。