`classmethod` 和元类方法有什么区别?
What are the differences between a `classmethod` and a metaclass method?
在 Python 中,我可以使用 @classmethod
装饰器创建一个 class 方法:
>>> class C:
... @classmethod
... def f(cls):
... print(f'f called with cls={cls}')
...
>>> C.f()
f called with cls=<class '__main__.C'>
或者,我可以在元上使用普通(实例)方法class:
>>> 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()
的输出所示,这两种方法提供相似的功能。
在 metaclass 上使用 @classmethod
和使用普通方法有什么区别?
在您的示例中,不同之处在于其他一些 类 会将 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'
这里有关于 meta 的更多信息类
What are some (concrete) use-cases for metaclasses?
当您像在问题中那样措辞时,@classmethod
和元classes 可能看起来很相似,但它们的目的却截然不同。在 @classmethod
的参数中注入的 class 通常用于构造实例(即替代构造函数)。另一方面,metaclasses 通常用于修改 class 本身(例如 Django 对其模型 DSL 所做的)。
这并不是说您不能在 class 方法中修改 class。但是问题就变成了为什么你没有以你想要修改它的方式首先定义它?如果不是,它可能会建议重构以使用多个 classes.
让我们稍微扩展一下第一个例子。
class C:
@classmethod
def f(cls):
print(f'f called with cls={cls}')
借用 Python docs,上面的内容将扩展为如下内容:
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__
如何获取实例或 class(或两者),因此您可以同时执行 C.f
和 C().f
。这与您给出的 metaclass 示例不同,后者会为 C().f
.
抛出 AttributeError
此外,在元class示例中,f
在C.__dict__
中不存在。当用 C.f
查找属性 f
时,解释器查看 C.__dict__
,然后在找不到后,查看 type(C).__dict__
(即 M.__dict__
)。如果您希望在 C
中灵活地覆盖 f
,这可能很重要,尽管我怀疑这是否有实际用途。
由于 classes 是 metaclass 的实例,metaclass 上的 "instance method" 表现得像 class 并不意外]方法。
但是,是的,存在差异 - 其中一些不仅仅是语义上的:
- 最重要的区别是元class 中的方法不是来自class 实例 的"visible"。发生这种情况是因为 Python 中的属性查找(以简化的方式 - 描述符可能优先)在实例中搜索属性 - 如果它不存在于实例中,Python 然后查找该实例的class,然后在 class 的超 class 上继续搜索, 但不在 class 上搜索 class。 Python stdlib 在
abc.ABCMeta.register
方法中使用了这个特性。
该功能可以永久使用,因为与 class 相关的方法本身可以自由地重新用作实例属性而不会发生任何冲突(但方法仍然会发生冲突)。
- 另一个区别虽然很明显,但在元class 中声明的方法可以在多个 class 中使用,没有其他相关性 - 如果您有不同的 class 层次结构,在 他们处理的内容 中根本不相关,但是想要所有 class 的一些通用功能,你必须想出一个 mixin class ,它必须作为基础包含在两个层次结构中(比如在应用程序注册表中包含所有 classes)。 (注意,mixin 有时可能比 metaclass 更好)
- class方法是一个特殊的"classmethod"对象,而metaclass中的方法是一个普通函数。
所以,class方法使用的机制恰好是“descriptor protocol”。虽然普通函数具有一个 __get__
方法,当从实例中检索它们时将插入 self
参数,并在从 class 检索时将该参数留空,但 classmethod
对象有一个不同的 __get__
,它将在两种情况下插入 class 本身("owner")作为第一个参数。
这在大多数情况下没有实际区别,但是如果您想将方法作为函数访问,以便为元中的方法添加动态添加装饰器或任何其他方法class meta.method
检索函数,准备使用,而您必须使用 cls.my_classmethod.__func__
从 class 方法检索它(然后您必须创建另一个 classmethod
对象并将其分配回来,如果你做一些包装)。
基本上,这些是 2 个示例:
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)
换句话说: 除了元class 中定义的方法对实例可见和 classmethod
对象可见的重要区别之外不,其他差异,在运行时会显得模糊和无意义 - 但这是因为语言不需要为 class 方法制定特殊规则:两种声明 class 的方式方法是可能的,作为语言设计的结果 - 一个,因为 class 本身是一个对象,另一个,作为许多可能性中的一种,使用允许一个人专门化的描述符协议实例和 class:
中的属性访问
classmethod
内置函数是在本机代码中定义的,但它可以只用纯 python 编码,并且会以完全相同的方式工作。下面的第 5 行 class 可以用作 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)
而且,除了方法之外,有趣的是要记住,metaclass 上的 @property
之类的专门属性将作为专门的 class 属性使用,就像, 没有任何令人惊讶的行为。
@class方法和Metaclass都是不同的。
Everything in python is an object. Every thing means every thing.
什么是元class?
如前所述,万物皆对象。 Classes 实际上也是对象 classes 是正式称为 meta-classes 的其他神秘对象的实例。 python 中的默认 metaclass 如果未指定
则为 "type"
默认情况下,所有 class 定义的都是类型的实例。
Classes are instances of Meta-Classes
几个要点是理解提到的行为
- 因为 classes 是元 classes 的实例。
- 像每个实例化的对象一样,对象(实例)从 class 获取它们的属性。 Class 将从 Meta-Class
获取它的属性
考虑以下代码
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()
其中,
- class C 是 class Meta
的实例
- "class C" 是 class 对象,它是 "class Meta"
的实例
- 像任何其他对象(实例)一样,"class C" 可以访问它的 attributes/methods 定义在它的 class "class Meta"
- 所以,解码 "C.foo()"。 "C" 是 "Meta" 的实例,"foo" 是通过 "Meta" 实例调用的方法,即 "C".
- 方法的第一个参数 "foo" 是对实例的引用,而不是 class 不同于 "classmethod"
我们可以验证 "class C" 是否是 "Class Meta
的实例
isinstance(C, Meta)
什么是class方法?
Python 方法被称为绑定。由于 python 强加了方法只能用实例调用的限制。
有时我们可能希望通过 class 直接调用方法而不需要任何实例(很像 java 中的静态成员)而不必创建任何 instance.By 默认实例来调用方法。作为解决方法,python 提供内置函数 class 方法将给定方法绑定到 class 而不是实例。
因为 class 方法绑定到 class。它至少需要一个引用 class 本身而不是实例 (self)
的参数
if built-in function/decorator classmethod is used. First argument
will be reference to class instead of instance
class ClassMethodDemo:
@classmethod
def foo(cls):
print(f'cls is ClassMethodDemo: {cls is ClassMethodDemo}')
因为我们使用了 "classmethod" 我们调用方法 "foo" 而没有创建任何实例如下
ClassMethodDemo.foo()
以上方法调用将 return 为真。由于第一个参数 cls 确实是对 "ClassMethodDemo"
的引用
总结:
- Class方法接收的第一个参数是 "a reference to class(traditionally referred as cls) itself"
- meta-classes 的方法不是 class 方法。 Meta-classes 的方法接收第一个参数,即 "a reference to instance(traditionally referred as self) not class"
在 Python 中,我可以使用 @classmethod
装饰器创建一个 class 方法:
>>> class C:
... @classmethod
... def f(cls):
... print(f'f called with cls={cls}')
...
>>> C.f()
f called with cls=<class '__main__.C'>
或者,我可以在元上使用普通(实例)方法class:
>>> 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()
的输出所示,这两种方法提供相似的功能。
在 metaclass 上使用 @classmethod
和使用普通方法有什么区别?
在您的示例中,不同之处在于其他一些 类 会将 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'
这里有关于 meta 的更多信息类 What are some (concrete) use-cases for metaclasses?
当您像在问题中那样措辞时,@classmethod
和元classes 可能看起来很相似,但它们的目的却截然不同。在 @classmethod
的参数中注入的 class 通常用于构造实例(即替代构造函数)。另一方面,metaclasses 通常用于修改 class 本身(例如 Django 对其模型 DSL 所做的)。
这并不是说您不能在 class 方法中修改 class。但是问题就变成了为什么你没有以你想要修改它的方式首先定义它?如果不是,它可能会建议重构以使用多个 classes.
让我们稍微扩展一下第一个例子。
class C:
@classmethod
def f(cls):
print(f'f called with cls={cls}')
借用 Python docs,上面的内容将扩展为如下内容:
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__
如何获取实例或 class(或两者),因此您可以同时执行 C.f
和 C().f
。这与您给出的 metaclass 示例不同,后者会为 C().f
.
AttributeError
此外,在元class示例中,f
在C.__dict__
中不存在。当用 C.f
查找属性 f
时,解释器查看 C.__dict__
,然后在找不到后,查看 type(C).__dict__
(即 M.__dict__
)。如果您希望在 C
中灵活地覆盖 f
,这可能很重要,尽管我怀疑这是否有实际用途。
由于 classes 是 metaclass 的实例,metaclass 上的 "instance method" 表现得像 class 并不意外]方法。
但是,是的,存在差异 - 其中一些不仅仅是语义上的:
- 最重要的区别是元class 中的方法不是来自class 实例 的"visible"。发生这种情况是因为 Python 中的属性查找(以简化的方式 - 描述符可能优先)在实例中搜索属性 - 如果它不存在于实例中,Python 然后查找该实例的class,然后在 class 的超 class 上继续搜索, 但不在 class 上搜索 class。 Python stdlib 在
abc.ABCMeta.register
方法中使用了这个特性。 该功能可以永久使用,因为与 class 相关的方法本身可以自由地重新用作实例属性而不会发生任何冲突(但方法仍然会发生冲突)。 - 另一个区别虽然很明显,但在元class 中声明的方法可以在多个 class 中使用,没有其他相关性 - 如果您有不同的 class 层次结构,在 他们处理的内容 中根本不相关,但是想要所有 class 的一些通用功能,你必须想出一个 mixin class ,它必须作为基础包含在两个层次结构中(比如在应用程序注册表中包含所有 classes)。 (注意,mixin 有时可能比 metaclass 更好)
- class方法是一个特殊的"classmethod"对象,而metaclass中的方法是一个普通函数。
所以,class方法使用的机制恰好是“descriptor protocol”。虽然普通函数具有一个 __get__
方法,当从实例中检索它们时将插入 self
参数,并在从 class 检索时将该参数留空,但 classmethod
对象有一个不同的 __get__
,它将在两种情况下插入 class 本身("owner")作为第一个参数。
这在大多数情况下没有实际区别,但是如果您想将方法作为函数访问,以便为元中的方法添加动态添加装饰器或任何其他方法class meta.method
检索函数,准备使用,而您必须使用 cls.my_classmethod.__func__
从 class 方法检索它(然后您必须创建另一个 classmethod
对象并将其分配回来,如果你做一些包装)。
基本上,这些是 2 个示例:
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)
换句话说: 除了元class 中定义的方法对实例可见和 classmethod
对象可见的重要区别之外不,其他差异,在运行时会显得模糊和无意义 - 但这是因为语言不需要为 class 方法制定特殊规则:两种声明 class 的方式方法是可能的,作为语言设计的结果 - 一个,因为 class 本身是一个对象,另一个,作为许多可能性中的一种,使用允许一个人专门化的描述符协议实例和 class:
classmethod
内置函数是在本机代码中定义的,但它可以只用纯 python 编码,并且会以完全相同的方式工作。下面的第 5 行 class 可以用作 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)
而且,除了方法之外,有趣的是要记住,metaclass 上的 @property
之类的专门属性将作为专门的 class 属性使用,就像, 没有任何令人惊讶的行为。
@class方法和Metaclass都是不同的。
Everything in python is an object. Every thing means every thing.
什么是元class?
如前所述,万物皆对象。 Classes 实际上也是对象 classes 是正式称为 meta-classes 的其他神秘对象的实例。 python 中的默认 metaclass 如果未指定
则为 "type"默认情况下,所有 class 定义的都是类型的实例。
Classes are instances of Meta-Classes
几个要点是理解提到的行为
- 因为 classes 是元 classes 的实例。
- 像每个实例化的对象一样,对象(实例)从 class 获取它们的属性。 Class 将从 Meta-Class 获取它的属性
考虑以下代码
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()
其中,
- class C 是 class Meta 的实例
- "class C" 是 class 对象,它是 "class Meta" 的实例
- 像任何其他对象(实例)一样,"class C" 可以访问它的 attributes/methods 定义在它的 class "class Meta"
- 所以,解码 "C.foo()"。 "C" 是 "Meta" 的实例,"foo" 是通过 "Meta" 实例调用的方法,即 "C".
- 方法的第一个参数 "foo" 是对实例的引用,而不是 class 不同于 "classmethod"
我们可以验证 "class C" 是否是 "Class Meta
的实例 isinstance(C, Meta)
什么是class方法?
Python 方法被称为绑定。由于 python 强加了方法只能用实例调用的限制。 有时我们可能希望通过 class 直接调用方法而不需要任何实例(很像 java 中的静态成员)而不必创建任何 instance.By 默认实例来调用方法。作为解决方法,python 提供内置函数 class 方法将给定方法绑定到 class 而不是实例。
因为 class 方法绑定到 class。它至少需要一个引用 class 本身而不是实例 (self)
的参数if built-in function/decorator classmethod is used. First argument will be reference to class instead of instance
class ClassMethodDemo:
@classmethod
def foo(cls):
print(f'cls is ClassMethodDemo: {cls is ClassMethodDemo}')
因为我们使用了 "classmethod" 我们调用方法 "foo" 而没有创建任何实例如下
ClassMethodDemo.foo()
以上方法调用将 return 为真。由于第一个参数 cls 确实是对 "ClassMethodDemo"
的引用总结:
- Class方法接收的第一个参数是 "a reference to class(traditionally referred as cls) itself"
- meta-classes 的方法不是 class 方法。 Meta-classes 的方法接收第一个参数,即 "a reference to instance(traditionally referred as self) not class"