Python中的元类
也许写代码的时候没有写过元类,但是它的概念和原理还是要理解。
目录
type()
类是元类的实例。因为在Python中万物皆对象,其实类和元类也都是对象。在Python中默认的元类是type
。
当type
作为一个函数时,它既可以查看对象所属的类,还可以创建一个类。除此之外type
也是一个类,是它自己的类。
使用type
查看对象所属的类。同时可以看到type
的类就是type
。
In [29]: class Foobar(object):
...: pass
...:
In [30]: foo = Foobar()
In [31]: type(foo)
Out[31]: __main__.Foobar
In [32]: type(Foobar)
Out[32]: type
In [33]: type(type)
Out[33]: type
正是因为前面提到的类也是对象,所以也可以动态的创建类。使用type
动态创建类,需要的参数依次为:类名,父类的元组,属性字典。
In [41]: def hello(self, name=None):
...: return 'hello, {}'.format(name or self.name)
In [43]: SayHello = type('SayHello', (object,), {'name': 'Snail', 'hello': hello})
In [44]: SayHello
Out[44]: __main__.SayHello
In [45]: action = SayHello()
In [46]: action.hello()
Out[46]: 'hello, Snail'
In [47]: action.hello('world')
Out[47]: 'hello, world'
metaclass
使用type()
可以动态创建类。这也是metaclass的作用,控制类的创建行为。
如何创建自己的元类
在Python中,通过创建继承自type
的类来定义新的元类。无论是Python2还是Python3,我们都可以通过下面的方式创建自己的元类
class MyMeta(type):
pass
使用元类创建类时,Python2是使用添加__metaclass__
属性的方式。
class MyClass(object):
__metaclass__ = MyMeta
pass
而在Python3中是在类定义中添加metaclass
参数。
class MyClass(object, metaclass=MyMeta):
pass
__metaclass__
属性
我们已经知道Python2使用__metaclass__
属性来创建类。所以在真正创建类之前Python会先查找__metaclass__
属性。查找的顺序:
- 在类定义中查找
- 在父类中查找[2] [3]
- 在MODULE层查找
__metaclass__
属性。只有在Python2中,并且创建的类是old-style类时[1]。
写一个元类
下面我们试着真正的写一个元类。假设一个问题:“我们希望模块中所有类的属性都是大写”。
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)
试着去理解,如果有看不明白的,可以在了解Python对象实例化后再回来理解这个例子。
什么时候需要用元类
一种流行的说法
Metaclasses are deeper magic that 99% of users should never worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why). – Python Guru Tim Peters
其实在日常使用中,你有可能用过metaclass。
- Django中ORM。Django在models.Model模块中定义了
__metaclass__
- acb模块中的ABCMeta
reference
- What is a metaclass in Python ?
- 回答中e-satis的回答对我帮助很大。
- Understanding Python metaclasses
- 作者使用的是Python3。并且文章中很大篇幅讲解了Python实例化过程。Python对象实例化我会单独写一篇文章。
- 类和对象的创建过程
注[1]:old-style类是指class MyClass: pass
这种类定义方式。对应的new-style是指class MyClass(object): pass
。
class OutMetaclass(type):
def __new__(cls, clsname, bases, attrs):
print 'OutMetaclass', clsname
return super(OutMetaclass, cls).__new__(cls, clsname, bases, attrs)
__metaclass__ = OutMetaclass
class NewClass(object):
pass
class OldClass:
pass
得到的结果
➜ test python A.py
OutMetaclass OldClass
注[2]:我在Python2.7中试验过一种写法(为了举例子)
class OutMetaclass(type):
def __new__(cls, clsname, bases, attrs):
print 'OutMetaclass', clsname
return super(OutMetaclass, cls).__new__(cls, clsname, bases, attrs)
__metaclass__ = OutMetaclass
class InMetaclass(type):
def __new__(cls, clsname, bases, attrs):
print 'InMetaclass', clsname
return super(InMetaclass, cls).__new__(cls, clsname, bases, attrs)
class MyClass(object):
__metaclass__ = OutMetaclass
class My2Class(MyClass):
__metaclass__ = InMetaclass
会得到如下错误(这是需要注意的点):
➜ test python A.py
OutMetaclass MyClass
InMetaclass My2Class
Traceback (most recent call last):
File "A.py", line 19, in <module>
class My2Class(MyClass):
File "A.py", line 13, in __new__
return super(InMetaclass, cls).__new__(cls, clsname, bases, attrs)
TypeError: Error when calling the metaclass bases
metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
注[3]:是先在module这种全局中查找,还是先在父类中查找?Stack Overflow中说先在module层查找。但是我的实验结果是先在父类中查找。
class OutMetaclass(type):
def __new__(cls, clsname, bases, attrs):
print 'OutMetaclass', clsname
return super(OutMetaclass, cls).__new__(cls, clsname, bases, attrs)
__metaclass__ = OutMetaclass
class InMetaclass(type):
def __new__(cls, clsname, bases, attrs):
print 'InMetaclass', clsname
return super(InMetaclass, cls).__new__(cls, clsname, bases, attrs)
class OldClass:
__metaclass__ = InMetaclass
class SubOldClass(OldClass):
pass
结果如下(说明先在父类中查找):
➜ test python A.py
InMetaclass OldClass
InMetaclass SubOldClass