资讯专栏INFORMATION COLUMN

Python -- 元类metaclass详解

tracy / 2022人阅读

摘要:原链接中的元类是什么类也是对象在理解元类之前,需要掌握中类概念。事实上,是中用于创建所有类的元类。类本身是元类的对象在中,除了,一切皆对象,一切都是类或者元类的对象。事实上是自己的元类,

学习契机

项目中使用Elasticsearch(ES)存储海量业务数据,基于ES向外提供的API进一层封装,按需处理原始数据提供更精确、更多样化的结果。在研究这一层的代码时接触到@six.add_metaclass(abc.ABCMeta),故而学习一下Python的元类。不过,虽然@six.add_metaclass(abc.ABCMeta)实现上与元类有关,但实际应用只需要调用其接口,并不需要接触后幕后的元类操作。
翻译这篇答案是为了方便自己记忆理解,其实原文中一些地方我自己不是很明白,所以这个翻译会根据自己理解的程度持续更新。

原链接

stackoverflow-What are metaclasses in Python?

Python中的元类是什么 类也是对象

在理解元类之前,需要掌握Python中类概念。Python的类概念稍有奇特,其借鉴于Smalltalk。
在大部分语言中,类用于描述如何生成一个对象,在Python中也是如此:

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

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

但是,在Python中类的意义更多,类同时也是对象。当你使用关键字class时,Python解释器执行代码时会生成一个对象。以下代码会在内存中创建一个名为“ObjectCreator”的对象:

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

这个对象(类)自身可以创建对象(实例),这是为什么它是类的原因。
不过它仍然是一个对象,你可以:

可以将它赋值给变量

可以复制

可以添加属性 TODO 添加属性只是对象的特性?

可以将其当作函数参数传递

举例:

>>> print(ObjectCreator) # you can print a class because it"s an object

>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter

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

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

但是这还不够动态,因为还是需要自己编写整个类的代码。所以,想一想,这些类既然是对象,就必然是某种东西生成的。当使用class关键字时,Python自动创建了类这个对象,但是像Python中大部分事情一样,Python中也可以手动创建类。
还记type方法吗?一个可以让你知道一个对象是什么类型的方法:

>>> print(type(1))

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

>>> print(type(ObjectCreator))

>>> print(type(ObjectCreator()))

除此之外,type还有一个完全不同的功能:动态创建类。将类的描述作为参数传递给type,会返回一个类。(我知道,同一个函数根据传参的不同而展示出两种完全不同的功能很不合理,不过这是为了Python的向后兼容。)
type如何创建类:

type(类名,
     父类元祖 (可为空),
     包含键值对属性的字典)

举例:

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

上面这个MyShinyClass类可以用以下方法手动创建:

>>> MyShinyClass = type("MyShinyClass", (), {}) # 返回一个类
>>> print(MyShinyClass)

>>> print(MyShinyClass()) # 创建一个类对象
<__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属性继承自类Foo
True

你肯定还想为类添加方法。只需要定义一个名称合理的函数,并将这个函数名作为属性传递给type就行:

>>> 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()
MyObject = MyClass()

你已经看到type可以让你做如下操作:

MyClass = type("MyClass", (), {})

这是因为type方法实际上是一个元类。事实上,type是Python中用于创建所有类的元类。不过现在你一定很奇怪为什么这个类名首字母是小写,而不是Type?我猜这是同str保持一致,str是用来创建string对象的类,int是用来创建integer对象的类,type则是创建类对象的类。
在Python中,一切,对就是一切,都是对象。包括整数、字符串、函数和类。所有东西都是对象,它们都是创建自某个类。
通过__class__属性可以验证这一点:

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

编写一个类时添加上__metaclass__属性:

class Foo(object):
    __metaclass__ = something...
    [...]

如果你像上面这样做,Python就会使用元类创建一个Foo类。
要当心了,这里有些小圈套。你先写下了“class Foo(object)”,但此时内存中还没有创建Foo类对象。Python会在类的声明中寻找属性__metaclass_,如果找到了就会使用其创建Foo类;如果没有,会使用type创建这个类。下面这段文字要多读几遍。
当你编写以下代码时:

class Foo(Bar):
    pass

Python做了这些事情:

在类Foo中有定义__metaclass__属性吗?
如果有,则继续;
如果没有,Python会在模块层寻找__metaclass__属性(这只针对没有继承任何其他类的情况);
如果模块层也没有,则会在Bar(第一个父类)中寻找(这就有可能是内置的type)。
这样找到__metaclass__后,使用它在内存中创建名称为Foo的类对象(这边跟上,一个类对象)

需要注意的是,__metaclass__属性不会被继承,但是父类的元类(Bar.__class__)可以被继承:如果Bar的__metaclass__属性定义了使用type()(不是type.__new())创建Bar类,其子类不会继承这个行为。(Be careful here that the metaclass attribute will not be inherited, the metaclass of the parent (Bar.__class__) will be. If Bar used a metaclass attribute that created Bar with type() (and not type.__new__()), the subclasses will not inherit that behavior.)TODO 这边不太理解
现在有个新问题,你可以赋什么值给__metaclass__?
答案是:可以创建一个类的东西。
那什么可以创建类?type、子类化type或者使用type的东西。

自定义元类

元类的主要目的,是在创建类的时候动态地改变类。通常你会想创建符合当前上下文的类供API使用。举一个简单的例子,当你希望一个模块中所有的类属性都是小写时,有几种方法可以实现,其中有一种方法就是在模块层设置__metaclass__。使用这种方法,这个模块中所有的类都将使用此元类创建,我们只需要使元类将所有类属性置为小写。
幸运的是,__metaclass__可以被任意调用,不一定非要是一个正式的类(我知道,名称包含“class”不一定非要是一个类,搞清楚了...这很有用)。
现在先用一个函数举一个简单的例子:

# 元类会自动获取通常传给`type`的参数
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    """
      返回一个类对象,将其属性置为大写
    """

    # 过滤出所有开头不为"__"的属性,置为大写
    uppercase_attr = {}
    for name, val in future_class_attr.items():
        if not name.startswith("__"):
            uppercase_attr[name.upper()] = val
        else:
            uppercase_attr[name] = val

    # 利用"type"创建类
    return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr  # 这会影响此模块中所有的类

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"

print(hasattr(Foo, "bar"))
# Out: False
print(hasattr(Foo, "BAR"))
# Out: True

f = Foo()
print(f.BAR)
# Out: "bip"

现在,完成同样的功能,但是为元类定义一个真实的类:

# 记住`type`实际上是一个像`str`和`int`的类,可以用于被继承
class UpperAttrMetaclass(type):
    # __new__放在__init__之前调用,此方法创建对象并反回
    # 而__init__则是初始化作为参数传递给此方法的对象
    # 除非你想控制如何创建一个对象,否则很少用到__new__
    # 在这里,被创建的对象是类,而我们想自定义这个类,所以重写了__new__
    # 如果需要的话你也可以在__init__中做一些操作
    # 一些高级用法会包括重写__call__,不过这里还不需要
    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith("__"):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type(future_class_name, future_class_parents, uppercase_attr)

这不是真正的面向对象(OOP),这里直接调用了type,没有重写或者调用父类的__new__。现在像这样处理:

class UpperAttrMetaclass(type):

    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith("__"):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        # 复用type.__new__方法
        # 这是基本的OOP,没什么深奥的
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)

你大概发现了传给type的额外的参数upperattr_metaclass。这没什么奇怪的:__new__的第一个参数总是其定义的类。就像类方法中第一个参数总是self。当然,为了清晰期间,这里我起的名字比较长,但是像self这样的参数通常有一个传统的名字。所以真正的产品代码中,元类是像这样的:

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith("__"):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type.__new__(cls, clsname, bases, uppercase_attr)

使用super可以更清晰,which will ease inheritance (because yes, you can have metaclasses, inheriting from metaclasses, inheriting from type)TODO:

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith("__"):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

以上,关于元类也没有更多了。使用元类的代码比较复杂的原因不在于元类,而在于你通常会依靠自省、操纵继承、__dict__变量等,使用元类做一些晦涩的事情。(it"s because you usually use metaclasses to do twisted stuff relying on introspection, manipulating inheritance, vars such as __dict__, etc.)TODO
元类来用于黑魔法时的确特别有用,因为也会将事情搞得很复杂。但就其本身而言,是简单的:

拦截一个类的创建

修改类

返回修改的类

为什么会用元类代替函数?

既然__metaclass__可以被任意调用,为什么要使用明显更复杂的类呢?有这样一些理由:

意图明显。当你看到UpperAttrMetaclass(type),你知道接下来会发生什么。

可以使用OOP。元类可以继承自元类,重写父类的方法,元类甚至可以使用元类。

如果为一个类指定的元类是类而不是方法,这个类的子类将是元类的一个实例。Children of a class will be instances of its metaclass if you specified a metaclass-class, but not with a metaclass-function.TODO

你可以将代码组织得更好。使用元类时肯定不会仅想像上面举的例子那样简单,通常是用于比较复杂的场景。将多个方法组织在一个类中有益于使代码更容易阅读。

你可以使用__new__,__init__ 和 __call__,这些方法可以处理不用的事情。即使很多时候你可以在__new__中完成所有工作,当然,一些人会更习惯用__init__。

这些东西叫 “metaclass”,小心了!这一定很难搞!

为什么使用元类

好了,现在的问题是:为什么要是用这样晦涩且容易出错的特性?其实,通常你不会用:

元类是99%的用户根本不必操心的深度魔法。如果你在考虑是否需要使用,那就不要用(真正需要的用户很清楚他们的需求,根本不需要解释为什么要使用元类) 
Python领袖 Tim Peters

使用元类的一个主要场景是创建API。Django ORM是一个典型的例子,它允许你像下面这样定义:

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

如果你这样做:

guy = Person(name="bob", age="35")
print(guy.age)

它不会返回一个IntegerField的对象,而是返回一个整数,甚至可以从数据库中直接获取数据。TODO
这是因为,在models.Model中定义了__metaclass__,使用了一些魔法将你定义的简单的Person类转换为一个复杂的数据库挂钩。(turn the Person you just defined with simple statements into a complex hook to a database field.)TODO

Django使用元类对外提供简单的API,简化了一些复杂的东西,API中重建的代码会去完成幕后真正的工作。

结语

首先,类是可以创建实例的对象。类本身是元类的对象:

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

在Python中,除了type,一切皆对象,一切都是类或者元类的对象。事实上type是自己的元类,这在纯Python中这是无法实现的,这里在实现层上做了一些手段。
其次,元类是复杂的。你可能不希望对非常简单的类使用元类,那么还有其他两种手段用来改变类:

monkey patching

类装饰器

如果你需要改变类,99%的情况下使用这两种方法。
但其实98%的情况你根本不需要改变类。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/44599.html

相关文章

  • How does it work - with_metaclass

    摘要:先简单介绍下中的元类。元类就是创建类的类,对于元类来说,类是它的实例,将返回。中的所有类,都是的实例,换句话说,是元类的基类。 我在看源代码的时候,经常蹦出这一句:How does it work!竟然有这种操作?本系列文章,试图剖析代码中发生的魔法。顺便作为自己的阅读笔记,以作提高。 先简单介绍下Python中的元类(metaclass)。元类就是创建类的类,对于元类来说,类是它的实...

    testbird 评论0 收藏0
  • python 类和元类(metaclass)的理解和简单运用

    摘要:什么是元类刚才说了,元类就是创建类的类。类上面的属性,相信愿意了解元类细节的盆友,都肯定见过这个东西,而且为之好奇。使用了这个魔法方法就意味着就会用指定的元类来创建类了。深刻理解中的元类 (一) python中的类 今天看到一篇好文,然后结合自己的情况总结一波。这里讨论的python类,都基于python2.7x以及继承于object的新式类进行讨论。 首先在python中,所有东西都...

    zhangqh 评论0 收藏0
  • [译]什么是元类metaclass?

    摘要:如果还是没有找到,就会使用父类中的元类来创建类。元类通常用于处理比较复杂的情况。这是因为使用了元类,它会将中定义的字段转换成数据库中的字段。中所有数据类型都是对象,它们要么是类的实例要么是元类的实例。 原文地址:what is metaclass in Python?我的简书地址::nummy 类即对象 在理解元类之前,需要先掌握Python中的类,Python中类的概念与SmallT...

    zsirfs 评论0 收藏0
  • Python: 陌生的 metaclass

    摘要:但一般情况下,我们使用类作为元类。那么,元类到底有什么用呢要你何用元类的主要目的是为了控制类的创建行为。当然,有很多种做法,这里展示用元类的做法。当你创建类时,解释器会调用元类来生成它,定义一个继承自的普通类意味着调用来创建它。 元类 Python 中的元类(metaclass)是一个深度魔法,平时我们可能比较少接触到元类,本文将通过一些简单的例子来理解这个魔法。 类也是对象 在 Py...

    miya 评论0 收藏0
  • 由type()函数对类和实例使用结果差异而引出的一个问题

    摘要:但是随后有人提出反对意见并说这个是随后搜索到这篇文章深刻理解中的元类里面介绍了如何使用函数创建一个类,并解释了属性。 有如下代码 #-*-coding:utf-8-*- class a(): pass a1 = a() print(type(a),type(a1)) 两个python版本分别为Python2.7.11Python3.5.1 在python2中得到的结果(, )a...

    zhangwang 评论0 收藏0

发表评论

0条评论

tracy

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<