1 自定义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
# 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)
上述基本就是关于metaclass的一切了。
使用metaclass之所以复杂,不是因为其代码实现复杂,而是因为我们一般使用metaclass来做一些逻辑上很复杂的操作,例如自省,修改继承以及改变类的默认attribute如__dict__
等。
metaclass的确可以被用来实现一些奇妙的功能,也因此可以用来进行极其复杂的逻辑操作。但是metaclass本身是很简单的:
- 影响class初始化的过程
- 修改class的内容
- 返回修改过的class
2 为什么我们要使用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吗?
3 为什么我们要使用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会根据需要重新实现这些复杂操作所需要的真正的代码