由于类是元类的实例,因此,元类上的“实例方法”将表现为类方法就不足为奇了。
但是,是的,存在一些差异-其中一些不只是语义上的:
- 最重要的区别是,在元类的方法不是“可见的”从一个类的实例。发生这种情况是因为Python中的属性查找(以简化的方式-描述符可能优先)在实例中搜索属性-如果实例中不存在该属性,Python然后在该实例的类中查找,然后继续搜索类的超类,但不在类的类上。Python stdlib在
abc.ABCMeta.register
方法中。该功能可以很好地使用,因为与类本身相关的方法可以自由地重用为实例属性,而不会发生任何冲突(但是方法仍然会发生冲突)。
- 另一个区别(尽管很明显)是,在元类中声明的方法可以在多个类中使用,而不必以其他方式使用-如果您具有不同的类层次结构,这些层次结构与它们处理的内容完全无关,但是希望所有类都具有某些通用功能,您必须提出一个mixin类,该类必须作为两个层次结构的基础包括在内(例如,将所有类都包含在应用程序注册表中)。(注:mixin有时可能比元类更好)
- 类方法是专门的“类方法”对象,而元类中的方法是普通函数。
因此,碰巧类方法使用的机制是“ 描述符协议 ”。普通函数具有一种__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 to
isinstance , 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
元类上的特殊属性将像特殊类属性一样工作,完全一样,没有令人惊讶的行为。