原问题地址:http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python
问题
什么是元类?使用它们能做什么?
答案 1
元类是类的一种。正如类定义了实例功能,元类也定义了类的功能。类是元类的实例。
在Python中你可以随意调用元类(参考Jerub的回答),实际上更有用的方法是使其本身成为一个真正的类。type是Python中常用的元类。你可能觉得奇怪,是的,type本身就是一个类,而且它就是type类型。你无法在Python中重新创造出完全像type这样的东西,但Python提供了一个小把戏。你只需要把type作为子类,就可以在Python中创建自己的元类。
元类是最常用的一类工厂。就像你通过调用类来创建类实例,Python也是通过调用元类来创建一个新类(当它执行class语句的时候),结合常规的 __init__
和__new__
方法,元类可以允许你在创建类的时候做一些“额外的事情”,like registering the new class with some registry(暂时不知道这句话的含义,不知道怎么翻译,字面意思是:就像用某个注册表来注册新的类那样),甚至可以用别的东西完全代替已有的类。当Python执行class语句时,它首先把整个的class语句作为一个正常的代码块来执行。由此产生的命名空间(一个字典)具有待定类的属性。元类取决于待定类的基类(元类是具有继承性的)、或待定类的__metaclass__
属性(如果有的话)或__metaclass__
全局变量。接下来,用类的名称、基类和属性调用元类,从而把元类实例化。然而,元类实际上定义的一个类的类型,而不只是类工厂,所以你可以用元类来做更多的事情。例如,你可以定义元类的一般方法。这些元类方法和类方法有相似之处,因为它们可以被没有实例化的类调用。但这些元类方法和类方法也有不同之处,元类方法不能在类的实例中被调用。type.__subclasses__()
是关于type的一个方法。你也可以定义常规的“魔法”函数,如__add__
, __iter__
和__getattr__
,以便实现或修改类的功能。
摘抄一个例子:
def make_hook(f):
"""Decorator to turn 'foo' method into '__foo__'"""
f.is_hook = 1
return f
class MyType(type):
def __new__(cls, name, bases, attrs):
if name.startswith('None'):
return None
# Go over attributes and see if they should be renamed.
newattrs = {}
for attrname, attrvalue in attrs.iteritems():
if getattr(attrvalue, 'is_hook', 0):
newattrs['__%s__' % attrname] = attrvalue
else:
newattrs[attrname] = attrvalue
return super(MyType, cls).__new__(cls, name, bases, newattrs)
def __init__(self, name, bases, attrs):
super(MyType, self).__init__(name, bases, attrs)
# classregistry.register(self, self.interfaces)
print "Would register class %s now." % self
def __add__(self, other):
class AutoClass(self, other):
pass
return AutoClass
# Alternatively, to autogenerate the classname as well as the class:
# return type(self.__name__ + other.__name__, (self, other), {})
def unregister(self):
# classregistry.unregister(self)
print "Would unregister class %s now." % self
class MyObject:
__metaclass__ = MyType
class NoneSample(MyObject):
pass
# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)
class Example(MyObject):
def __init__(self, value):
self.value = value
@make_hook
def add(self, other):
return self.__class__(self.value + other.value)
# Will unregister the class
Example.unregister()
inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()
print inst + inst
class Sibling(MyObject):
pass
ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__
shareedit
答案 2
作为对象的类
在理解元类之前,你需要掌握Python中的类。Python对于类的定义很特别,这是从Smalltalk语言中借鉴来的。
在大多数语言中,类只是描述如何创建一个对象的代码段。Python中的类大体上也是如此:
>>> class ObjectCreator(object):
... pass
...
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>
而Python中的类并不是仅限于此。它的类也是对象。
是的,对象。
当你使用关键字class时,Python执行它并创建一个对象。下面是有关的指令
>>> class ObjectCreator(object):
... pass
...
这个对象(类)本身就能够创建一些对象(实例),这就是为什么它是类。
但它仍然是一个对象,因而:
- 你可以将它分配给一个变量
- 你可以复制它
- 你可以增加它的属性
- 你可以把它作为一个功能参数来用
例如:
>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
... print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>
Creating classes dynamically
类的动态创建
既然类是对象,你就能动态地创建它们,就像创建任何对象那样。
首先,你可以在一个使用class的函数中创建类:
>>> def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # return the class, not an instance
... else:
... class Bar(object):
... pass
... return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>
但它并不是动态的,因为你还是要自己写出整个类。
类是对象,它们必须由某种东西产生。
当你使用关键字class时,Python会自动创建该对象。但是,与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可以把对于类的描述作为参数,并返回一个类。
(同样的函数根据你传入的参数而有完全不同的用途。我知道这看起来有点怪。这是因为Python向后兼容。)
type是这样应用的:
例如:
>>> class MyShinyClass(object):
... pass
可以这样子来进行手动创建
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__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) # bar is inherited from Foo
True
最终,你会想把一些方法添加到你的类中。需要用一个适当的识别标志来定义一个函数,并将其指定为一个属性。
>>> 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中,类就是对象,你可以动态地创建类。
这就是关键字class在Python中的应用,它是通过使用元类来发挥作用。
元类是什么
元类是用来创建类的东西。
你通过定义类来创建对象,对吧?
但我们知道Python的类就是对象。
元类用于创建这些对象,元类是类的类,你可以这样来描述它们:
MyClass = MetaClass()
MyObject = MyClass()
你已经看到了type可以这样来用:
MyClass = type('MyClass', (), {})
这是因为type实际上是一个元类,作为元类的type在Python中被用于在后台创建所有的类。
现在你感到疑惑的是为什么这里是小写的type,而不是大写的Type?
我想这是为了与创建字符串对象的类str和创建整数对象的类int在写法上保持一致。type只是用于创建类的类。
你可以通过检查__class__
的属性来看清楚。
Python中的一切,我的意思是所有的东西,都是对象。包括整数、字符串、函数和类。所有这些都是对象。所有这些都是由一个类创建的:
>>> 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__
是什么?
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>
所以,元类只是用于创建类对象的东西。
如果你愿意,你可以把它称为“类工厂”。
type
是Python中内建元类,当然,你也可以创建你自己的元类。__metaclass__
的属性当你创建类的时候,可以添加一个__metaclass__
属性:
class Foo(object):
__metaclass__ = something...
[...]
如果你这样做,Python会使用元类来创建Foo这个类。
小心,这是棘手的。
这是你是首次创建class Foo(object)
,但是类对象Foo在内存中还没有被创建。Python会在类定义中寻找__metaclass__
。如果找到它,Python会用它来创建对象类Foo。如果没有找到它,Python将使用type来创建这个类。
把上面的话读几遍。
当你写下:
class Foo(Bar):
pass
Python会实现以下功能:
Foo有没有__metaclass__
的属性?如果有,通过借鉴__metaclass__
,用Foo这个名字在内存中创建一个类对象(我说的是一个类对象,记住我的话)。如果Python找不到__metaclass__
,它会在模块层级寻找__metaclass__
,并尝试做同样的事情(但这只适用于不继承任何东西的类,基本上是旧式类)。如果它根本找不到任何__metaclass__
,它将使用Bar(第一个父类)自己的元类(这可能是默认的type)来创建类对象。小心点,__metaclass__
属性不会被继承,而父类的元类(Bar.__class__
)将会被继承。如果Bar
所用的__metaclass__
属性是用type()
来创建Bar(而不是type.__new__()
),它的子类不会继承这种功能。现在最大的问题是,你可以在__metaclass__
中写些什么?
答案是:可以创建类的东西。
什么可以创建类?type,或者父类。
自定义元类
一个元类的主要目的是当它被创建时,这个类可以自动改变。
通常在API中,可以创建一个元类,以之匹配于当前的内容。
想象一个愚蠢的例子:模块中的所有类的属性都应该用大写字母来写。你有几种方法,其中的一种方法就是在模块层次上设置__metaclass__
。
这样,这个模块中所有的类都将使用这个元类来创建,我们只需要告诉元类把所有属性改为大写。
幸运的是,__metaclass__
实际上可以任意调用,它并不需要成为一个正式的类(我知道,名字中带有“class”字样的东西未必就是类,想想看吧…但这是有益的)。
因此,我们将通过使用函数来举一个简单的例子。
# 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.
"""
# pick up any attribute that doesn't start with '__' and uppercase it
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)
__metaclass__ = upper_attr # this will affect all classes in the module
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'
现在,让我们完全照做,但使用一个真的类作为元类:
# 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)
但这不是真正的面向对象编程。我们直接调用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
# reuse the type.__new__ method
# this is basic OOP, nothing magic in there
return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
你可能已经注意到额外的参数upperattr_metaclass
,它没有什么特别之处:__new__
以upperattr_metaclass
为第一参数,并且在upperattr_metaclass
中被定义。就像你把self作为实例的第一个参数那样,或者作为类方法的第一个参数。
当然,我在这里为了清晰起见,使用了一个很长的名称,但就像self一样,所有的参数都有习惯的名称。所以,在开发实践中所写的元类看起来是这样的:
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使它更清楚,这样将缓解继承(因为,是的,你可以拥有元类,继承metaclasses
,继承type):
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)
就是这样。关于元类真的是没有更多要讲的了。
使用元类的代码复杂的原因并不在于元类,那是因为你通常用元类来实现一些奇怪的功能,而这些功能要依靠自省、继承、变量,如:__dict__
等。
的确,元类对于“魔法”特别有用,这些事务是复杂的,但元类本身很简单:
- 拦截类的创建
- 修改类
- 返回修改后的类
你为什么要使用元类而不是函数?
__metaclass__
可以被任意调用。既然类明显更复杂,你为什么还要使用它呢?
这样做有几个理由:
- 目的明确。当你读
UpperAttrMetaclass(type)
- 时,你知道要遵循的是什么。
- 可以使用面向对象编程。元类可以继承元类、重写父类的方法。元类甚至可以使用元类。
- 可以优化代码。你从不为像上面例子中琐碎的东西而使用元类。它通常用于复杂的东西。在一个类中有多种方法并且将它们优化组合,是非常有价值的,使得代码可读性更强。
- 你可能喜欢使用
__new__
- ,
__init__
- 和
__call__
- 。它们能帮你实现不同的功能。尽管你通常可以用
__new__
- 来实现所有的功能,有些人还是更喜欢使用
__init__
- 。
- 这些被称为元类。可恶!它肯定意味着什么!
你为什么会使用元类?
现在的大问题是:为什么你会使用一些复杂难懂、容易出错的特性?
嗯,通常你不会这样做:
元类是更深层次的魔法,超过99%的用户不需要担心。不要怀疑你是否需要元类(那些真正需要元类的人对此确定无疑,并且不需要解释为什么)。
——Python专家蒂姆﹒彼得斯
元类的主要使用案例是创建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转化成一个复杂的钩子连接到数据库字段。
通过显示一个简单的API和元类,Django使复杂的东西看起来简单,再从API重构代码去完成真正的幕后工作。
最后的话
首先,你知道,类是可以创建实例的对象。
事实上,类本身就是实例。在元类中
>>> class Foo(object): pass
>>> id(Foo)
142630324
在Python中,一切都是对象,它们都是类的实例或元类的实例。
但是type除外。
type实际上是自己的元类。你无法在纯粹的Python中复制它,所以只能在实施层面做点小把戏。
其次,元类是复杂的。你可能不想把它们用于非常简单的类。你可以用两种不同的技术来改变类:
- 猴子补丁monkey patching
- 类装饰器
当你需要改变类的时候,99%的情况下,使用它们是明智之举。