为啥元类适用于类属性但@classmethod @property 不适用?

Posted

技术标签:

【中文标题】为啥元类适用于类属性但@classmethod @property 不适用?【英文标题】:Why metaclass works for a class propery but a @classmethod @property doesn't?为什么元类适用于类属性但@classmethod @property 不适用? 【发布时间】:2021-12-24 16:18:42 【问题描述】:

这是 Django deb 模型的简化重新实现。

这很有效(类似于 Django 所做的,虽然它是一个属性,而不是一个属性):

class ClassPropertyType(type):
    @property
    def objects(cls):
        return cls.init_empty_storage_if_needed()


class Model(metaclass=ClassPropertyType):
    @classmethod
    def init_empty_storage_if_needed(cls):
        if not hasattr(cls, "__objects_storage"):
            cls.__objects_storage = ObjectStorage(cls)
        return cls.__objects_storage


class ObjectStorage:
    def __init__(self, model_class):
        pass

    def all(self):
        pass

Model.objects.all()

这个:

class Model:
    @classmethod
    def init_empty_storage_if_needed(cls):
        if not hasattr(cls, "__objects_storage"):
            cls.__objects_storage = ObjectStorage(cls)
        return cls.__objects_storage

    @classmethod
    @property
    def objects(cls):
        return cls.init_empty_storage_if_needed()


class ObjectStorage:
    def __init__(self, model_class):
        pass

    def all(self):
        pass

Model.objects.all()

给出一个例外:

Traceback (most recent call last):
  File "classmethod_property.py", line 21, in <module>
    Model.objects.all()
AttributeError: 'property' object has no attribute 'all'

我想使用更直观的@classmethod+@property 方式。如何让它做同样的事情?

【问题讨论】:

【参考方案1】:

您的代码确实有效 - 只是 classmethod 在最近的 Python 中被修改为与 property 很好地配合(我认为只有在 3.10 中)。

在此之前,如果您认为,property 装饰器将方法转换为与方法截然不同的对象:如果没有特定代码,“classmethod”就无法以透明的方式使用它。

在底层,发生的是:对于类方法和属性, 从类中检索属性时,例如在Model.objects 中,Python 运行时检查绑定到Model 类主体上的objects 的对象是否具有__get__ 方法。 (propertyclassmethod 和函数对象都实现了这一点)。使用“instance=None, owner=Model”参数调用。获取“instance=None”时的常规“属性”将返回自身:这是检索属性的常规方式。在较新的 Python 中,类方法会检查它是在装饰属性,而不是常规函数,然后进行适当的调用。

至于“元类” - 元类中的方法或属性只是元类实例(元类'类)的常规方法或属性 - property 不会像装饰时发生的那样包装或转换它与classmethod。这就是它在旧版 Python 中工作的原因:使用普通语言机制 - 使用 instance=Model, owner=MetaModel 调用 MetaModel.objects 属性以获得 Model.objects 访问,然后将该调用转换为原始 objects 函数的调用将实例 (Model) 作为第一个参数 (self),就像常规的“实例”属性一样。

【讨论】:

以上是关于为啥元类适用于类属性但@classmethod @property 不适用?的主要内容,如果未能解决你的问题,请参考以下文章

Python - 元类装饰器 - 如何使用 @classmethod

仅适用于类及其子类的属性

为啥元类不能访问由元类定义的类的子类继承的属性?

为啥 Pylint 在元类定义的属性使用上出错?

为啥一个类具有它的元类的属性?

为啥不在实例属性查找中搜索元类的属性?