Python “classmethod”和元类方法之间有什么区别?
在Python中,我可以使用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
@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类似并不意外
然而,确实存在差异——其中有些差异不仅仅是语义上的:
abc.ABCMeta.register
方法中利用了这个特性。
该特性可以永久使用,因为与类本身相关的方法可以自由地作为实例属性重新使用,而不会产生任何冲突(但方法仍然会有冲突)\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()