渣翻StackOverflow高票问答:python中的元类是什么,原问答地址:https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python/100037#100037
在了解元类之前,需要掌握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
上面这段代码在内存中创建名为ObjectCreator的对象。
这个对象(类)拥有创建对象(实例)的能力,所以它是一个类。
但是它也仍然是一个对象,因此:
比如:
# 可以打印一个类因为它是个对象 >>> print(ObjectCreator) <class '__main__.ObjectCreator'> >>> def echo(o): >>> print(0) # 作为参数传递 >>> 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>
既然类是对象,那么可以像任意对象那样动态地创建它。
首先,可以在一个函数中使用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自动创建一个对象,但是和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
是这样工作的:
type(name, bases, attrs)
在这个段代码中:
name
: 类命bases
: 父类元组(为了继承关系,可以为空)attrs
: 包含属性名和值的字典比如:
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
甚至可以像给普通创建的类对象添加方法那样,可以在动态地创建类之后,给类添加更多的方法。
>>> def echo_bar_more(self): ... print('yet another method') ... >>> FooChild.echo_bar_more = echo_bar_more >>> hasattr(FooChild, 'echo_bar_more') True
到这里你可以明白:在python中,类是对象,可以动态地创建类。
这就是当你使用class
关键字时python的行为,当使用元类时也是这样做的。
元类是创建类的东西。我们为了创建对象而定义类,但是在python中类也是对象。所以,元类就是创建这些对象的东西。它们是类的类。
MyClass = MetaClass() my_object = MyClass()
你已经看过了type
允许你做的操作:
MyClass = type('MyClass', (), {})
是因为type
其实是一个元类,type
是python在幕后用来创建所有类的元类。
你可能会疑惑它为什么是小写的,为什么不写作Type?
我猜是为了保持一致性,str是创建strings对象的类,int是创建integer对象的类,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'>
所以元类只是创建类对象的东西,如果你想也可以叫它类工厂(class factory),type
是python内置的元类,当然也可以创建自己的元类。
__metaclass__
属性在python2中,当你写一个类实现代码的时候可以添加一个__metaclass__
属性。
class Foo(object): __metaclass__ = something [...]
如果你像上面这样做,python使用这个元类来创建类Foo
。小心,这种方式很棘手。你先写下class Foo(object)
,但是类对象Foo
还没有在内存中被创建。
python会在类定义中查找__metaclass__
,如果找到,他就会用元类来创建对象类Foo
,如果没有,使用type
来创建类。
class Foo(Bar): pass
当执行上面的代码的时候,python执行以下操作:
先查找Foo
有没有__metaclass__
属性。如果有,使用__metaclass__
在内存中创建一个类对象。如果找不到__metaclass__
,将在模块级别查找__metaclass__
,然后尝试做同样的操作(但仅限于不继承任何东西的类,基本是旧式类)。
如果它找不到任何的__metaclass__
,将会使用bar
(第一个父类)的元类(可能会是默认的type
)来创建类对象。
要小心__metaclass__
属性不会被继承,父类的元类(Bar.__class__
)会被继承。如果Bar
使用了一个用type()
方法(而不是type.__new__
)来创建Bar
的__metaclass__
属性,子类将不会继承这个行为。
现在问题是可以在__metaclass__里放进什么?答案就是可以创建类的东西。那什么可以创建类呢?type
,或者任何它的子类或者使用它的东西。
设置元类的语法在python3中已被改变
class Foo(object, metaclass=something): ...
比如,__metaclass__
属性不再使用,而是作为基类列表的关键字参数。但是元类的行为基本保持不变。
python3中的元类新增的是你也可以使用关键字参数给元类传递属性,比如:
class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2): ...
下面的内容将讲述python是如何处理的
元类的主要目的是在创建类的时候自动地去改变类。
通常是为了创建匹配当前上下文的类的API这样做。
想象你决定在你的模块中的所有的类的属性都要大写。有几种方法可以实现,但是其中一种是设置一个模块级别的__metaclass__
。用这种方法,这个模块的所有类都使用这个元类创建,我们只需要告诉这个元类把所有的属性转成大写即可。
幸运得,__metaclass__
可以是任何可调用的对象,它不需要是一个普通类。
所以,我们可以用一个函数来开始一个简单的例子
# 这个元类将自动地被传递你过去传递给type的同样的参数 def upper_attr(future_class_name, future_class_parents, future_class_attrs): """ 返回一个属性被转换为大写的类对象 """ uppercase_attrs = { attr if attr.startswith("__") else attr.upper(): v for attr, v in future_class_attrs.items() } # type来做创建类的操作 return type(future_class_name, future_class_parents, uppercase_attrs) # 这个将会影响模块的所有类 __metaclass__ = upper_attr # 全局的__metaclass__不会对对象起作用 class Foo(): # 但是我们可以在这里定义__metaclass__来只影响这个类,并且会影响到对象的子类 bar = 'bip'
可以验证一下:
>>> hasattr(Foo, 'bar') False >>> hasattr(Foo, 'BAR') True >>> Foo.BAR 'bip'
现在我们使用一个真正的类作为元类来实现同样的功能
# type其实是像str和int一样的类,所以可以继承自type class UpperAttrMetaclass(type): # __new__方法在__init__方法之前被调用 # 它是创建并返回对象的方法 # __init__只初始化作为参数传递的对象 # 除非想要控制对象是如何创建的,否则很少使用__new__ # 在这里被创建对象是类,我们想要自定义它 # 所以重写__new__方法 # 如果你想也可以在__init__中做一些操作 # 一些高级用法包括重写__call__我们在这里不做讲述 def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attrs): uppercase_attrs = { attr if attr.startswith('__') else attr.upper(): v for attr, v in future_class_attrs.items() } return type(future_class_name, future_class_parents, uppercase_attrs)
现在我们知道了他们的含义,让我们用更短更现实的变量名来重写上面的方法
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, attrs): uppercase_attrs = { attr if attr.startswith("__") else attr.upper(): v for attr, v in attrs.items() } return type(clsname, bases, uppercase_attrs)
你可能已经注意到了额外的参数cls
,它没什么特殊的:__new__
始终接收定义它的类作为第一个参数,就像普通方法的self
参数,它接受实例作为第一个参数,或者作为类方法的时候接受定义它的类作为第一个参数。
但是这不是合适的面向对象OOP思想。我们可以直接调用type
,并且不重写或者调用父类的__new__
:
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, attrs): uppercase_attrs = { attr if attr.startswith("__") else attr.upper(): v for attr, v in attrs.items() } return type.__new__(cls, clsname, bases, uppercase_attrs)
使用super可以让它更清晰明了,它将会简化继承(因为你当然可以从type中继承,从元类中继承,从而拥有元类)
class UpperAttrMetaclass(type): def __new__(cls, clsname, bases, attrs): uppercase_attrs = { attr if attr.startswith("__") else attr.upper(): v for attr, v in attrs.items() } return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attrs)
在python3中,如果你像下面这样使用关键字参数调用
class Foo(object, metaclass=MyMetaclass, kwarg1=value1): ...
它在元类中会转化成这样去使用:
class MyMetaclass(type): def __new__(cls, clsname, bases, dct, kwargs1=default): ...
之所以使用元类的代码这么复杂不是因为元类,而是因为通常你使用元类去操作依赖于内省,操纵继承,变量如__dict__
等等的扭曲的事情上
确实元类在做这些黑魔法操作上特别有用,所以才会有这么复杂的东西,但是他们本身是很简单的:
既然__metaclass__
接受任何可调用对象,既然使用类明显得更复杂为什么还要使用类?
下面是这么做的几个原因:
UpperAttrMetaclass(type)
的时候,你知道接下来会发生什么__new__
,__init__
和__call__
这些允许你做不同操作的函数,即使通常你可以全部在__new__
中实现,也有一些人更乐意使用__init__
.问题是为什么要用一些隐晦的容易出错的特性呢?
其实通常我们不用。
元类是更深奥的魔法,99%的用户都不需要担心它。如果你不确定你是否需要它们,那么你就不需要(需要它们的人一定很确定他们需要元类并且不需要解释)
_Python Guru Tim Peters
使用元类的主要应用场景是创建API。一个典型的例子就是Django的ORM,它允许你像下面这样去定义:
class Person(model.Model): name = models.CharField(max_length=30) age = models.IntegerField()
但是如果你这样做:
person = Person(name='bob', age='35') print(person.age)
它不会返回一个IntegerField
对象,而是返回一个int
,甚至可以直接从数据库中获取。
这可能是因为models.Model
定义了__metaclass__
,它使用了一些魔法将你使用简单语句定义的Person
转化为连接到数据库字段的复杂钩子。
Django通过暴露一个简单的API和使用元类,从这个API中重建代码来完成幕后的实际操作,使复杂的东西看起来简单。
首先,你知道类是可以创建实例的对象。
实际上类就是他们本身的实例,元类的实例。
>>> class Foo(object): pass >>> id(Foo) 142630324
python中的一切都是对象,它们都要么是类的实例,要么是元类的实例。除了type
。
type其实是它自己的元类。它不是你可以在纯python中重现的东西,它是在实现级别上做了一些欺骗才实现的。
其次,元类很复杂,你可能不像用它们来做很简单的类修改,你可以通过下面两个不同的技术来改变类:
99%的情况,你最好使用这些来改变类。
但是98%的情况你完全不需要修改类。
元类是类的类,一个类定义了类的实例的行为,同样一个元类定义了类的行为。类是元类的实例。
在python中可以为元类使用任意的可调用对象,但是更好的实现是让它成为一个真正的类。type
是python的常见元类,type
本身是一个类,并且是它自己的类型。虽然在python中不能完全重现类似type
的东西,但是python里还是有些小技巧,为了创建自己的元类,只需要继承type
。
一个元类最常用作类工厂(class-factory),当你通过调用类来创建对象的时候,python通过调用元类来创建一个新类(当执行class语句的时候)。通过__init__
和__new__
方法的结合,元类允许你在创建一个一个类的时候做一些额外的操作,比如用一些注册信息来注册类,或者使用其他东西完全取代类。
当执行class
语句(class statement)的时候,python首先像执行普通的代码块那样去执行class
语句体。结果命名空间(一个字典)暂存了这个即将生成的类(class-to-be)的属性。元类是通过查找这个即将生成的类(元类被他继承)的__metaclass__
属性或者__metaclass__
全局变量来决定的。然后使用此类类命,基类,属性调用元类来实例化它。
然而,元类实际定义了类的类型,而不仅仅是类工厂,所以可以使用元类来做的事情很多,比如,可以在元类上定义常规方法,这些元类方法就类似于类方法,可以在不实例化类的时候被调用,但是又不像类方法,他们不能被类的实例对象调用. type.__subclasses__()
就是一个在type元类里这样的方法的例子。同样也可以定义常规魔术方法,比如__add__
,__iter__
,__getattr__
来实现或者改变类行为。
下面是一些零碎的例子:
def make_hook(f): """将foo方法变为__foo__的装饰器""" f.is_hook = 1 return f class MyType(type): def __new__(mcls, name, bases, attrs): if name.startswith('None'): return None; # 遍历属性确认他们是否需要重命名 newattrs = {} for attrname, attrvalue in attrs.iteritems(): if getattr(attrvalue, 'is_hook', 0): newattrs['__%s__' % attrname] = attrvalue else: newattrs[attrname] = attrvalue return super(MyType, mcls).__new__(mcls, 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 # 或者自动生成类命和类 # 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__