翻译自:https://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python
什么是元类,怎么运用元类?
类是对象
在理解元类之前,必须掌握Python中的类,Python中的类是非常特别的思想,其借鉴自Smalltalk 语言。
在许多语言中,类是一段描述如何产生对象的代码。Python也是如此.
>>> class ObjectCreator(object):
pass
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x00000295A6FC1C50>
>>>
但是在Python中类不仅仅是如此,类也是对象。当你使用关键字class时,Python解释执行并创建了一个对象。
class ObjectCreator(object):
pass
# 在内存中创建一个名字是 ObjectCreator的对象类) 自己具有创建对象[实例]的能力 ,这就是其被称为类的原因。但是类的本质还是一个对象,这是因为:
• 可以将它赋值给一个变量
• 可以复制他
• 可以给他添加属性
• 可以将它当做参数传递给一个函数
e.g:
>>> print(ObjectCreator) # 你可以打印类,因为其是一个对象
<class '__main__.ObjectCreator'>
>>> echo = lambda 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 0x00000295A6FCF748>
>>>
动态地创建类
由于类是对象,你可以向任何对象一样,随时的创建类。
首选,你可以在函数中用关键字class创建类
>>> def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # 返回类,不是实例
... else:
... class Bar(object):
... pass
... return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # 函数返回的是类不是实例
<class '__main__.Foo'>
>>> print(MyClass()) # 你可以用这个类创建一个对象
<__main__.Foo object at 0x89c6d4c>
但是其动态性还不足,因为你仍需要写整个类
当你使用class关键字时,Python会自动的创建对象。但是在大多时候,你可以自动的控制它的创建。
记得type函数吗。这个古老的函数可以让你知道对象的类型:
>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>
type有种与众不同的能力,它能动态地创建类。type可以接收类的描述,返回一个类。
type函数如下:
type(name of the class,
tuple of the parent class (for inheritance, can be empty),
dictionary containing attributes names and values)
例如:
>>> class MyShinyClass(object):
... pass
可以用下边的方式创建:
>>> MyShinyClass = type('MyShinyClass', (), {}) #返回对象(一个类)
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # 创建这个类的实例
<__main__.MyShinyClass object at 0x8997cec>
你可能注意到我们用MyShinyClass作为类的名字,并且他是一个变量存储实类的引用。这两者是不同的,但是我们在这没必要复杂化。
type接受一个字典作为类的属性。
>>> 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
你可以实现继承
>>> class FooChild(Foo):
... pass
也可以:
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar)
True
>>> FooChild
<class '__main__.FooChild'>
>>> FooChild.__mro__ # FooChild的类搜索树
(<class '__main__.FooChild'>, <class '__main__.Foo'>, <class 'object'>)
>>>
最后,你可以为类添加方法。可以通过合适的签名定义函数,并将其赋值给类的属性。
>>> 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中,类也是对象。你可以动态地凭空(fly)创建一个类。
这就是当你使用关键字class时,Python所做的事。在元类中也是同样如此。
元类是什么
元类是创建类的原料(stuff)
为了创建对象,你可以定义类。但是我们已经认识到Python的类也是对象。
而元类可以创建这样的对象,这些对象是类的类,例如:
MyClass = MetaClass()
MyObject = MyClass()
你已经明白type可以做如下的事情:
MyClass = type(‘MyClass’, (), {})
这是因为函数type实际上是一个元类。Python用元类type来创建所有的类。
你也许奇怪为什么type是小写而不是大写?
我猜测其是为了与str保持一致,str类用来创建字符串对象。iNt类用于创建整型对象。因此type用来创建类的对象。
通过检查__class__
属性,你可以看到这一点。
在Python中一切都是对象。包括整数,字符串,函数和类.他们都是对象,可以由一个类创建:
>>> age.__class__
<class 'int'>
>>> name='non'
>>> name.__class__
<class 'str'>
>>> def foo():pass
>>> foo.__class__
<class 'function'>
>>>
现在,考虑一下,__class__的__class__
属性是什么?
>>> foo.__class__
<class 'function'>
>>> age.__class__.__class__
<class 'type'>
>>> name.__class__.__class__
<class 'type'>
>>> foo.__class__.__class__
<class 'type'>
显然,元类就是创建类对象的原料
你可以称之为类工厂
type是Python的内置元类,但是你也可以创建自己的元类
__metaclass__
属性
你可以为你写的类添加__metaclass__
属性:
class Foo(object):
__metaclass__ = something...
[...]
Python将用元类创建Foo类。
这涉及到一个技巧。
当你写class Foo(object)
时,类对象Foo还没有在内存中创建。Python将首先在类的定义中查找__metaclass__
。如果找到,将会创建一个对象—–类Foo,如果没有找到将使用type创建这个类。
当你做下面的操作时:
class Foo(Bar):
pass
Python将按照下面的步骤执行:
Foo中是否有 __metaclass__
属性?
如果有,在内存中创建一个类对象(class object),并命名为Foo
如果Python没有找到__metaclass__
属性,将会在模块层寻找__metaclass__
,并尝试做同样的事情。
如果最终找不到__metaclass__
属性,他讲使用Bar(Foo的第一个父类)自己的元类(有可能是默认的type)创建类对象。
这里需要注意__metaclass__
属性不会继承自父类的__metaclass__
属性(Bra.class)。如果Bar使用__metaclass__
属性以type()(不是type.__new__()
)的方式创建Bar,子类不会继承这个行为。
现在有个非常重要的问题,我们可以在__metaclass__
中放置什么?
答案是:创建类所需的信息。
谁可以创建一个类?type或者任何使用type的子类。
定制元类
元类的主要目的是在创建类时,动态的改变它。
这通常用于API,你可以创建一个与上下文匹配的类。
举一个比较简单的例子,你决定在某个模块中使所有的类属性全部大写。有许多方式实现,其中一种方式可以在模块层使用__metaclass__
实现。
这种方式中,这个模块的所有类以一个元类创建,我们只需要告诉这个元类将所有的属性转变成大写。
幸运地是,__metaclass__
实际上是可以回调的,我们不必显示的形式化一个类。
举一个简单的例子:
# 元类将自动的获取type传递过来的参数
def upper_attr(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
# type负责创建类
return type(future_class_name, future_class_parents, uppercase_attr)
__metaclass__ = upper_attr #作用到该模块的所有类
class Foo(): # global __metaclass__ won't work with "object" though
# but we can define __metaclass__ here instead to affect only this class
# and this will work with "object" children
bar = 'bip'
print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True
f = Foo()
print(f.BAR)
# Out: 'bip'
现在,将元类定义为一个真实的类:
# -*- coding: UTF-8 –*-
# `type` 就是类似`str` 和 `int` 的类
# 因此你可以继承它
class UpperAttrMetaclass(type):
# __new__ 在 __init__前执行
# __new__创建一个对象并返回
# __init__ 仅仅通过传递参数给对象实现初始化
# __new__很少使用,仅仅用户你想控制对象的创建方式
# 这里我们创建的对象是一个类,需要定制创建,因此需要重写方法__new__
# 你也可以在 __init__ 中做初始化操作
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)
__metaclass__ = UpperAttrMetaclass
但是这还不够OOP。我们可以直接调用type,不必重写或者调用new方法:
class UpperAttrMetaclass(type):
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
# 这是基本的OOP,不是魔法
return type.__new__(upperattr_metaclass,future_class_name,
future_class_parents, uppercase_attr)
注意,type.new的第一个参数upperattr_metaclass,这个参数就相当于class中的第一参数self
作为接收实例一样。upperattr_metaclass这里接收的是类。
按照惯例更改变量的命名之后,代码如下:
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 type.__new__(cls, clsname, bases, uppercase_attr)
我们可以通过使用super使代码更简洁。
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)
的确,元类是特别有用的黑魔法,因此显得有些复杂。但是其本身来说,实现下列功能确很简单:
• 拦截类的创建
• 修改类
• 返回修改的类
为何使用元类?
元类主要用于创建API。一个典型的例子是 Django ORM
它允许你定义如下操作
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
但是你如果这样做:
guy = Person(name='bob', age='35')
print(guy.age)
它不会返回IntegerField 对象。而是返回int。这是由于models.Model定义了__metaclass__,并使用某种魔法将你定义的Person映射为数据库的字段。Django 通过元类暴露API的接口,使一些复杂的操作看起来很简单,通过API重建代码来实现背后的工作。
结语
1.首先,类时对象可以创建实例。
实际上,类是元类的实例。
>>> class Foo(object): pass
>>> id(Foo)
142630324
Python 中一切都是对象,他们是类的实例或元类的实例(type除外)
2.type是它自己的元类。
其次,元类十分复杂。一般情况下,修改简单的类不用元类,二用以下两种技术:
• 猴子补丁
• 类装饰器
99%的情况下采用以上两种方式修改类。