add by zhj: 这是大*上一位小白提出的问题,好吧,我承认我也是小白,元类这块我也是好多次想搞明白,
但终究因为太难懂而败下阵来。看了这篇文章明白了许多,再加下啄木鸟社区的 Python 类型和对象 。卧槽,
这简直就是珠联璧合,日月神剑啊,尼玛。终于理解了元类。元类就是创建类对象的类,建议用__metaclass__用于指定元类,它的好
处也就是可以要创建类之前和之后修改类的属性,创建后修改属性也可以在__init__中做,不过完全可以用__new__代替,在元类的
__new__()方法中会直接或间接调用type(classname, parentclasses , attrs),我们可以控制这三个参数,type()就是实例化type类,
即在堆上创建一个类对象,任何类对象的创建都会调用这个接口,我们可以控制让__new__()返回什么样的类对象,比如我们可以在
__new__多次调用type(),让他生成多个类对象,然后将这些类按一定的策略组合起来返回。说白了,__metaclass__是用于控制类的
生成过程。我们大多数情况下无需指定__metaclass__,这种情况下,解释器会按一定的策略找到元类。元类的参数就是我们用class关键字
定义类时的类名,类的所有父类,类中定义的{属性名:属性对象}。
翻译时部分地方有修改。
原文:http://*.com/questions/100003/what-is-a-metaclass-in-python?answertab=votes#tab-top
类是对象
在理解元类之前,你需要先掌握Python中的类。在Python中,对类的定义比较特殊,这点借鉴了Smalltalk语言。在大多
数语言中,类只是用于描述如何创建对象的代码片,在Python中,在一定程度上也是这样:
>>> class ObjectCreator(object):
... pass
... >>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>
不过,Python中的类不止如此。类也是对象,是的,我没说错,在《Python源码剖析》中提到除了Python的内置类型外
其它对象都是在堆上创建的,而内置类型应该是在内存的全局数据区。在Python中,一切都是对象,对象对用户来说只提供
了变量,变量就是那些标识符,变量以引用的方式操作对象,变量相当于对象暴露给用户的接口。Python中的引用跟C++
的引用差不多相同,不过也有差异。
当你使用关键字class时,Python解释器执行时,会创建一个对象。如下,Python会在堆中创建一个对象,并在符号表
中增加一个标识符ObjectCreator,指向那个对象的地址,这就是Python中所说的引用,我们一般称ObjectCreator为变量。
>>> class ObjectCreator(object):
... pass
...
我们称这个对象为类对象,因为该对象可以实例化,创建实例对象,因此我们称它为类。
因为它是一个对象,所以:
- 可以将它赋给一个变量
- 可以copy它
- 可以给它增加属性
- 可以作为函数参数
比如
>>> print(ObjectCreator) # 可以print it
<class '__main__.ObjectCreator'>
>>> def echo(o):
... print(o)
...
>>> echo(ObjectCreator) # 类对象作为函数参数
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # 增加类的属性
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # 赋值给另一个变量
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>
通过type类创建类
原文中说通过type类创建对象是动态,其实我们平时使用class关键字定义类也是动态的,当Python解释器遇到class关键字的,
它就会执行,并创建类对象。其实,类对象也是实例化的结果,即类对象是由另一个类实例化得到的,我们称创建类的类为元类,
反之也成立,如果类X实例化后得到是类对象,那X就是元类。在Python中,只有type类及其子类才可以当元类。多说
一句,这里会有鸡生蛋、蛋生鸡的问题。在Python中,一切都是对象,一切对象都是类实例化的结果。对象由类实例化得到,而类也是
对象,它也是由类(元类)实例化得到,继续向上,元类也是对象,也要由另一个元类实例化得到,这样下去就没有尽头了。在Python中,
这个追溯终止在type类。元类是type类或其子类,而type类的元类就是它自己,哈哈,type类自己创建自己,牛逼吧,当然type类的
这个特性是Python设计者设计并实现的。至于Python解释器在定义类时是怎么找到该类的元类的,后面我们会讲。
还记得type()方法吗?我们常用它看一个对象X所属的类,即创建该对象X的类,当对象X是类对象时,看到的就是元类。如下,
>>> print(type(1))
<type 'int'>
>>> print(type(""))
<type 'str'>
>>> print(type(ObjectCreator)) #查看创建ObjectCreator类的类
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>
type类还有另外一个功能,它可以创建类,它以类的一些信息做为type()的参数,并返回一个类。我知道,type根据输入参数的不同而有不同的功能,
这种做法是愚蠢的。当type()只有一个参数时,它的功能就是返回创建该参数的类;当多于一个参数时,type()才是type类的实例化,实例化得到
一个类并返回该类。type创建类时,参数格式如下,classname是类名,字符串类型,parentclasses是类所有父类,元组类型,attrs是类的所有{属性:值},
字典类型。如果用户是用class关键字定义的类,那解释器会自动转为下面的格式执行。
type(classname, parentclasses , attrs)
比如,
>>> class MyShinyClass(object):
... pass
当解释器执行时,会转为下面的语句,当然,你也可以直接这么写。
MyShinyClass = type('MyShinyClass', (object,), {})
下面我们来定义一个类,并在类中定义属性,如
>>> class Foo(object):
... bar = True
它会被翻译成下面的形式,
>>> Foo = type('Foo', (), {'bar':True})
用用看吧
>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True
我们可以用另一个类继承它,so:
>>> class FooChild(Foo):
... pass
would be:
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True
OK,你会想给你的类增加方法。定义一个函数,并将它分配给类的属性
>>> 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
说到这里,你应该已经明白了类的创建过程。那Python创建类都是这么简单吗?都是直接用type元类实例化得到?
不是的,你可以指定元类,我们会在“__metaclass__属性”一节会讲。
什么是元类
元类就是创建类的类,当通过type.__new__(cls, classname, bases, attrs)创建类时,cls就是该类的元类,
它是type类或其子类。在上面,我们提到可以使用type()查看类的元类,你也可以使用__class__,他们是完全等价的。
一切类的创建最终都会调用type.__new__(cls, classname, bases, attrs),它会在堆中创建一个类对象,并返回
>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>
那__class__.__class__是什么呢?__class__返回的是一个类对象,再一次__class__返回创建类对象的类,
即类的元类,如下,这个例子中类的元类都是type类,我们平时用的绝大部分类的元类都是type,不过也有例外,
有些类的元类是type的子类。当然,如果你对某个类一直调用__class__,那在有限次之后,它返回的就是type类了。
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>
__metaclass__属性
你可以在类中添加__metaclass__属性,用它指定创建该类的元类,前面我们说过,元类必须是type类或type子类。
注:其实__metaclass__只要是一个可调用对象就行,该可调用对象的参数格式为callable(classname, parentclasses , attrs),
可以看到,这跟上一节用type类创建类对象时,参数格式完全相同。Python要求该对象执行时必须要调用执行type()或type子类(),
当Python解释器执行时,会调用这个可调用对象,参数也是Python解释器添加上去的。一般的,__metaclass__用元类,在本
文中,作者分别用了函数和元类。这里还有一点,子类如果指定了__metaclass__,该__metaclass__必须与父类的元类相同或
是父类元类的子类,不然,呵呵,Python解释器会选其中一个做元类,但选哪个,貌似没有什么规律。
如果我们创建类时,要指定__metaclass__属性,那最好创建的这个类的父类的元类是type类(有点绕),这样就不容易出错,
不然的话,请慎重使用元类。
class Foo(father_class):
__metaclass__ = something...
[...]
我们假定在继承关系上,子类的元类是父类的元类或父类的元类的子类。在这个正确的前提下,我们说一下Python解释器是怎么确定
一个类的元类的(只讨论新式类的创建,旧式类不讨论,对于新式类,Python解释器的第三步是直接使用type类,作者讨论了定义类时,
没有父类的情况,这种类我们不会用到,不讨论)。add by zhj:但后面我发现,下面这个规律并不成立,在"到底为什么要使用元类"
一节我猜想了更可能的规律,并初步验证过了。
1、在类中查找__metaclass__属性,如果找到就用,如果没有,进入2
2、在继承树中查找__metaclass__属性,如果还是没有,进入3
3、使用type类
举个例子吧,比如你想将一个类的属性全部转为大写(以__开头的属性除外),其实一种办法就是使用__metaclass__属性。
前面我们提到过,__metaclass__可以是任意可调用对象,只要调用super()或super子类()就可以。
OK,我们先使用一个函数做__metaclass__,在实际上我们一般不会用函数,而是用类。
# 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_attr):
"""
Return a class object, with the list of its attribute turned
into uppercase.
"""
print "call upper_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 # let `type` do the class creation
return type(future_class_name, future_class_parents, uppercase_attr) class Foo(object):
__metaclass__ = upper_attr
bar = 'bip' print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True f = Foo()
print(f.BAR)
# Out: 'bip'
执行输出如下:
>>>
call upper_attr
False
True
bip
>>>
然后我们将函数换成类,__new__方法是静态方法,当创建类时,Python解释器会实例化元类
UpperAttrMetaclass(classname, parentclasses , attrs),进一步它会执行
UpperAttrMetaclass.__new__(UpperAttrMetaclass, classname, parentclasses , attrs)方法。我们在下面看到,__new__
会调用type类,其实指定__metaclass__的好处也就是可以要创建类之前修改类的属性,即在调用type(classname, parentclasses , attrs)
之前修改这三个参数,前两个参数貌似没啥好修改的,主要是修改第三个参数,第三个参数是一个字典,我们可以修改键,
即修改属性名称,也可以修改键值,即属性对象,属性对象有value/property/method。
Django的ORM就是这样干的,我们在Model中定义的字段类型是各种field类实例,但当创建该model类时,会将这些field类实例转为Python
内置的类型,如CharField()实例转为unicode或str类型,IntegerField()实例转为int型。
# 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_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(),我们没有使用super().__new__,下面我们修改一下
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, attrs): uppercase_attrs = {}
for name, val in attrs.items():
if not name.startswith('__'):
uppercase_attrs[name.upper()] = val
else:
uppercase_attrs[name] = val return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attrs)
元类可以做到:
- 拦截类的生成
- 修改类
- 返回修改后的类
__metaclass__为什么要用类而不是函数
__metaclass__可以接受任意可调用对象,为什么要使用类而不是函数呢?有下面几个原因
- 意图清晰。当你读到
UpperAttrMetaclass(type)
, 你知道接下来要做什么 - 你可以使用OOP
- 你可以重定义
__new__
,__init__
和__call__,不过其实你完成都在
__new__方法中完成。有些人更喜欢用
__init__
到底为什么要使用元类
那么问题来了,用元类谁最强?哈哈,开个玩笑。为什么你要使用容易出错的元类呢?
well,通常情况下你不需要使用
元类是一个高级特性,99%的用户不需要关心。如果你还在想是否需要它,那你其实不需要(真正需要使用元类的人是非常确定自己的确需要的).
Python大师Tim Peters说过:元类主要的使用场景是创建API,一个典型的例子是Django ORM。
在Django ORM中,可以如下定义model
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
但是当你下面这样做时
guy = Person(name='bob', age='')
print(guy.age)
它并不会返回一个IntegerField
实例,而是返回一个int实例。之所以会这样,是因为models.Model
定义了元类
(不过它并没有直接使用__metaclass__属性,而是使用的另一种方法定义的元类),并且它使用一些魔法将你
用几条简单语句定义的Person类转为数据库的字段。Django通过暴露给用户简单的API,并通过使用元类,使复杂
的事情对用户来说变得简单。我看了一下源码,如下。
class Model(six.with_metaclass(ModelBase)):
_deferred = False def __init__(self, *args, **kwargs):
signals.pre_init.send(sender=self.__class__, args=args, kwargs=kwargs) # Set up the storage for instance state
self._state = ModelState()
……
……
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
return meta("NewBase", bases, {})
class ModelBase(type):
"""
Metaclass for all models.
"""
def __new__(cls, name, bases, attrs):
super_new = super(ModelBase, cls).__new__
……
……
six.with_metaclass(ModelBase)等价于ModelBase("NewBase", (), {}),第二个参数为空时,Python解释器会认为该类继承于object类,
所以这句话等价于ModelBase("NewBase", (object,), {}),我在测试中发现创建的NewBase类的元类是ModelBase类,但Python
并没有给NewBase创建__metaclass__字段,这让人疑惑,看来上面说的一个类创建时查找元类的规律并不成立。那Python解释器到底是怎
么确定NewBase的元类就是ModelBase呢?经我初步验证,应该是靠调用type.__new__(cls, classname, bases, attrs)方法时,第一个参数,
因为一切类对象最终都是通过这个方法创建的,在创建时,它的第一个参数就是被创建的类的元类,第一个参数一般是type类或其子类,类创建时会记
录下它的元类。你可能会问,如果一个类是由多个类对象组合而成的呢?没问题,如果最终的类的id与其中一个类相同,那说明并没有创建新的类,
类的id不变,类的元类就不变,如果它的id与每个类都不同,那就是创建了一个新的类,创建时还是会调用type.__new__方法,第一个参数就是
它的元类。
我非常不喜欢ModelBase("NewBase", (object,), {})这种方式来定义类,因为这样定义你很难知道NewBase的元类是谁,如果ModelBase
定义了__new__方法,且在调用type.__new__()时第一个参数是ModelBase,那NewBase的元类就是ModelBase,如果ModelBase没有重定
义__new__,那对象创建时调用的type.__new__方法,第一个参数是type,即NewBase的元类就是type类。我操,好麻烦啊,我还是推荐传统
的定义类的方式,如下,这样才能一目了然的看到NewBase的元类是ModelBase,不过有一个要求,该NewBase的__metaclass__必须是NewBase
父类的元类的子类,或者相同,这样才能确保NewBase的元类是__metaclass__指定的类。
class NewBase(object):
__metaclass__ = ModelBase
最后
看了这么多,是不是晕了,反正我有点晕,再次提醒大家,最好不要指定元类,也不要用调用type()或其子类的方式来定义类,
因为这块很容易出错。推荐大家还是有class关键字去定义类。
首先,你要知道类是能产生实例的对象。
实际上,类是元类的实例。
>>> class Foo(object): pass
>>> id(Foo)
142630324
Python中的一切皆对象,这些对象分为两种:类对象和实例对象。type类的元类就是它自己。
其次,元类是复杂的,对于类的简单的改动是不需要使用元类的。你可以通过下面两种技术来改变类:
- monkey patching
- 装饰器
这里再说两句吧,在gevent库中就使用了monkey patching,它可以将Python标准库中的IO接口
替换为自己的接口,感觉好牛逼的样子,有时间看看它是怎么实现的。
Python中的元类(译)的更多相关文章
-
Python中的元类(metaclass)
推荐+收藏:深刻理解Python中的元类(metaclass) 做一些笔记学习学习: 在大多数编程语言中,类就是用来描述如何生成一个对象的代码段,在Python中类也是一个对象,这个(类)对象自身拥有 ...
-
[转]深刻理解Python中的元类(metaclass)以及元类实现单例模式
使用元类 深刻理解Python中的元类(metaclass)以及元类实现单例模式 在看一些框架源代码的过程中碰到很多元类的实例,看起来很吃力很晦涩:在看python cookbook中关于元类创建单例 ...
-
深刻理解Python中的元类metaclass(转)
本文由 伯乐在线 - bigship 翻译 英文出处:* 译文:http://blog.jobbole.com/21351/ 译注:这是一篇在Stack overflow上很热 ...
-
深刻理解Python中的元类(metaclass)
译注:这是一篇在Stack overflow上很热的帖子.提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解.他知道这肯定和自省有关,但仍然觉得 ...
-
[转] 深刻理解Python中的元类(metaclass)
非常详细的一篇深入讲解Python中metaclass的文章,感谢伯乐在线-bigship翻译及作者,转载收藏. 本文由 伯乐在线 - bigship 翻译.未经许可,禁止转载!英文出处:stacko ...
-
Python中的元类
从前面"Python对象"文章中了解到,在Python中一切都是对象,类可以创建实例对象,但是类本身也是对象. class C(object): pass c = C() prin ...
-
深刻理解Python中的元类(metaclass)【转】
译注:这是一篇在Stack overflow上很热的帖子.提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解.他知道这肯定和自省有关,但仍然觉得 ...
-
深刻理解Python中的元类(metaclass)以及元类实现单例模式
在理解元类之前,你需要先掌握Python中的类.Python中类的概念借鉴于Smalltalk,这显得有些奇特.在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段.在Python中这一点仍 ...
-
深入理解Python中的元类(metaclass)
原文 译注:这是一篇在Stack overflow上很热的帖子.提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解.他知道这肯定和自省有关,但仍 ...
随机推荐
-
036医疗项目-模块三:药品供应商目录模块——供货商药品目录(批量)添加药品的功能---------Action层
这篇文章我们来讲Action层: 我们先讲开发步骤: 1:我们要根据Service层里面要传的参数,在Action层传入对应的参数. Service层是:public void insertGysym ...
-
C#链接阿里云OCS
一.阿里云OCS简单介绍 阿里云OCS兼容Memcached,因为OCS就相当于Memcached的服务器端,我们代码只是当作客户端,链接上服务器端就行了.阿里云OCS介绍详情见 http://www ...
-
ACM Arithmetic Expression
Description Given N arithmetic expressions, can you tell whose result is closest to 9? Input Line 1: ...
-
PL/SQL-Thread creation error:存储空间不足,无法处理此命令
PL/SQL中执行SQL语句,提示“Thread creation error:存储空间不足,无法处理此命令”.查找了解决方案,如下: 1. 单击开始,然后单击运行. 2. 键入 regedit,然后 ...
-
Oracle行转列的函数
--行转列的函数-- CREATE OR REPLACE FUNCTION Calvin( col IN VARCHAR2,dw IN VARCHAR2) RETURN VARCHAR2 IS ret ...
-
EasyUI的下拉选择框控件方法被屏蔽处理方式
1.html标签如下 <div id="selectMap" style="top: 1px;left: 80px;position: absolute;" ...
-
[转]SQL Server中临时表与表变量的区别
[转]http://blog.csdn.net/skyremember/archive/2009/03/05/3960687.aspx 我们在数据库中使用表的时候,经常会遇到两种使用表的方法,分别就是 ...
-
Redis Windows版安装详解
一.下载Redis Redis下载有两个途径一是官网.二是Github,由于Redis官方只支持Linux系统,所以官网是没有Windows版本的,不过微软开源团队维护了一份所以我们可以使用这个. 官 ...
-
springboot统一异常处理类及注解参数为数组的写法
统一异常处理类 package com.wdcloud.categoryserver.common.exception; import com.wdcloud.categoryserver.commo ...
-
HBase篇(5)- BloomFilter
[每日五分钟搞定大数据]系列,HBase第五篇.上一篇我们落下了Bloom Filter,这次我们来聊聊这个东西. Bloom Filter 是什么? 先简单的介绍下Bloom Filter(布隆过滤 ...