python metaclass 详细说明
<meta charset="utf-8">
Class也是Object
在理解metaclass之前,我们需要先理解Python中的class。从某种程度上来说,Python中的class的定位比较特殊。
对于大部分面向对象语言来说,class是一段定义了如何产生object的代码块。在Python中这一定义也成立:
>>> class example(object):
... pass
...
>>> object1 = example()
>>> print(object1)
<__main__.example object at 0x102e26990>
但是在Python中,class并不只有这一角色。class实际上也是object。当我们使用class定义一个类的时候,Python会执行相应代码并在内存中创建一个名为example
的object。
但该object(class)是具有创建其他object(instance)的能力的。这也是这个object是一个class的原因。由于本质上class任然是一个object,所以我们可以对class做出以下操作:
- 我们可以将其赋给一个变量
- 我们可以对其进行拷贝
- 我们可以赋给其新的变量
- 我们可以将其作为参数赋给其他的函数
举例如下:
# print a class since it's an object
>>> print(example)
<class '__main__.example'>
# assign an attribute to the class
>>> print(hasattr(example, 'new_attribute'))
False
>>> example.new_attribute = 'assign an attribute to the class'
>>> print(hasattr(example, 'new_attribute'))
True
>>> print(example.new_attribute)
assign an attribute to the class
# assign the class to a variable
>>> example_mirror = example
>>> print(example_mirror)
<class '__main__.example'>
>>> print(example_mirror())
<__main__.example object at 0x102e26a90>
# pass class as a parameter
>>> def echo(cls):
... print(cls)
...
>>> echo(example)
<class '__main__.example'>
动态创建class
既然class也是object,那么我们就可以像创建普通的object一样动态创建class。
第一种方法,我们可以在方法中创建class。如下面的例子所示:
>>> def dynamic_class_creater(name):
... if name == 'name1':
... class class1(object):
... pass
... return class1
... else:
... class class2(object):
... pass
... return class2
...
>>> first_class = dynamic_class_creater('name1')
>>> print(first_class)
<class '__main__.class1'>
>>> print(first_class())
<__main__.class1 object at 0x10e4149d0>
但通过这种方式创建class并没有特别动态。我们任然需要自己定义类的具体内容。考虑到class也是object,那么也一定有某种方法能够像产生instance一样产生类。
当我们使用class关键字创建类的时候,Python会自动创建对应的object。像Python中其他大多数情况一样,我们也可以手动创建这个class object。这一操作可以通过type()
实现。
通常情况下我们可以调用type
来得到一个object的类型是什么。如下面的例子所示:
>>> print(type(1))
<type 'int'>
>>> print(type('str'))
<type 'str'>
>>> print(type(example()))
<class '__main__.example'>
>>> print(type(example))
<type 'type'>
在这里我们看到我们所创建example类的type是'type'
。这实际上也就是接下来要讨论的内容。既type
的完全不同的功能——type
可以动态创建class。type()
函数可以接收class的描述来作为参数并返回所生成的class object。type
同时具有这两个迥异的功能是由于Python兼容性问题导致的。在此我们不做深究。
当使用type
创建class时,其用法如下:
type(class_name, tuple_of_parent_class, dict_of_attribute_names_and_values)
其中第二个参数tuple_of_parent_class
用来表示继承关系,可以为空。第三个参数用来描述我们所要创建的类所应该具有的attribute。如下面的例子所示:
>>>class class_example(object):
... pass
上面定义的这个类可以由如下type
函数创建:
>>>class_example = type('class_example', (), {}) # create a class on the fly
>>>print(class_example)
<class '__main__.class_example'>
>>> print(class_example()) # get a instance of the class
<__main__.class_example object at 0x10e414b10>
在这个例子中,type
所接收的第一个参数'class_example'
是该类的类名,同时我们使用了class_example
作为存储该class object引用的变量。这二者可以不同。但一般我们没有理由采用不同的名字从而使得代码更加复杂。
我们也可以使用一个字典来定义所创建的class的attribute:
>>> class_example = type('class_example', (), {'attr': 1})
>>> print(class_example)
<class '__main__.class_example'>
>>> print(class_example.attr)
1
>>> print(class_example())
<__main__.class_example object at 0x10e414a90>
>>> print(class_example().attr)
1
上面的例子中type
返回的class等同于下面这个class:
>>> class class_example(object):
... attr = 1
当然,我们也可以用type返回一个继承class_example
的类:
>>> child_example = type('child_example', (class_example,), {})
>>> print(child_example)
<class '__main__.child_example'>
>>> print(child_example.attr)
1
上面这个例子中type返回的class等同于如下class:
>>> class child_example(class_example):
... pass
我们甚至可以动态创建包括方法的类。只要我们创建好方法并将其赋给相应的attribute即可:
>>> def echo(self):
... print(self.attr)
...
>>> child_example = type('child_example', (class_example,), {'echo': echo})
>>> hasattr(class_example, 'echo')
False
>>> hasattr(child_example, 'echo')
True
>>> child_example().echo()
1
同样,我们也可以先动态创建一个class,然后再赋给其新的方法:
>>> child_example = type('child_example', (class_example,), {})
>>> def another_method(self):
... print('another method')
...
>>> child_example.another_method = another_method
>>> hasattr(child_example, 'another_method')
True
>>> child_example().another_method()
another method
综上所述,Python中的class其实是一个object,并且我们可以动态创建class。事实上这也是我们在使用class关键字的时候Python所做的事情。Python通过使用metacalss来实现这一过程。
究竟什么是metaclass?
metaclass就是Python中用来创建class object的class。我们可以将其看做能够产生class的类工厂。我们可以通过如下例子理解这个关系:
class = metaclass()
object = class()
从上文中我们知道了type()
可以被用来动态创建class,这是因为实际上type
是一个metaclass。而且type
实际上是Python用在在幕后创建所有class的metaclass。
包括int, string, function, class在内,Python中所有的东西都是object,而所有的object都是被相应的class创造的。我们可以通过__class__
的值得知这一点。
>>> age = 24
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> bar = Bar()
>>> bar.__class__
<class '__main__.Bar'>
那么,这些__class__
的__class__
又是什么呢?
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> bar.__class__.__class__
<type 'type'>
可以看出,所有的class都来自于type
。type
,作为metaclass,创建了以上所有的class object。
type
是Python定义好的metaclass。当然,我们也可以自定义metaclass。
类的metaclass attribute
当定义class的时候,我们可以使用__metaclass__
attribute来指定用来初始化当前class的metaclass。如下面的例子所示:
class Foo(object):
__metaclass__ = something
[other statements...]
如果我们指定了__metaclass__
,Python就是使用这个metaclass来生成class Foo
。
当Python试图创建class Foo
的时候,Python会首先在class的定义中寻找__metaclass__
attribute。如果存在__metaclass__
,Python将会使用指定的__metaclass__
来创建class Foo
。如果没有指定的话,Python就会使用默认的type
作为metaclas创建Foo
。
所以,对于下面这个例子:
class Foo(Bar):
pass
Python首先在Foo
中寻找是否存在__metaclass__
attribute。
如果存在的话,Python将使用这个metaclass在内存中创建一个名字为Foo
的class object。如果Python
如果class定义中不存在__metaclass__
的话,Python将会寻找MODULE级别的__metaclass__
。如果存在的话鸠进行与前述相同的操作。但是只有我们定义的class没有继承任何类的情况下,Python才会在MODULE级别寻找__metaclass__
。或者说,只有当该类是一个旧类的情况下,Python才会在MODULE级别寻找__metaclass__
。(关于新类和旧类的区别,请看这篇文章).
当Python仍然没有找到__metaclass__
时,Python将会使用当前类的母类的metaclass来创建当前类。在我们上面这个例子中,Python会使用Foo
的母类Bar
的metaclass来创建Foo
的class object。
同时需要注意的是,在class中定义的__metaclass__
attribute并不会被子类继承。被子类继承的是母类的metaclass,也就是母类的.__class__
attribute。比如上面的例子中,Bar.__class__
将会被Foo
继承。也就是说,如果Bar
定义了一个__metaclass__
attribute来使用type()
创建Bar
的class object(而非使用type.__new__()
),那么Bar
的子类,也就是Foo
,并不会继承这一行为。
那么问题来了:我们究竟应该在__metaclass__
attribute中定义什么?
答案是:能够创建class的东西。
那么什么能够创建class呢?type
,或者任何type
的子类。
自定义metaclass
metaclass的主要目的是在class被创建的时候对生成的class进行自动的动态修改。
一般来说,这一点主要应用于API,例如我们想要根据当前的内容创建相匹配的class。
举一个简单的例子如下:我们决定让当前module下所有的class的attribute的名字都是大写。要实现这个功能有很多种方法。使用__metaclass__
就是其中之一。
设置了__metaclass__
的话,class的创建就会由指定的metaclass处理,那么我们只需要让这个metaclass将所有attribute的名字改成大写即可。
__metaclass__
可以是任何Python的callable
,不必一定是一个正式的class。
下面我们首先给出一个使用function作为__metaclass__
的例子。
# the metaclass will automatically get passed the same argument
# that is passed to `type()`
def upper_attr(class_name, class_parents, 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 turn it into uppercase.
uppercase_attr = {}
for name, val in class_attr.items():
if name.startswith('__'):
uppercase_attr[name] = val
else:
uppercase_attr[name.upper()] = val
# let `type` do the class creation
return type(class_name, class_parents, uppercase_attr)
class Foo(object):
# this __metaclass__ will affect the creation of this new style class
__metaclass__ = upper_attr
bar = 'bar'
print(hasattr(Foo), 'bar')
# False
print(hasattr(Foo), 'BAR')
# True
f = Foo()
print(f.BAR)
# 'bar'
接下来我们通过继承type的方式实现一个真正的class形式的metaclass。注意如果尚不清楚__new__
和__init__
的作用和区别的,请看这篇文章.
# remember that `type` is actually a just a class like int or str
# so we can inherit from it.
class UpperAttrMetaclass(type):
'''
__new__ is the method called before __init__
It's the function that actually creates the object and returns it.
__init__ only initialize the object passed as a parameter.
We rarely use __new__, except when we want to control how the object
is created.
For a metaclass, the object created is a class. And since we want to
customize it, we need to override __new__.
We can also do something by overriding __init__ to get customized initialization
process as well.
Advanced usage involves override __call__, but we won't talk about this here.
'''
def __new__(upperattr_metaclass, class_name, class_parents, class_attr):
uppercase_attr = {}
for name, val in class_attr.items():
if name.startswith('__'):
uppercase_attr[name] = val
else:
uppercase_attr[name.upper()] = val
return type(class_name, class_parents, uppercase_attr)
但这不是很OOP。我们直接调用了type
而非调用type.__new__
。那么OOP的做法如下。
class UpperAttrMetaclass(type):
def __new__(upperattr_metaclass, class_name, class_parents, class_attr):
uppercase_attr = {}
for name, val in class_attr.items():
if name.startswith('__'):
uppercase_attr[name] = val
else:
uppercase_attr[name.upper()] = val
# basic OOP. Reuse the parent's `__new__()`
return type.__new__(upperattr_metaclass, class_name, class_parents, uppercase_attr)
我们注意到,__new__
所接收的参数中有一个额外的upperattr_metaclass
。这没有什么特别的。如同__init__
总是接收调用它的object作为第一个参数一样(惯例上用self
来命名__init__
所接收的第一个参数),__new__
总是接收其被定义在内的class作为第一个参数,就像类方法总是接收其被定义的class作为第一个参数一样(惯例上用cls
命名类方法所接收的第一个参数)。
清楚起见,这里给出的例子的变量和方法名都很长。但在实际的应用中,类似于使用self
和cls
代替第一个参数,我们可以将这些名字替换为更加简洁的形式:
class UpperAttrMetaclass(type):
def __new__(cls, cls_name, bases, attr_dict):
uppercase_attr = {}
for name, val in attr_dict.items():
if name.startswith('__'):
uppercase_attr[name] = val
else:
uppercase_attr[name.upper()] = val
return type.__new__(cls, cls_name, bases, uppercase_attr)
通过应用super
,我们可以使得上面这段代码更加干净简洁,也使得继承更加容易(我们可能有metaclass继承别的一些metaclass,而这些metaclass又继承type
):
class UpperAttrMetaclass(type):
def __new__(cls, cls_name, bases, attr_dict):
uppercase_attr = {}
for name, val in attr_dict.items():
if name.startswith('__'):
uppercase_attr[name] = val
else:
uppercase_attr[name.upper()] = val
return super(UpperAttrMetaclass, cls).__new__(cls, cls_name, bases, uppercase_attr)
Voilà!上述基本就是关于metaclass的一切了。
使用metaclass之所以复杂,不是因为其代码实现复杂,而是因为我们一般使用metaclass来做一些逻辑上很复杂的操作,例如自省,修改继承以及改变类的默认attribute如__dict__
等。
metaclass的确可以被用来实现一些奇妙的功能,也因此可以用来进行极其复杂的逻辑操作。但是metaclass本身是很简单的:
- 影响class初始化的过程
- 修改class的内容
- 返回修改过的class
为什么我们要使用metaclass,而不是使用一些函数来实现类似的功能?
就像前文所说,__metaclass__
实际上可以是任何callable
,那么为什么我们还要使用metaclass而不是直接调用这些函数呢?
使用class作为metaclass有如下几个理由:
- 使用class作为metaclass能够使得我们代码的动机更加明确。比如当我们读到上面所定义的
UpperAttrMetaclass(type)
代码时,我们清楚地知道接下来这段代码想要干什么(自定义class object初始化的过程)。 - 我们能够使用OOP的思想进行处理。class作为metaclass可以继承其他的metaclass,重载母类的方法,甚至可以使用别的metaclass。
- 如果我们使用class作为metaclass,某一使用该metaclass的class的子类将仍是是其metaclass的实例。但这一功能无法通过使用函数作为metaclass实现。
- 使用metaclass可以使得代码结构更加优美。实际应用中我们很少使用metaclass来实现上面那样简单的功能。使用metaclass往往是为了实现非常复杂的操作。如果使用class作为metaclass,我们就可以把相应的方法封装到这一个metaclass中,使得代码更加易懂。
- 使用class作为metaclass可以在class中容易的定义
__new__
,__init__
,__call__
方法。虽然我们在将所有的逻辑都放入__new__
中,但有的时候根据需要使用其他几个方法会使得逻辑更加清晰。 - 额贼!人家名字就叫metaclass。这不是带着个class吗?
为什么我们要使用metaclass呢?
那么究竟为什么我们要使用metaclass这样一个难以理解且容易出错的实现方式呢?
答案是通常情况下我们不需要使用metaclass。
引用Python大师Tim Peters的话来说,就是:
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).
metaclass主要的使用情况就是用来创建API。使用metaclass的一个典型的例子是Django ORM。
它是的我们可以使用如下当时定义一个model:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
同时,如果我们调用这个model:
guy = Person(name='bob', age='35')
print(guy.age)
其并不会返回一个IntegerField
对象,而是会返回一个int
,甚至可以直接从数据库中调用这个值。
正是因为models.Model
定义了__metaclass__
,并使用了一些操作来将我们使用简单的语句定义的Person
转化成了与数据库相应的域相联系的类,这种逻辑才成为可能。
Django使得很多复杂的逻辑仅暴露一个简单的API接口就可以调用,这正是通过metaclass实现的。metaclass会根据需要重新实现这些复杂操作所需要的真正的代码。
再说两句
首先我们知道了Python中的class实际上是object,同时class仍具有创建对应的实例的能力。
实际上class本身也是metaclass的实例。
>>> class Foo(object):
... pass
...
>>> id(Foo)
4299321816
Python中的任何东西都是object,这些object不是class的实例就是metaclass的实例。
当然,type
除外。
type
事实上是其自身的metaclass。我们使用Python是无法重复这种实现的。这一逻辑是在Python代码实现的层面定义的。引用一下道德经中的说法,我们可以说Python中type生metaclass,metaclass生class,class生万物。
另外,metaclass的应用一般颇为复杂,大多数情况下我们可以使用别的方法实现相同的功能。比如我们可以通过一下两种技术修改class:
- monkey patching
- class decorators
99%我们需要改变class的情况下,我们使用上述两种技术可以解决。
但事实是,99%的情况下我们根本不需要改变class。
作者:耀凯考前突击大师
链接:https://www.jianshu.com/p/224ffcb8e73e
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。