python 面向对象专题(15):元类(四): 自定义metaclass

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命名类方法所接收的第一个参数)。

清楚起见,这里给出的例子的变量和方法名都很长。但在实际的应用中,类似于使用selfcls代替第一个参数,我们可以将这些名字替换为更加简洁的形式:

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会根据需要重新实现这些复杂操作所需要的真正的代码

 

posted @ 2021-05-05 19:22  秋华  阅读(131)  评论(0编辑  收藏  举报