add by zhj:这是我见过的对metaclass解释最清楚的文章了,例子很好,真是一例胜千言

原文:http://wiki.jikexueyuan.com/project/explore-python/Class/metaclass.html

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

类也是对象

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

  • 把类赋值给一个变量
  • 把类作为函数参数进行传递
  • 把类作为函数的返回值
  • 在运行时动态地创建类

看一个简单的例子:

  1. class Foo(object):
  2. foo = True
  3. class Bar(object):
  4. bar = True
  5. def echo(cls):
  6. print cls
  7. def select(name):
  8. if name == 'foo':
  9. return Foo # 返回值是一个类
  10. if name == 'bar':
  11. return Bar
  12. >>> echo(Foo) # 把类作为参数传递给函数 echo
  13. <class '__main__.Foo'>
  14. >>> cls = select('foo') # 函数 select 的返回值是一个类,把它赋给变量 cls
  15. >>> cls
  16. __main__.Foo

熟悉又陌生的 type

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

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

  1. class Foo(object):
  2. Foo = True
  3. >>> type(10)
  4. <type 'int'>
  5. >>> type('hello')
  6. <type 'str'>
  7. >>> type(Foo())
  8. <class '__main__.Foo'>
  9. >>> type(Foo)
  10. <type 'type'>

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

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

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

最简单的情况

假设有下面的类:

  1. class Foo(object):
  2. pass

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

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

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

  • 第 1 个参数是字符串 'Foo',表示类名
  • 第 2 个参数是元组 (object, ),表示所有的父类
  • 第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法

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

接着,我们看看使用:

  1. >>> print Foo
  2. <class '__main__.Foo'>
  3. >>> print Foo()
  4. <__main__.Foo object at 0x10c34f250>

有属性和方法的情况

假设有下面的类:

  1. class Foo(object):
  2. foo = True
  3. def greet(self):
  4. print 'hello world'
  5. print self.foo

用 type 来创建这个类,如下:

  1. def greet(self):
  2. print 'hello world'
  3. print self.foo
  4. Foo = type('Foo', (object, ), {'foo': True, 'greet': greet})

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

  1. >>> f = Foo()
  2. >>> f.foo
  3. True
  4. >>> f.greet
  5. <bound method Foo.greet of <__main__.Foo object at 0x10c34f890>>
  6. >>> f.greet()
  7. hello world
  8. True

继承的情况

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

  1. class Base(object):
  2. pass

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

  1. class Foo(Base):
  2. foo = True

改用 type 来创建,如下:

  1. Foo = type('Foo', (Base, ), {'foo': True})

什么是元类(metaclass)

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

  1. 类是实例对象的模板,元类是类的模板
  2. +----------+ +----------+ +----------+
  3. | | | | | |
  4. | | instance of | | instance of | |
  5. | instance +------------>+ class +------------>+ metaclass|
  6. | | | | | |
  7. | | | | | |
  8. +----------+ +----------+ +----------+

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

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

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

元类的使用

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

  1. class Foo(object):
  2. name = 'foo'
  3. def bar(self):
  4. print 'bar'

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

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

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

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

  • PrefixMetaClass 从 type 继承,这是因为 PrefixMetaclass 是用来创建类的
  • __new__ 是在 __init__ 之前被调用的特殊方法,它用来创建对象并返回创建后的对象,对它的参数解释如下:
    • cls:当前准备创建的类
    • name:类的名字
    • bases:类的父类集合
    • attrs:类的属性和方法,是一个字典

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

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

  1. class Foo(object):
  2. __metaclass__ = PrefixMetaclass
  3. name = 'foo'
  4. def bar(self):
  5. print 'bar'

在 Python3 中,这样做:

  1. class Foo(metaclass=PrefixMetaclass):
  2. name = 'foo'
  3. def bar(self):
  4. print 'bar'

现在,让我们看看使用:

  1. >>> f = Foo()
  2. >>> f.name # name 属性已经被改变
  3. ---------------------------------------------------------------------------
  4. AttributeError Traceback (most recent call last)
  5. <ipython-input-774-4511c8475833> in <module>()
  6. ----> 1 f.name
  7. AttributeError: 'Foo' object has no attribute 'name'
  8. >>>
  9. >>> f.my_name
  10. 'foo'
  11. >>> f.my_bar()
  12. bar
  13. >>> f.echo('hello')
  14. 'hello'

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

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

  1. class PrefixMetaclass(type):
  2. def __new__(cls, name, bases, attrs):
  3. # 给所有属性和方法前面加上前缀 my_
  4. _attrs = (('my_' + name, value) for name, value in attrs.items())
  5. _attrs = dict((name, value) for name, value in _attrs) # 转化为字典
  6. _attrs['echo'] = lambda self, phrase: phrase # 增加了一个 echo 方法
  7. return type.__new__(cls, name, bases, _attrs)
  8. class Foo(object):
  9. __metaclass__ = PrefixMetaclass # 注意跟 Python3 的写法有所区别
  10. name = 'foo'
  11. def bar(self):
  12. print 'bar'
  13. class Bar(Foo):
  14. prop = 'bar'

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

  1. >>> b = Bar()
  2. >>> b.prop # 发现没这个属性
  3. ---------------------------------------------------------------------------
  4. AttributeError Traceback (most recent call last)
  5. <ipython-input-778-825e0b6563ea> in <module>()
  6. ----> 1 b.prop
  7. AttributeError: 'Bar' object has no attribute 'prop'
  8. >>> b.my_prop
  9. 'bar'
  10. >>> b.my_name
  11. 'foo'
  12. >>> b.my_bar()
  13. bar
  14. >>> b.echo('hello')
  15. '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 来创建它。

参考资料

陌生的 metaclass(转)的更多相关文章

  1. 详解Objective-C的meta-class

    比较简单的一篇英文,重点是讲解meta-class.翻译下,加深理解. 原文标题:What is a meta-class in Objective-C? 原文地址:http://www.cocoaw ...

  2. 详解Objective-C的meta-class 分类: ios相关 ios技术 2015-03-07 15:41 51人阅读 评论(0) 收藏

    比较简单的一篇英文,重点是讲解meta-class.翻译下,加深理解. 原文标题:What is a meta-class in Objective-C? 原文地址:http://www.cocoaw ...

  3. Objective-C 中的 Meta-class 是什么?

    在这篇文章中,我关注的是 Objective-C 中的一个陌生的概念-- meta-class.在 Objective-C 中的每个类都有一个相关联的 meta-class,但是你很少会直接使用 me ...

  4. 深度|作为C端应用的代表,成功的陌生社交应用是什么样子的?

    作 为C端应用的代表,成功的陌生社交应用是什么样子的?活跃用户数?收益回报率?在实际社交产品设计中,我们一直为这些所谓的KPI左右,具体到设计行为 上:摆弄相应的界面元素,优化一下文案.页面流,但却很 ...

  5. Android探索之ContentProvider熟悉而又陌生的组件

    前言: 总结这篇文章之前我们先来回顾一下Android Sqlite数据库,参考文章:http://www.cnblogs.com/whoislcj/p/5506294.html,Android程序内 ...

  6. 【Linux学习】如何了解一个陌生的命令?

    如何了解一个陌生的命令? 有一些命令可以用来了解某个命令本身的情况,比如这个命令的绝对路径. $which ls which 在默认路径中搜索命令,返回该命令的绝对路径. $whereis ls wh ...

  7. metaclass 常用方式

    一个类作为metaclass的时候,我们需要重写它的__new__方法,这个方法的参数包括要创建class object的 metaclass,类名,父类集合,类成员 class MyMetaclas ...

  8. python 中的metaclass和baseclasses

    提前说明: class object  指VM中的class 对象,因为python一切对象,class在VM也是一个对象,需要区分class对象和 class实例对象. class instance ...

  9. 【引】runtime全解析,P2:关于Class 和 MetaClass

    几个基本的概念: id,id的定义是,typedef struct objc_object { Class isa;} *id; id是指向struct objc_object的一个指针.这个意思是, ...

随机推荐

  1. not in 的优化

    //---------------------- 建表1 ---------------------- create table TESTTABLE( id1 VARCHAR2(12), name V ...

  2. Visual自动添加CSS兼容前缀

    安装方法 打开vs code 的 扩展 ---> 搜索 Autoprefixer,并安装. 使用方法 打开css文件,按F1,选择 Autoprefix CSS 这条命令 没执行命令之前: 执行 ...

  3. 【转】Web前端性能优化——如何提高页面加载速度

    前言:  在同样的网络环境下,两个同样能满足你的需求的网站,一个“Duang”的一下就加载出来了,一个纠结了半天才出来,你会选择哪个?研究表明:用户最满意的打开网页时间是2-5秒,如果等待超过10秒, ...

  4. 使用UIScrollView 结合 UIImageView 实现图片循环滚动

    场景: 在开发工作中,有时我们需要实现一组图片循环滚动的情况.当我们使用 UIScrollView 结合 UIImageView 来实现时,一般 UIImageView 会尽量考虑重用,下面例子是以( ...

  5. 修改git用户密码

    第一步:登录git服务器: 第二步:切换到git用户 su git 第三步:登录GitLab的Rails控制台(GitLab使用RoR语言开发), gitlab-rails console produ ...

  6. [Localization] SSD - Single Shot MultiBoxDetector

    Prerequisite: VGG Ref: [Object Tracking] Localization and Detection SSD Paper: http://lib.csdn.net/a ...

  7. 5、二、App Components(应用程序组件):0、概述

    二.App Components(应用程序组件) 0.概述   App Components Android's application framework lets you create rich ...

  8. akka cluster sharding

    cluster sharding 的目的在于提供一个框架,方便实现 DDD,虽然我至今也没搞明白 DDD 到底适用于是什么场合,但是 cluster sharding 却是我目前在做的一个 proje ...

  9. Elasticsearch数据迁移工具elasticdump工具

    1. 工具安装 wget https://nodejs.org/dist/v8.11.2/node-v8.11.2-linux-x64.tar.xz tar xf node-v8.11.2-linux ...

  10. thinkphp5---使用自定义助手函数

    在进行项目开发的时候,系统自带的助手函数往往满足不了自己的需求,就需要通过自定义助手函数来实现某个功能,具体做法: 新建:myhelper.php 写入: <?php if (!function ...