资讯专栏INFORMATION COLUMN

Python: 陌生的 metaclass

miya / 1369人阅读

摘要:但一般情况下,我们使用类作为元类。那么,元类到底有什么用呢要你何用元类的主要目的是为了控制类的创建行为。当然,有很多种做法,这里展示用元类的做法。当你创建类时,解释器会调用元类来生成它,定义一个继承自的普通类意味着调用来创建它。

元类

Python 中的元类(metaclass)是一个深度魔法,平时我们可能比较少接触到元类,本文将通过一些简单的例子来理解这个魔法。

类也是对象

在 Python 中,一切皆对象。字符串,列表,字典,函数是对象,类也是一个对象,因此你可以:

把类赋值给一个变量

把类作为函数参数进行传递

把类作为函数的返回值

在运行时动态地创建类

看一个简单的例子:

class Foo(object):
    foo = True

class Bar(object):
    bar = True

def echo(cls):
    print cls

def select(name):
    if name == "foo":
        return Foo        # 返回值是一个类
    if name == "bar":
        return Bar

>>> echo(Foo)             # 把类作为参数传递给函数 echo

>>> cls = select("foo")   # 函数 select 的返回值是一个类,把它赋给变量 cls
>>> cls
__main__.Foo
熟悉又陌生的 type

在日常使用中,我们经常使用 object 来派生一个类,事实上,在这种情况下,Python 解释器会调用 type 来创建类。

这里,出现了 type,没错,是你知道的 type,我们经常使用它来判断一个对象的类型,比如:

class Foo(object):
    Foo = True

>>> type(10)

>>> type("hello")

>>> type(Foo())

>>> type(Foo)

事实上,type 除了可以返回对象的类型,它还可以被用来动态地创建类(对象)。下面,我们看几个例子,来消化一下这句话。

使用 type 来创建类(对象)的方式如下:

type(类名, 父类的元组(针对继承的情况,可以为空),包含属性和方法的字典(名称和值))

最简单的情况

假设有下面的类:

class Foo(object):
    pass

现在,我们不使用 class 关键字来定义,而使用 type,如下:

Foo = type("Foo", (object, ), {})    # 使用 type 创建了一个类对象

上面两种方式是等价的。我们看到,type 接收三个参数:

第 1 个参数是字符串 "Foo",表示类名

第 2 个参数是元组 (object, ),表示所有的父类

第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法

在上面,我们使用 type() 创建了一个名为 Foo 的类,然后把它赋给了变量 Foo,我们当然可以把它赋给其他变量,但是,此刻没必要给自己找麻烦。

接着,我们看看使用:

>>> print Foo

>>> print Foo()
<__main__.Foo object at 0x10c34f250>
有属性和方法的情况

假设有下面的类:

class Foo(object):
    foo = True
    def greet(self):
        print "hello world"
        print self.foo

type 来创建这个类,如下:

def greet(self):
    print "hello world"
    print self.foo

Foo = type("Foo", (object, ), {"foo": True, "greet": greet})

上面两种方式的效果是一样的,看下使用:

>>> f = Foo()
>>> f.foo
True
>>> f.greet
>
>>> f.greet()
hello world
True
继承的情况

再来看看继承的情况,假设有如下的父类:

class Base(object):
    pass

我们用 Base 派生一个 Foo 类,如下:

class Foo(Base):
   foo = True

改用 type 来创建,如下:

Foo = type("Foo", (Base, ), {"foo": True})
什么是元类(metaclass)

元类(metaclass)是用来创建类(对象)的可调用对象。这里的可调用对象可以是函数或者类等。但一般情况下,我们使用类作为元类。对于实例对象、类和元类,我们可以用下面的图来描述:

类是实例对象的模板,元类是类的模板

+----------+             +----------+             +----------+
|          |             |          |             |          |
|          | instance of |          | instance of |          |
| instance +------------>+  class   +------------>+ metaclass|
|          |             |          |             |          |
|          |             |          |             |          |
+----------+             +----------+             +----------+

我们在前面使用了 type 来创建类(对象),事实上,type 就是一个元类

那么,元类到底有什么用呢?要你何用...

元类的主要目的是为了控制类的创建行为。我们还是先来看看一些例子,以消化这句话。

元类的使用

先从一个简单的例子开始,假设有下面的类:

class Foo(object):
    name = "foo"
    def bar(self):
        print "bar"

现在我们想给这个类的方法和属性名称前面加上 my_ 前缀,即 name 变成 my_name,bar 变成 my_bar,另外,我们还想加一个 echo 方法。当然,有很多种做法,这里展示用元类的做法。

1.首先,定义一个元类,按照默认习惯,类名以 Metaclass 结尾,代码如下:

class PrefixMetaclass(type):
    def __new__(cls, name, bases, attrs):
        # 给所有属性和方法前面加上前缀 my_
        _attrs = (("my_" + name, value) for name, value in attrs.items())  
        
        _attrs = dict((name, value) for name, value in _attrs)  # 转化为字典
        _attrs["echo"] = lambda self, phrase: phrase  # 增加了一个 echo 方法
        
        return type.__new__(cls, name, bases, _attrs)  # 返回创建后的类

上面的代码有几个需要注意的点:

PrefixMetaClass 从 type 继承,这是因为 PrefixMetaclass 是用来创建类的

__new__ 是在 __init__ 之前被调用的特殊方法,它用来创建对象并返回创建后的对象,对它的参数解释如下:

cls:当前准备创建的类

name:类的名字

bases:类的父类集合

attrs:类的属性和方法,是一个字典

2.接着,我们需要指示 Foo 使用 PrefixMetaclass 来定制类。

在 Python2 中,我们只需在 Foo 中加一个 __metaclass__ 的属性,如下:

class Foo(object):
    __metaclass__ = PrefixMetaclass
    name = "foo"
    def bar(self):
        print "bar"

在 Python3 中,这样做:

class Foo(metaclass=PrefixMetaclass):
    name = "foo"
    def bar(self):
        print "bar"

现在,让我们看看使用:

>>> f = Foo()
>>> f.name    # name 属性已经被改变
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
 in ()
----> 1 f.name

AttributeError: "Foo" object has no attribute "name"
>>>
>>> f.my_name
"foo"
>>> f.my_bar()
bar
>>> f.echo("hello")
"hello"

可以看到,Foo 原来的属性 name 已经变成了 my_name,而方法 bar 也变成了 my_bar,这就是元类的魔法。

再来看一个继承的例子,下面是完整的代码:

class PrefixMetaclass(type):
    def __new__(cls, name, bases, attrs):
        # 给所有属性和方法前面加上前缀 my_
        _attrs = (("my_" + name, value) for name, value in attrs.items())  
        
        _attrs = dict((name, value) for name, value in _attrs)  # 转化为字典
        _attrs["echo"] = lambda self, phrase: phrase  # 增加了一个 echo 方法
        
        return type.__new__(cls, name, bases, _attrs)

class Foo(object):
    __metaclass__ = PrefixMetaclass   # 注意跟 Python3 的写法有所区别
    name = "foo"
    def bar(self):
        print "bar"

class Bar(Foo):
    prop = "bar"

其中,PrefixMetaclass 和 Foo 跟前面的定义是一样的,只是新增了 Bar,它继承自 Foo。先让我们看看使用:

>>> b = Bar()
>>> b.prop     # 发现没这个属性
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
 in ()
----> 1 b.prop

AttributeError: "Bar" object has no attribute "prop"
>>> b.my_prop
"bar"
>>> b.my_name
"foo"
>>> b.my_bar()
bar
>>> b.echo("hello")
"hello"

我们发现,Bar 没有 prop 这个属性,但是有 my_prop 这个属性,这是为什么呢?

原来,当我们定义 class Bar(Foo) 时,Python 会首先在当前类,即 Bar 中寻找 __metaclass__,如果没有找到,就会在父类 Foo 中寻找 __metaclass__,如果找不到,就继续在 Foo 的父类寻找,如此继续下去,如果在任何父类都找不到 __metaclass__,就会到模块层次中寻找,如果还是找不到,就会用 type 来创建这个类。

这里,我们在 Foo 找到了 __metaclass__,Python 会使用 PrefixMetaclass 来创建 Bar,也就是说,元类会隐式地继承到子类,虽然没有显示地在子类使用 __metaclass__,这也解释了为什么 Bar 的 prop 属性被动态修改成了 my_prop。

写到这里,不知道你理解元类了没?希望理解了,如果没理解,就多看几遍吧~

小结

在 Python 中,类也是一个对象。

类创建实例,元类创建类。

当你创建类时,解释器会调用元类来生成它,定义一个继承自 object 的普通类意味着调用 type 来创建它。

本文由 funhacks 发表于个人博客,采用 Creative Commons BY-NC-ND 4.0(自由转载-保持署名-非商用-禁止演绎)协议发布。
非商业转载请注明作者及出处。商业转载请联系作者本人。
本文标题为: Python: 陌生的 metaclass
本文链接为: https://funhacks.net/2016/11/...

参考资料

oop - What is a metaclass in Python? - Stack Overflow

深刻理解Python中的元类(metaclass) - 伯乐在线

使用元类 - 廖雪峰的官方网站

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

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

相关文章

  • [译]理解pythonmetaclass

    摘要:但我并不是一个翻译者并不会严格遵守每行每句的翻译有时候我会将表述换个顺序省略一些我认为无关紧要的话,以便读者更好理解。类也是对象在理解之前,我们先要掌握中的类是什么。这个对象类自身拥有产生对象实例的能力。不过这样做是为了保持向后兼容性。 前言 这篇博客是我在stackoverflow上看了一个提问回复后写的,例子基本用的都是e-satis本人的例子,语言组织也基本按照翻译来。 但我并不...

    liuchengxu 评论0 收藏0
  • How does it work - with_metaclass

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

    testbird 评论0 收藏0
  • Python -- 元类metaclass详解

    摘要:原链接中的元类是什么类也是对象在理解元类之前,需要掌握中类概念。事实上,是中用于创建所有类的元类。类本身是元类的对象在中,除了,一切皆对象,一切都是类或者元类的对象。事实上是自己的元类, 学习契机 项目中使用Elasticsearch(ES)存储海量业务数据,基于ES向外提供的API进一层封装,按需处理原始数据提供更精确、更多样化的结果。在研究这一层的代码时接触到@six.add_me...

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

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

    zhangqh 评论0 收藏0
  • Python: metaclass小记

    摘要:最前面那个,解释器实际的流程是解析这段代码,得知它需要创建一个类对象,这个类的名字叫做它的父类列表用表示是,它的属性用一个来表示就是。解决方法很简单关键就是前面被特别标记了的应当返回这个的父类的方法返回的对象。 (原发于我的blog:Python: metaclass小记 ) 友情提示:本文不一定适合阅读,如果执意要读,请备好晕车药。 题记 Metaclasses are deepe...

    mushang 评论0 收藏0

发表评论

0条评论

miya

|高级讲师

TA的文章

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