这篇文章是从Stack Overflow上讨论Python 元类的帖子中一个非常热的回答中照搬过来的。同时借鉴了CSDN类似文章 深刻理解Python中的元类(metaclass)以及元类实现单例模式。在这需要感谢以上文章作者的分享,同时在自己博客中记录的目的:一是通过书写能让自己加深对 Python 元类的理解,二是为了保留,方便以后的回顾和再学习。
目录
1. 理解类也是对象
在理解Python元类之前,你首先需要掌握下Python中的类。 Python中类的概念借鉴与Smalltalk。在大多数语言中,类是一组用来描述如何生成一个对象的代码段。 这一点在Python中任然成立
>>> class ObjectCreator(object):
... pass
...
>>> my_obj = ObjectCreator()
>>> print(my_obj)
<__main__.ObjectCretor object at 0x10981d990>
>>>
但是在Python中类远不止如此。类同样也是一种对象。只要你使用关键字class,Python解释器在执行的时候就会创建一个对象。下面的代码段:
>>> class ObjectCreator(object):
... pass
...
将在内存中创建一个对象,名字就是ObjectCreator。这个对象(类)自身拥有创建对象(类实例)的能力,而这就是为什么他是一个类的原因。但本质上它还是一个对象,从而你可以对它做如下的操作:
>>> class ObjectCreator(object):
... pass
...
- 你可以将它赋给一个变量
- 你可以拷贝它
- 你可以给它增加相关属性
- 你可以将它作为一个函数的变量
如下所示:
>>> print(ObjectCreator) # you can print the class because it also a 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 attribute to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCretor object at 0x10981da10>
2. 动态的创建类
因为类也是对象,所以你可以在运行时动态的创建它们,就像其他任何对象一样。首先你可以在函数中创建类,使用class关键字就可以。
>>> def choose_class(name):
... if name == "foo":
... class Foo(object):
... pass
... return Foo # return the class, not instance
... else:
... class Bar(object):
... pass
... return Bar
>>>
>>> Myclass = choose_class('foo')
>>> print(Myclass) # the function return a class, not a instance
<class '__main__.Foo'>
>>> print(Myclass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>
但这还是不够动态,因为你需要自己编写整个类的代码。 由于类也是对象,因此它们也需要什么东西来生成才对。当你你使用class 关键字时,Python解释器会自动创建这个对象。 但就和Python中的大多数事情一样,Pthon任然提供给你手动处理的方法。还记得内建函数type吗? 这个古老但强大的函数能够让你知道一个对象的类型是什么:
>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(ObjectCreator)
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>
同样,type 具有一种完全不同的能力,它也能动态的创建类。type可以将类的描述作为参数,然后生成一个类:
type(name of the class, tuple of the parent class (for inheritance, can be empty), dictonary containing attributes names and values)
例如:
>>> class MyShinyClass(object):
... pass
能通过下面的方式进行创建:
>>> MyShinyClass = type('MyShinyClass', (), {})
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # Create a instance with the class
<__main__.MyShinyClass object at 0x8997cec>
你会发现我们使用”MyShinyClass“作为类名,并且也可以当做一个变量来作为类的引用。
接下来我们来看下如何使用type来创建类的属性:
>>> class Foo(object):
... bar = True
可以用以下方式创建:
>>> print(Foo)
<class '__main__.Foo'>
>>> foo = Foo()
>>> print(foo)
<__main__.Foo object at 0x8a9b84c>
>>> print(foo.bar)
True
同时FooChild 可以继承Foo类:
>>> class FooChild(Foo):
... pass
使用type 进行创建:
>>> FooChild = type('FooChild', (Foo, ), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar)
True
即时你想为FooChild类增加相应的方法,只需要定义好相应的函数,并将该函数作为属性传入:
>>> 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在幕后做的事情,而这就是通过元类来实现的。
3. 元类
3.1 什么是元类
通过上面的描述,类也是对象, 从而可以知道元类就是创建类的类, 如图1:
你定义一个类的目的主要是为了创建一个对象,对吗? 那么我们知道Python class 也是对象。 那么,元类(metaclass)就是用来创建这些对象的类。你可以通过下面的表达式想象下:
>>> Myclass = Metaclass()
>>> m_object = Myclass()
我们知道 type 可以让我们通过下面的方式创建类:
Myclass = type('Myclass', (), {})
这主要是因为type实际上是一个元类(metaclass)。 type是Python中在幕后用来创建所有类的元类。
题外话,为什么 type 不用大写的Type表示呢?也许是为了保持与 ‘str’, ‘int’ 这些类的统一吧。 ‘str’是用来创建字符串的类,‘int’是用来创建整形的类。
你可以通过 __class__来验证,在Python中,所有的东西都是一个类,对的,是所有的东西都是对象。包括 int(整型数),string(字符串), 函数以及类。它们都是对象,而且他们都是从一个类创建出来的。
>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> age.__class__
<type 'str'>
>>> def foo():
... pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object):
... pass
>>> b = Bar()
>>> b.__class__
<type '__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的内建元类,当然你也可以自己创建元类。
3.2 __metaclass__ 的属性
你可以在写一个类的时候为其添加 __metaclass__ 属性, 定义了__metaclass__就定义了这个类的元类。
>>> class Foo(object):
... __metaclass__ = something...
... [...]
例如:当我们写如下代码时:
>>> class Foo(Bar):
... pass
在该类定义的时候,并未在内存中生成,直到它被调用, Python做了如下的操作:
- Foo中有__metaclass__属性吗?如果是,Python会在内存中通过__metaclass__创建一个名字为Foo的类对象(这边是类对象,请注意)
- 如果Python没有找到__metaclass__, 它会继续在父类中寻找__metaclass__属性,并尝试做和前面同样的操作
- 如果Python在任何父类中都找不到__metaclass__, 它就会在模块层次中去寻找__metaclass__, 并尝试做同样的操作
- 如果还是找不到__metaclass__, Python会用内置的type来创建这个类对象
3.3 自定义元类
使用元类的主要目的是在创建类的时能自动改变类。 通常,你会为API做这样的事情,你希望可以创建符合上下文的类。
试想一个非常简单的例子,如果你想在模块里保证类的所有属性都应该是大写的形式。有好几种办法可以办到,但其中一种就是通过设定元类__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.
"""
# 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)
但是,这种方式其实不是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
# 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,这并没有什么特别的。类方法的第一个参数总是表示当前的实例,就像在普通的类方法中的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方法的话,我们还可以使它变得更清晰一些。
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)
参考文献
1. What are metaclasses in Python?