陌生的 metaclass(转)
add by zhj:这是我见过的对metaclass解释最清楚的文章了,例子很好,真是一例胜千言
原文:http://wiki.jikexueyuan.com/project/explore-python/Class/metaclass.html
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
<class '__main__.Foo'>
>>> cls = select('foo') # 函数 select 的返回值是一个类,把它赋给变量 cls
>>> cls
__main__.Foo
熟悉又陌生的 type
在日常使用中,我们经常使用 object
来派生一个类,事实上,在这种情况下,Python 解释器会调用 type
来创建类。
这里,出现了 type
,没错,是你知道的 type
,我们经常使用它来判断一个对象的类型,比如:
class Foo(object):
Foo = True
>>> type(10)
<type 'int'>
>>> type('hello')
<type 'str'>
>>> type(Foo())
<class '__main__.Foo'>
>>> type(Foo)
<type 'type'>
事实上,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
<class '__main__.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
<bound method Foo.greet of <__main__.Foo object at 0x10c34f890>>
>>> 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)
<ipython-input-774-4511c8475833> in <module>()
----> 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)
<ipython-input-778-825e0b6563ea> in <module>()
----> 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 来创建它。
参考资料
- oop - What is a metaclass in Python? - Stack Overflow
- 深刻理解Python中的元类(metaclass) - 伯乐在线
- 使用元类 - 廖雪峰的官方网站
- Python基础:元类
- 在Python中使用class decorator和metaclass
陌生的 metaclass(转)的更多相关文章
- 详解Objective-C的meta-class
比较简单的一篇英文,重点是讲解meta-class.翻译下,加深理解. 原文标题:What is a meta-class in Objective-C? 原文地址:http://www.cocoaw ...
- 详解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 ...
- Objective-C 中的 Meta-class 是什么?
在这篇文章中,我关注的是 Objective-C 中的一个陌生的概念-- meta-class.在 Objective-C 中的每个类都有一个相关联的 meta-class,但是你很少会直接使用 me ...
- 深度|作为C端应用的代表,成功的陌生社交应用是什么样子的?
作 为C端应用的代表,成功的陌生社交应用是什么样子的?活跃用户数?收益回报率?在实际社交产品设计中,我们一直为这些所谓的KPI左右,具体到设计行为 上:摆弄相应的界面元素,优化一下文案.页面流,但却很 ...
- Android探索之ContentProvider熟悉而又陌生的组件
前言: 总结这篇文章之前我们先来回顾一下Android Sqlite数据库,参考文章:http://www.cnblogs.com/whoislcj/p/5506294.html,Android程序内 ...
- 【Linux学习】如何了解一个陌生的命令?
如何了解一个陌生的命令? 有一些命令可以用来了解某个命令本身的情况,比如这个命令的绝对路径. $which ls which 在默认路径中搜索命令,返回该命令的绝对路径. $whereis ls wh ...
- metaclass 常用方式
一个类作为metaclass的时候,我们需要重写它的__new__方法,这个方法的参数包括要创建class object的 metaclass,类名,父类集合,类成员 class MyMetaclas ...
- python 中的metaclass和baseclasses
提前说明: class object 指VM中的class 对象,因为python一切对象,class在VM也是一个对象,需要区分class对象和 class实例对象. class instance ...
- 【引】runtime全解析,P2:关于Class 和 MetaClass
几个基本的概念: id,id的定义是,typedef struct objc_object { Class isa;} *id; id是指向struct objc_object的一个指针.这个意思是, ...
随机推荐
- hdoj:2034
#include <iostream> #include <vector> #include<algorithm> //包含sort函数 using namespa ...
- vs2013 error LNK2005 已经在***.obj中定义
错误解决办法: 方法一: 中文 项目--属性 ---连接器---输入 附加依赖项 空格Nafxcwd.lib Libcmtd.lib ...
- Java知多少(23)类的基本运行顺序
我们以下面的类来说明一个基本的 Java 类的运行顺序: public class Demo{ private String name; private int age; public Demo(){ ...
- Mac/Linux如何查找应用所安装路径
Linux.Mac中查看某 个软件的安装路径(地址)有时显得非常重要.比如某个文件的快速启动项被删除,或者你要建立快速启动项,或者想删除. 添加安装文件等等,很多地方都要用到查案文件安装路径的命令. ...
- Zookeeper系列四:Zookeeper实现分布式锁、Zookeeper实现配置中心
一.Zookeeper实现分布式锁 分布式锁主要用于在分布式环境中保证数据的一致性. 包括跨进程.跨机器.跨网络导致共享资源不一致的问题. 1. 分布式锁的实现思路 说明: 这种实现会有一个缺点,即当 ...
- Java如何检查端口是否被使用?
在Java编程中,如何扫描打开的端口(是否被使用)? 以下示例显示如何通过创建 Socket 对象来检查主机上打开或正在使用的端口(相当于一个简单的端口扫描器). package com.yiibai ...
- 转:Python语言编程学习资料(电子书+视频教程)下载汇总
开发工具: Python语言集成开发环境 Wingware WingIDE Professional v3.2.12 Python语言集成开发环境 Wingware WingIDE Professio ...
- [Bayes] Understanding Bayes: Updating priors via the likelihood
From: https://alexanderetz.com/2015/07/25/understanding-bayes-updating-priors-via-the-likelihood/ Re ...
- 大杂烩 -- 简析TCP的三次握手与四次分手
基础大杂烩 -- 目录 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - ...
- Window应急响应(四):挖矿病毒
0x00 前言 随着虚拟货币的疯狂炒作,挖矿病毒已经成为不法分子利用最为频繁的攻击方式之一.病毒传播者可以利用个人电脑或服务器进行挖矿,具体现象为电脑CPU占用率高,C盘可使用空间骤降,电脑温度升 ...