python - `classmethod` 和元类方法有什么区别?

标签 python methods metaclass class-method

在 Python 中,我可以使用 @classmethod 创建一个类方法。装饰师:

>>> class C:
...     @classmethod
...     def f(cls):
...             print(f'f called with cls={cls}')
...
>>> C.f()
f called with cls=<class '__main__.C'>

或者,我可以在元类上使用普通(实例)方法:
>>> class M(type):
...     def f(cls):
...             print(f'f called with cls={cls}')
...
>>> class C(metaclass=M):
...     pass
...
>>> C.f()
f called with cls=<class '__main__.C'>

C.f() 的输出所示,这两种方法提供了类似的功能。

使用 @classmethod 有什么区别?并在元类上使用普通方法?

最佳答案

由于类是元类的实例,因此元类上的“实例方法”的行为类似于类方法也就不足为奇了。

但是,是的,存在差异——其中一些不仅仅是语义上的:

  • 最重要的区别是元类中的方法对类实例是不“可见的”。发生这种情况是因为 Python 中的属性查找(以简化的方式 - 描述符可能优先)在实例中搜索属性 - 如果它不存在于实例中,Python 然后在该实例的类中查找,然后搜索继续该类的父类(super class),但不在该类的类上。 Python 标准库在 abc.ABCMeta.register 中使用了这个特性。方法。
    该功能可以很好地使用,因为与类本身相关的方法可以自由地重新用作实例属性而不会发生任何冲突(但方法仍然会发生冲突)。
  • 另一个区别虽然很明显,但在元类中声明的方法可以在多个类中使用,而不是其他相关的 - 如果您有不同的类层次结构,它们处理的内容完全不相关,但希望所有类都有一些共同的功能,你必须想出一个 mixin 类,它必须作为基础包含在两个层次结构中(比如在应用程序注册表中包含所有类)。 (注意,mixin 有时可能比元类更好)
  • 类方法是专门的“类方法”对象,而元类中的方法是普通函数。

  • 因此,恰好类方法使用的机制是“descriptor protocol”。而普通函数具有 __get__插入 self 的方法从实例中检索它们时的参数,并在从类中检索时将该参数留空,classmethod对象有不同的__get__ ,这将在这两种情况下插入类本身(“所有者”)作为第一个参数。

    这在大多数情况下没有实际差异,但是如果您希望将方法作为函数访问,以便为元类中的方法动态添加装饰器或其他任何方法 meta.method检索函数,准备使用,而您必须使用 cls.my_classmethod.__func__从类方法中检索它(然后您必须创建另一个 classmethod 对象并将其分配回,如果您进行一些包装)。

    基本上,这些是两个例子:

    
    class M1(type):
        def clsmethod1(cls):
            pass
    
    class CLS1(metaclass=M1):
        pass
    
    def runtime_wrap(cls, method_name, wrapper):
        mcls = type(cls)
        setattr(mcls, method_name,  wrapper(getatttr(mcls, method_name)))
    
    def wrapper(classmethod):
        def new_method(cls):
            print("wrapper called")
            return classmethod(cls)
        return new_method
    
    runtime_wrap(cls1, "clsmethod1", wrapper)
    
    class CLS2:
        @classmethod
        def classmethod2(cls):
            pass
    
     def runtime_wrap2(cls, method_name, wrapper):
        setattr(cls, method_name,  classmethod(
                    wrapper(getatttr(cls, method_name).__func__)
            )
        )
    
    runtime_wrap2(cls1, "clsmethod1", wrapper)
    

    换句话说:除了元类中定义的方法在实例中可见的重要区别之外,classmethod对象不这样做,其他差异在运行时会显得晦涩和无意义 - 但发生这种情况是因为语言不需要使用类方法的特殊规则:两种声明类方法的方式都是可能的,结果来自语言设计 - 一个是因为一个类本身就是一个对象,另一个是使用描述符协议(protocol)的可能性,它允许在实例和类中专门化属性访问:
    classmethod builtin 是在 native 代码中定义的,但它可以只用纯 python 编码并且以完全相同的方式工作。下面的 5 行类可用作 classmethod与内置 @classmethod" at all (though distinguishable through introspection such as calls to 没有运行时差异的装饰器实例 , and even repr`当然):

    
    class myclassmethod:
        def __init__(self, func):
            self.__func__ = func
        def __get__(self, instance, owner):
            return lambda *args, **kw: self.__func__(owner, *args, **kw)
    

    而且,除了方法之外,有趣的是要记住诸如 @property 之类的专用属性。在元类上将作为专门的类属性工作,完全一样,没有任何令人惊讶的行为。

    关于python - `classmethod` 和元类方法有什么区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59341761/

    相关文章:

    python - 元类的 __repr__ 类,而不是类

    python - 使用 type() 手动创建新类时,子类 __module__ 设置为元类模块

    python - 我从 Quandl 检索股票数据时哪里出错了?

    javascript - 如何使用实用程序函数来检测操作系统和浏览器的 react ?

    python - 如何将当前对象引用(自身)传递给 timeit 模块的 Timer 类?

    java:39: 找不到符号 symbol : 方法 exponent(double,int) else { return base * exponent(base, exponent - 1);}

    Java:静态初始化 block 中的方法比主方法中的方法慢

    python - 我可以在不使用 Python 3.6 中的元类的情况下编写 abc.ABC 吗?

    python - Pip 不适用于 Ubuntu 上的 Python 3.10

    python - 快速多项式移位