什么是元类?它们是用来做什么的?
原文由 e-satis 发布,翻译遵循 CC BY-SA 4.0 许可协议
在了解元类之前,您需要掌握 Python 中的类。 Python 从 Smalltalk 语言中借用了关于什么是类的非常独特的概念。
在大多数语言中,类只是描述如何生成对象的代码片段。在 Python 中也是如此:
>>> class ObjectCreator(object):
... pass
...
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>
但是类比 Python 中的类更多。类也是对象。
是的,对象。
只要您使用关键字 class
,Python 就会执行它并创建一个 对象。指令
>>> class ObjectCreator(object):
... pass
...
在内存中创建一个名为 ObjectCreator
的对象。
这个对象(类)本身能够创建对象(实例),这就是为什么它是一个类。
但它仍然是一个对象,因此:
例如:
>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
... print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>
由于类是对象,您可以像创建任何对象一样即时创建它们。
首先,您可以使用 class
在函数中创建一个类:
>>> def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # return the class, not an instance
... else:
... class Bar(object):
... pass
... return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>
但它不是那么动态,因为你仍然必须自己编写整个类。
由于类是对象,因此它们必须由某些东西生成。
当您使用 class
关键字时,Python 会自动创建此对象。但与 Python 中的大多数事情一样,它为您提供了一种手动完成的方法。
还记得函数 type
吗?让你知道对象是什么类型的好旧函数:
>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>
嗯, type
也有一个完全不同的能力:它可以动态创建类。 type
可以把一个类的描述作为参数,返回一个类。
(我知道,根据您传递给它的参数,同一个函数可以有两种完全不同的用途,这很愚蠢。由于 Python 的向后兼容性,这是一个问题)
type
以这种方式工作:
type(name, bases, attrs)
在哪里:
name
: 班级名称bases
: 父类元组(为了继承,可以为空)attrs
:包含属性名称和值的字典例如:
>>> class MyShinyClass(object):
... pass
可以这样手动创建:
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>
您会注意到我们使用 MyShinyClass
作为类的名称和保存类引用的变量。它们可以不同,但没有理由使事情复杂化。
type
接受一个字典来定义类的属性。所以:
>>> class Foo(object):
... bar = True
可以翻译成:
>>> Foo = type('Foo', (), {'bar':True})
并用作普通班级:
>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True
当然,你可以继承它,所以:
>>> class FooChild(Foo):
... pass
将会:
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True
最终,您会希望将方法添加到您的类中。只需定义一个具有适当签名的函数并将其分配为属性即可。
>>> def echo_bar(self):
... print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True
并且您可以在动态创建类之后添加更多方法,就像向正常创建的类对象添加方法一样。
>>> def echo_bar_more(self):
... print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True
你知道我们要去哪里了:在 Python 中,类是对象,你可以动态地动态创建一个类。
这就是当您使用关键字 class
时 Python 所做的,它通过使用元类来实现。
元类是创建类的“东西”。
您定义类是为了创建对象,对吗?
但是我们了解到 Python 类是对象。
嗯,元类是创建这些对象的东西。它们是类的类,您可以这样描绘它们:
MyClass = MetaClass()
my_object = MyClass()
您已经看到 type
让您可以执行以下操作:
MyClass = type('MyClass', (), {})
这是因为函数 type
实际上是一个元类。 type
是 Python 用来在幕后创建所有类的元类。
现在您想知道“为什么它是小写的,而不是 Type
?”
好吧,我想这是与创建字符串对象的类 int
和创建整数对象的类 str
的一致性问题。 type
只是创建类对象的类。
您可以通过检查 __class__
属性看到这一点。
一切,我的意思是一切,在 Python 中都是一个对象。这包括整数、字符串、函数和类。它们都是对象。所有这些都是从一个类创建的:
>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>
现在,任何 __class__
的 __class__
是什么?
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>
因此,元类只是创建类对象的东西。
如果愿意,您可以将其称为“类工厂”。
type
是 Python 使用的内置元类,当然,您可以创建自己的元类。
__metaclass__
属性在 Python 2 中,您可以在编写类时添加一个 __metaclass__
属性(有关 Python 3 语法,请参阅下一节):
class Foo(object):
__metaclass__ = something...
[...]
如果这样做,Python 将使用元类创建类 Foo
。
小心,这很棘手。
您首先编写 class Foo(object)
,但类对象 Foo
尚未在内存中创建。
Python 将在类定义中查找 __metaclass__
。如果找到它,它将使用它来创建对象类 Foo
。如果没有,它将使用 type
创建类。
多读几遍。
当你这样做时:
class Foo(Bar):
pass
Python 执行以下操作:
--- 中是否有 --- Foo
__metaclass__
属性?
如果是,则使用 --- 中的内容在内存中创建一个名为 Foo
__metaclass__
类对象(我说的是类对象,请留在此处)。
如果 Python 找不到 __metaclass__
,它将在 MODULE 级别寻找 __metaclass__
,并尝试做同样的事情(但仅适用于不继承任何东西的类,基本上旧式课程)。
然后,如果它根本找不到任何 __metaclass__
,它将使用 Bar
的(第一个父级)自己的元类(可能是默认的 type
) 创建类对象。
这里要小心 __metaclass__
属性不会被继承,父类( Bar.__class__
)的元类将被继承。 If Bar
used a __metaclass__
attribute that created Bar
with type()
(and not type.__new__()
), the subclasses will不继承该行为。
现在最大的问题是,您可以在 __metaclass__
中输入什么?
答案是可以创建类的东西。
什么可以创建一个类? type
,或任何子类化或使用它的东西。
设置元类的语法在 Python 3 中已更改:
class Foo(object, metaclass=something):
...
即不再使用 __metaclass__
属性,取而代之的是基类列表中的关键字参数。
然而,元类的行为在 很大程度上保持不变。
在 Python 3 中添加到元类的一件事是您还可以将属性作为关键字参数传递到元类中,如下所示:
class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
...
阅读下面的部分,了解 Python 如何处理这个问题。
元类的主要目的是在创建时自动更改类。
您通常为 API 执行此操作,您希望在其中创建与当前上下文匹配的类。
想象一个愚蠢的例子,你决定模块中的所有类的属性都应该用大写字母书写。有几种方法可以做到这一点,但一种方法是在模块级别设置 __metaclass__
。
这样,这个模块的所有类都将使用这个元类创建,我们只需要告诉元类将所有属性转为大写即可。
幸运的是, __metaclass__
实际上可以是任何可调用的,它不需要是一个正式的类(我知道,名称中带有“类”的东西不需要是一个类,去计算…… . 但它很有帮助)。
所以我们将从一个简单的例子开始,使用一个函数。
# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attrs):
"""
Return a class object, with the list of its attribute turned
into uppercase.
"""
# pick up any attribute that doesn't start with '__' and uppercase it
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in future_class_attrs.items()
}
# let `type` do the class creation
return type(future_class_name, future_class_parents, uppercase_attrs)
__metaclass__ = upper_attr # this will affect all classes in the module
class Foo(): # global __metaclass__ won't work with "object" though
# but we can define __metaclass__ here instead to affect only this class
# and this will work with "object" children
bar = 'bip'
让我们检查:
>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'bip'
现在,让我们做同样的事情,但是对元类使用真实的类:
# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
# __new__ is the method called before __init__
# it's the method that creates the object and returns it
# while __init__ just initializes the object passed as parameter
# you rarely use __new__, except when you want to control how the object
# is created.
# here the created object is the class, and we want to customize it
# so we override __new__
# you can do some stuff in __init__ too if you wish
# some advanced use involves overriding __call__ as well, but we won't
# see this
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in future_class_attrs.items()
}
return type(future_class_name, future_class_parents, uppercase_attrs)
让我们重写上面的内容,但现在我们知道它们的含义后,使用更短和更实际的变量名称:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in attrs.items()
}
return type(clsname, bases, uppercase_attrs)
您可能已经注意到额外的参数 cls
。它没有什么特别之处: __new__
总是接收它定义的类作为第一个参数。就像你有 self
对于将实例作为第一个参数接收的普通方法,或者类方法的定义类。
但这不是正确的 OOP。我们直接调用 type
并且我们没有覆盖或调用父级的 __new__
。让我们这样做:
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in attrs.items()
}
return type.__new__(cls, clsname, bases, uppercase_attrs)
我们可以使用 super
使它更清晰,这将简化继承(因为是的,你可以有元类,从元类继承,从类型继承):
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, attrs):
uppercase_attrs = {
attr if attr.startswith("__") else attr.upper(): v
for attr, v in attrs.items()
}
# Python 2 requires passing arguments to super:
return super(UpperAttrMetaclass, cls).__new__(
cls, clsname, bases, uppercase_attrs)
# Python 3 can use no-arg super() which infers them:
return super().__new__(cls, clsname, bases, uppercase_attrs)
哦,在 Python 3 中,如果您使用关键字参数执行此调用,如下所示:
class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
...
它在元类中转换为使用它:
class MyMetaclass(type):
def __new__(cls, clsname, bases, dct, kwargs1=default):
...
而已。关于元类,真的没有什么比这更多的了。
使用元类的代码复杂性背后的原因不是因为元类,而是因为您通常使用元类来依赖自省、操纵继承、诸如 __dict__
等变量来做扭曲的事情。
确实,元类对于施展黑魔法特别有用,因此可以做一些复杂的事情。但它们本身很简单:
由于 __metaclass__
可以接受任何可调用对象,为什么要使用一个类,因为它显然更复杂?
这样做有几个原因:
UpperAttrMetaclass(type)
时,您知道接下来会发生什么__new__
、 __init__
和 __call__
。这将允许您做不同的事情,即使通常您可以在 __new__
中完成所有操作,有些人只是更喜欢使用 __init__
。现在是大问题。你为什么要使用一些晦涩难懂的容易出错的功能?
嗯,通常你不会:
元类是更深层次的魔法,99% 的用户永远不必担心它。如果你想知道你是否需要它们,你不需要(真正需要它们的人肯定知道他们需要它们,不需要解释为什么)。
Python 大师蒂姆·彼得斯
元类的主要用例是创建 API。一个典型的例子是 Django ORM。它允许您定义如下内容:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
但是如果你这样做:
person = Person(name='bob', age='35')
print(person.age)
它不会返回 IntegerField
对象。它会返回一个 int
,甚至可以直接从数据库中获取。
这是可能的,因为 models.Model
定义了 __metaclass__
并且它使用了一些魔法将你刚刚用简单语句定义的 Person
转换为复杂的数据库字段挂钩。
Django 通过公开一个简单的 API 和使用元类,从这个 API 重新创建代码以在幕后完成真正的工作,使复杂的事情看起来很简单。
首先,您知道类是可以创建实例的对象。
嗯,事实上,类本身就是实例。元类。
>>> class Foo(object): pass
>>> id(Foo)
142630324
Python 中万物皆对象,它们要么是类的实例,要么是元类的实例。
除了 type
。
type
实际上是它自己的元类。这不是您可以在纯 Python 中重现的东西,而是通过在实现级别作弊来完成的。
其次,元类很复杂。您可能不想将它们用于非常简单的类更改。您可以使用两种不同的技术来更改类:
99% 的时间你需要改变类,你最好使用这些。
但是 98% 的时候,你根本不需要改变类。
原文由 e-satis 发布,翻译遵循 CC BY-SA 4.0 许可协议
4 回答4.4k 阅读✓ 已解决
4 回答3.8k 阅读✓ 已解决
1 回答3k 阅读✓ 已解决
3 回答2.1k 阅读✓ 已解决
1 回答4.5k 阅读✓ 已解决
1 回答3.8k 阅读✓ 已解决
1 回答2.8k 阅读✓ 已解决
元类是类的类。类定义类的实例(即对象)的行为方式,而元类定义类的行为方式。类是元类的一个实例。
在 Python 中,您可以对元类使用任意可调用对象(如 Jerub 所示),更好的方法是使其本身成为实际类。
type
是 Python 中常用的元类。type
本身就是一个类,它是它自己的类型。您将无法纯粹在 Python 中重新创建type
类的东西,但 Python 会作弊。要在 Python 中创建自己的元类,您真的只想type
。元类最常用作类工厂。当您通过调用类创建对象时,Python 通过调用元类创建一个新类(当它执行“类”语句时)。结合正常的
__init__
和__new__
方法,元类因此允许您在创建类时做“额外的事情”,比如用一些注册表注册新类或用一些东西替换类否则完全。当
class
语句被执行时,Python首先将class
语句的主体作为正常的代码块执行。生成的命名空间(一个字典)保存了类的属性。元类是通过查看类的基类来确定的(元类被继承),在__metaclass__
属性(如果有的话)或__metaclass__
全局变量。然后使用类的名称、基类和属性调用元类以实例化它。然而,元类实际上定义了一个类的 _类型_,而不仅仅是它的工厂,所以你可以用它们做更多的事情。例如,您可以在元类上定义普通方法。这些元类方法类似于类方法,因为它们可以在没有实例的类上调用,但它们也不像类方法,因为它们不能在类的实例上调用。
type.__subclasses__()
是type
元类上的方法示例。您还可以定义普通的“魔术”方法,如__add__
、__iter__
和__getattr__
来实现或更改类的行为方式。下面是一个点点滴滴的汇总示例: