Python “classmethod”和元类方法之间有什么区别?

Python “classmethod”和元类方法之间有什么区别?,python,methods,metaclass,class-method,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'> >>C类: ... @类方法 ... def f

在Python中,我可以使用
@classmethod
装饰器创建类方法:

>>> class C:
...     @classmethod
...     def f(cls):
...             print(f'f called with cls={cls}')
...
>>> C.f()
f called with cls=<class '__main__.C'>
>>C类:
...     @类方法
...     def f(cls):
...             打印(用cls={cls}'调用f'f)
...
>>>C.f()
用cls呼叫f=
或者,我可以在元类上使用普通(实例)方法:

>>> 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'>
>>M类(类型):
...     def f(cls):
...             打印(用cls={cls}'调用f'f)
...
>>>C类(元类=M):
...     通过
...
>>>C.f()
用cls呼叫f=
C.f()
的输出所示,这两种方法提供了类似的功能


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

在您的示例中,区别在于其他一些将M设置为元类的类

class M(type):
    def f(cls):
        pass

class C(metaclass=M):
    pass

class C2(metaclass=M):
    pass

C.f()
C2.f()
这里有更多关于元类的内容

当你像在问题中那样表达它时,
@classmethod
和元类可能看起来很相似,但它们有着完全不同的用途。在
@classmethod
的参数中注入的类通常用于构造实例(即替代构造函数)。另一方面,元类通常用于修改类本身(例如,就像Django对其模型DSL所做的那样)

这并不是说不能在classmethod中修改类。但问题是,为什么不先用想要修改的方式定义类呢?如果不是,它可能建议重构使用多个类

让我们稍微扩展一下第一个示例

class C:
    @classmethod
    def f(cls):
        print(f'f called with cls={cls}')
借用,上述内容将扩展为以下内容:

class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        def newfunc(*args):
            return self.f(klass, *args)
        return newfunc

class C:
    def f(cls):
        print(f'f called with cls={cls}')
    f = ClassMethod(f)
注意
\uuuu get\uuuuu
如何获取实例或类(或两者),因此您可以同时执行
C.f
C().f
。这与您给出的元类示例不同,它将为
C().f
抛出一个
AttributeError


此外,在元类示例中,
f
不存在于
C.\uu dict\uu
中。当使用
C.f
查找属性
f
时,解释器查看
C.\uuu dict\uuu
,然后在查找失败后,查看
类型(C)。\uu dict\uu
(即
M.\uu dict\uu
)。如果您希望在
C
中灵活地重写
f
,这可能很重要,尽管我怀疑这是否会有实际用途。

由于类是元类的实例,因此元类上的“实例方法”的行为与classmethod类似并不意外

然而,确实存在差异——其中有些差异不仅仅是语义上的:

  • 最重要的区别是元类中的方法在类实例中不“可见”。这是因为Python中的属性查找(以一种简化的方式-描述符可能优先)搜索实例中的属性-如果该属性不在实例中,Python将查找该实例的类,然后继续搜索该类的超类,而不是该类的类。Python stdlib在
    abc.ABCMeta.register
    方法中利用了这个特性。 该特性可以永久使用,因为与类本身相关的方法可以自由地作为实例属性重新使用,而不会产生任何冲突(但方法仍然会有冲突)
  • 另一个明显的区别是,在元类中声明的方法可以在多个类中使用,而不是在其他方面相关-如果您有不同的类层次结构,它们处理的内容根本不相关,但希望所有类都具有一些公共功能,那么您必须提出一个mixin类,这两个层次结构中都必须包含作为基础的类(例如,在应用程序注册表中包含所有类)。(注意,mixin有时可能比元类更好)
  • classmethod是一个专门的“classmethod”对象,而元类中的方法是一个普通函数
  • 因此,类方法使用的机制恰好是“”。虽然普通函数有一个
    \uuuuu get\uuuu
    方法,当从实例检索时,该方法将插入
    self
    参数,当从类检索时,该参数保持为空,但
    classmethod
    对象有一个不同的
    \uuu get\uuuu
    ,该方法将插入类本身(“所有者”)作为两种情况下的第一个参数

    这在大多数情况下都没有实际的区别,但是如果您希望以函数的形式访问该方法,以便为其动态添加decorator,或者为元类
    meta中的方法添加任何其他内容。method
    检索该函数,准备使用,而您必须使用
    cls.my\u classmethod.\uuu func\uu
    从类方法中检索它(然后您必须创建另一个
    classmethod
    对象,并将其重新分配,如果您进行了一些包装)

    基本上,以下是两个示例:

    
    M1类(类型):
    def cls方法1(cls):
    通过
    类CLS1(元类=M1):
    通过
    def运行时包装(cls、方法名称、包装):
    mcls=类型(cls)
    setattr(mcls,方法\名称,包装器(getatttr(mcls,方法\名称)))
    def包装器(类方法):
    def新方法(cls):
    打印(“称为包装器”)
    返回类方法(cls)
    返回新的\u方法
    运行时包装(cls1,“clsmethod1”,包装)
    CLS2类:
    @类方法
    def类别方法2(cls):
    通过
    def运行时_wrap2(cls、方法_名称、包装):
    setattr(cls,方法\名称,类方法(
    包装器(getatttr(cls,方法名称)。\uuuuu func\uuuuuu)
    )
    )
    运行时包装(cls1,“clsmethod1”,包装)
    
    换句话说:除了元类中定义的方法在实例中可见和
    classmethod
    对象不可见这两个重要区别之外,其他区别在运行时也会出现
    class ClassMethod(object):
        "Emulate PyClassMethod_Type() in Objects/funcobject.c"
    
        def __init__(self, f):
            self.f = f
    
        def __get__(self, obj, klass=None):
            if klass is None:
                klass = type(obj)
            def newfunc(*args):
                return self.f(klass, *args)
            return newfunc
    
    class C:
        def f(cls):
            print(f'f called with cls={cls}')
        f = ClassMethod(f)
    
    class Meta(type):
        def foo(self):
            print(f'foo is called self={self}')
            print('{} is instance of {}: {}'.format(self, Meta, isinstance(self, Meta)))
    
    class C(metaclass=Meta):
        pass
    
    C.foo()
    
      isinstance(C, Meta)
    
    class ClassMethodDemo:
        @classmethod
        def foo(cls):
            print(f'cls is ClassMethodDemo: {cls is ClassMethodDemo}')
    
    ClassMethodDemo.foo()