类方法和元类方法之间有什么区别?


12

在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在元类上使用和使用普通方法之间有什么区别?

Answers:


5

由于类是元类的实例,因此,元类上的“实例方法”将表现为类方法就不足为奇了。

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

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

因此,碰巧类方法使用的机制是“ 描述符协议 ”。普通函数具有一种__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对象看不到,其他区别在运行时似乎模糊且毫无意义-但这是因为不需要使用语言脱离了针对类方法的特殊规则:由于语言设计的缘故,两种声明类方法的方法都是可能的-一种是因为类本身就是对象,另一种是可能描述符协议的使用,它允许人们在实例和类中专门进行属性访问:

classmethod内置在本机代码中定义,但它可能只是在纯Python进行编码,并在完全相同的方式是可行的。5行类的波纹管可用作classmethod装饰器,与内置的@classmethod" at all (though distinguishable through introspection such as calls toisinstance , and evenrepr当然没有运行时差异):


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

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


2

当您像在问题中一样说出短语时,@classmethod和元类可能看起来相似,但目的却大不相同。插入到@classmethod的参数中的类通常用于构造实例(即,替代构造函数)。另一方面,元类通常用于修改类本身(例如,就像Django对它的模型DSL所做的那样)。

这并不是说您不能在类方法内部修改类。但是,问题就变成了,为什么您不首先以想要修改的方式定义类?如果不是,则可能建议重构以使用多个类。

让我们扩展第一个示例。

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

Python文档中借用的内容,以上内容将扩展为以下内容:

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)

请注意如何__get__获取实例或类(或两者),因此可以同时使用C.fC().f。这与您提供的元类示例不同,后者将引发AttributeErrorfor C().f

而且,在元类示例中,f不存在C.__dict__。使用查找属性fC.f,解释器将查看C.__dict__然后在查找失败后将查找type(C).__dict__(为M.__dict__)。这可能重要,如果你想灵活地覆盖fC,但我怀疑这将永远是实用的。


0

在您的示例中,不同之处在于某些其他类将M设置为其元类。

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

class C(metaclass=M):
    pass

class C2(metaclass=M):
    pass

C.f()
C2.f()
class M(type):
     pass

class C(metaclass=M):
     @classmethod
     def f(cls):
        pass

class C2(metaclass=M):
    pass

C.f()
# C2 does not have 'f'

有关元类的更多信息,元类有 哪些(具体)用例?


0

@classmethod和Metaclass都不同。

python中的所有内容都是一个对象。每件事都意味着每一件事。

什么是元类?

如前所述,每一件事都是一个对象。类实际上也是对象,而类是正式称为元类的其他神秘对象的实例。如果未指定,则python中的默认元类为“ type”

默认情况下,所有定义的类都是类型的实例。

类是元类的实例

很少有要领要理解动作

  • 类是元类的实例。
  • 像每个实例化的对象一样,对象(实例)也从类中获取其属性。类将从元类中获取其属性

考虑以下代码

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()

哪里,

  • C类是Meta类的实例
  • “类C”是作为“类元”的实例的类对象
  • 像其他任何对象(实例)一样,“类C”可以访问其在类“类Meta”中定义的属性/方法
  • 因此,解码“ C.foo()”。“ C”是“ Meta”的实例,“ foo”是通过“ Meta”的实例(即“ C”)调用的方法。
  • 与“ classmethod”不同,方法“ foo”的第一个参数是对实例而非类的引用

我们可以验证“ C类”是否是“ Meta类”的实例

  isinstance(C, Meta)

什么是类方法?

据说Python方法是绑定的。由于python施加了限制,该方法只能使用实例调用。有时我们可能想直接通过类调用方法而无需任何实例(非常类似于java中的静态成员)而无需创建任何实例。调用方法需要默认实例。作为一种解决方法,python提供了内置函数classmethod来将给定方法绑定到类而不是实例。

由于类方法是绑定到类上的。它至少需要一个引用类本身而不是实例(自己)的参数

如果使用内置函数/装饰器类方法。第一个参数将引用类而不是实例

class ClassMethodDemo:
    @classmethod
    def foo(cls):
        print(f'cls is ClassMethodDemo: {cls is ClassMethodDemo}')

当我们使用“ classmethod”时,我们调用方法“ foo”而不创建任何实例,如下所示

ClassMethodDemo.foo()

上面的方法调用将返回True。由于第一个参数cls实际上是对“ ClassMethodDemo”的引用

摘要:

  • Classmethod的第一个参数是“对class(传统上称为cls)本身的引用”
  • 元类的方法不是类方法。元类的方法接收第一个参数,即“对实例(传统上称为自身)的引用而不是类”
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.