什么是 Python 中的元类?

45 浏览
0 Comments

什么是 Python 中的元类?

什么是 元类?它们有什么用途?

admin 更改状态以发布 2023年5月22日
0
0 Comments

元类是类的类。一个类定义了类的实例(即对象)的行为,而元类定义了类的行为。一个类是元类的实例。

虽然在Python中你可以使用任意可调用对象作为元类(就像 Jerub 所示),但更好的方法是将其作为一个实际的类。在Python中,type 是通常使用的元类。 type 本身是一个类,也是自己的类型。你不会仅使用Python就能够重新创建像 type 这样的东西,但是Python有点欺骗。要在Python中创建自己的元类,您只需要将其作为type的子类即可。

元类最常用作类工厂。当您通过调用类创建对象时,Python通过调用元类创建一个新的类(当执行 'class' 语句时)。结合普通的__init____new__方法,元类因此允许您在创建类时执行“额外的事情”,比如将新类注册到某个注册表中或将类替换为完全不同的东西。

当执行 class 语句时,Python 首先将 class 语句体作为普通代码块执行。产生的命名空间(一个字典)保存着将要创建的类的属性。元类通过查看将要创建的类的基类(元类是继承而来的)、类的 metaclass 属性(如果有)或全局变量 metaclass 来确定。然后,元类用类的名称、基类和属性来调用,从而实例化该类。

然而,元类实际上定义了类的类型,而不仅仅是一个工厂。因此,你可以通过元类做更多的事情。例如,你可以在元类上定义普通方法。这些元类方法类似于类方法,因为它们可以在没有实例的情况下在类上调用,但它们也不像类方法,因为它们不能在类的实例上调用。 type.__subclasses__()type 元类的一个方法示例。你也可以定义普通的“魔法”方法,例如 __add____iter____getattr__,以实现或更改类的行为方式。

这是一个聚合示例的代码段:

def make_hook(f):
    """Decorator to turn 'foo' method into '__foo__'"""
    f.is_hook = 1
    return f
class MyType(type):
    def __new__(mcls, name, bases, attrs):
        if name.startswith('None'):
            return None
        # Go over attributes and see if they should be renamed.
        newattrs = {}
        for attrname, attrvalue in attrs.iteritems():
            if getattr(attrvalue, 'is_hook', 0):
                newattrs['__%s__' % attrname] = attrvalue
            else:
                newattrs[attrname] = attrvalue
        return super(MyType, mcls).__new__(mcls, name, bases, newattrs)
    def __init__(self, name, bases, attrs):
        super(MyType, self).__init__(name, bases, attrs)
        # classregistry.register(self, self.interfaces)
        print "Would register class %s now." % self
    def __add__(self, other):
        class AutoClass(self, other):
            pass
        return AutoClass
        # Alternatively, to autogenerate the classname as well as the class:
        # return type(self.__name__ + other.__name__, (self, other), {})
    def unregister(self):
        # classregistry.unregister(self)
        print "Would unregister class %s now." % self
class MyObject:
    __metaclass__ = MyType
class NoneSample(MyObject):
    pass
# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)
class Example(MyObject):
    def __init__(self, value):
        self.value = value
    @make_hook
    def add(self, other):
        return self.__class__(self.value + other.value)
# Will unregister the class
Example.unregister()
inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()
print inst + inst
class Sibling(MyObject):
    pass
ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__

0
0 Comments

类作为对象

在理解元类之前,了解 Python 类更深入的知识将会有所帮助。Python 对于类有一个非常特殊的想法,这个想法来自于 Smalltalk 语言。

在大多数语言中,类只是描述如何生成对象的一些代码片段。在 Python 中也有这种情况:

>>> class ObjectCreator(object):
...       pass
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

但是在 Python 中,类不仅仅是这些。类本身也是对象。

没错,就是对象。

当 Python 脚本运行时,每一行代码都会从上到下执行。当 Python 解释器遇到 class 关键字时,Python 会根据接下来的类的“描述”创建一个 对象。因此,以下指令

>>> class ObjectCreator(object):
...       pass

...创建了一个名称为 ObjectCreator 的对象!

这个对象(即类)本身也能够创建对象(称为实例)。

但是它仍然是一个对象。因此,像所有对象一样:

  • 你可以将其赋值给一个变量1

    JustAnotherVariable = ObjectCreator
    

  • 你可以将属性附加到它上面

    ObjectCreator.class_attribute = 'foo'
    

  • 你可以将其作为函数参数传递

    print(ObjectCreator)
    

1 注意,仅仅将其赋值给另一个变量并不会改变类的 __name__,也就是说,

>>> print(JustAnotherVariable)

>>> print(JustAnotherVariable())
<__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

>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

但是这并不是很动态,因为你仍然需要自己编写整个类。

由于类也是对象,因此它们必须由某些东西生成。

当你使用 class 关键字时,Python 会自动创建这个对象。但像 Python 中的大多数东西一样,它也给你提供了手动创建这个对象的方法。

还记得函数 type 吗?那个能让你知道一个对象类型的老函数:

>>> print(type(1))

>>> print(type("1"))

>>> print(type(ObjectCreator))

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

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

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

>>> 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?"

嗯,我猜这是为了与创建字符串对象的str类和创建整数对象的int类保持一致。 type只是创建类对象的类。

你可以通过检查__class__属性来查看所有东西,我是指所有东西,在Python中都是对象。 这包括整数、字符串、函数和类。 它们都是对象,而且它们都是从类创建的:

>>> age = 35
>>> age.__class__

>>> name = 'bob'
>>> name.__class__

>>> def foo(): pass
>>> foo.__class__

>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__

现在,任何__class____class__是什么呢?

>>> age.__class__.__class__

>>> name.__class__.__class__

>>> foo.__class__.__class__

>>> b.__class__.__class__

因此,元类只是创建类对象的东西。

你可以称它为“类工厂”,如果你愿意的话。

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__属性?

如果是,则使用__metaclass__的内容在内存中创建一个类对象(请跟着我理解) ,并将其命名为Foo

如果Python找不到__metaclass__,则会在模块级别上查找__metaclass__,并尝试做相同的事情(但只针对那些没有继承任何东西的类,基本上是旧式类)。

然后,如果它找不到任何__metaclass__,它将使用Bar的(第一个父类)自己的元类(可能是默认的type)来创建类对象。

要注意的是,__metaclass__属性不会被继承,父类(Bar.__class__)的元类会被继承。如果Bar使用了一个创建Bartype()__metaclass__属性(而不是type.__new__()),那么子类将不会继承这个行为。

现在的大问题是,你可以在__metaclass__里放什么?

答案是一些可以创建类的东西。

那么什么可以创建类呢?type或任何子类或使用它的东西。

Python 3中的元类

在Python 3中,设置元类的语法已经改变:

class Foo(object, metaclass=something):
    ...

也就是说,不再使用__metaclass__属性,而是使用基类列表中的关键字参数。

然而,元类的行为基本上保持不变。

Python 3中添加到元类的一件事是,你还可以将属性作为关键字参数传递给元类,像这样:

class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
    ...

请阅读下面的部分,了解Python如何处理此问题。

自定义元类

元类的主要目的是在创建类时自动更改类。

通常在 API 中,您会使用元类来创建与当前上下文匹配的类。

想象一个愚蠢的例子,您决定模块中的所有类都应该使用大写字母编写其属性。 有几种方法可以实现这一点,但一种方法是在模块级别设置__metaclass__

这样,该模块的所有类都将使用此元类创建,我们只需告诉元类将所有属性转换为大写字母即可。

幸运的是,__metaclass__实际上可以是任何可调用对象,它不需要是正式的类(我知道,一个带有“class”名称的东西不需要是类,有点难以理解...但这很有用)。

因此,我们将从一个简单的例子开始,使用一个函数。

# 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作为第一个参数接收实例,或者对于类方法有定义类作为第一个参数。

但这不是正确的面向对象编程方式。我们直接调用了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进一步简化代码,这将有助于继承(因为是的,您可以有继承自元类的元类,继承自type):

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),你知道接下来会发生什么
  • 你可以使用 OOP。元类可以继承元类,覆盖父类方法。元类甚至可以使用元类。
  • 类的子类如果指定了元类类,则将是其元类的实例,但使用元类函数则不会。
  • 你可以更好地组织你的代码。你永远不会为像上面的示例那样微不足道的事情使用元类。通常是用于复杂的事情。能够制作几个方法并将它们分组在一个类中,这非常有用,可以使代码更易于阅读。
  • 你可以钩住 __new____init____call__。这将允许你做不同的事情,即使通常你可以在 __new__ 中完成所有这些,但有些人更喜欢使用 __init__
  • 这些被称为元类,该死!这一定有什么含义!

为什么要使用元类?

现在是一个大问题。为什么要使用一些晦涩难懂的功能呢?

通常情况下,您不需要使用元类:

元类是更深层次的魔法,99% 的用户都不需要担心它。
如果您想知道自己是否需要它们,那么您不需要(那些真正需要它们的人知道他们需要它们,而且不需要关于为什么需要的解释)。

Python大师Tim Peters

元类的主要用途是创建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并使用元类来重新创建代码,从而使复杂的事情看起来简单。

最后的话

首先,您知道类是可以创建实例的对象。

实际上,类本身也是元类的实例。

>>> class Foo(object): pass
>>> id(Foo)
142630324

在Python中,所有东西都是对象,它们都是类的实例或元类的实例。

除了type

type实际上是它自己的元类。这不是您可以在纯Python中复制的内容,而是在实现层面上稍微作弊。

其次,元类很复杂。您可能不想将其用于非常简单的类修改。您可以使用两种不同的技术来更改类:

99%的时间,您需要类修改,最好使用这些方法。

但是实际上有98%的情况下,你根本不需要对类进行修改。

0