Python深入浅出分析元类

  • Post category:Python

介绍一下 “Python深入浅出分析元类” 的完整攻略。

什么是元类

元类是Python中一个比较高级的特性,在我们理解之前,我们需要先搞懂类和对象。
类是一种抽象的概念,用来表示对象的共性特征,而对象则是类的具体实例,拥有自己唯一的数据和方法。

而元类是用来控制对象生成的一种特殊的类。我们知道类实际上也是一种对象,因此元类就是用来控制类对象(class object)生成的一种特殊的类。元类本身是一个虽小却强大的黑魔法,我们平常开发中也比较少使用,不过了解一下也未尝不可。

举个简单的例子,我们可以用一个元类来控制一个类中的方法名都必须以“my_”开头。下面就来看看怎么实现这个功能。

元类示例1:控制方法名

class MyMetaClass(type):
    def __new__(metacls, name, bases, attrs):
        for k, v in attrs.items():
            if callable(v) and not k.startswith('my_'):
                raise TypeError('Method {} must start with "my_"'.format(k))
        return super().__new__(metacls, name, bases, attrs)


class MyClass(metaclass=MyMetaClass):
    def my_method(self):
        print('This is my_method')

    def your_method(self):
        print('This is your_method')

在这个示例中,我们先定义了一个名为 MyMetaClass 的元类,它继承自 type,也就是所有类的元类。这个元类中的 new 方法会在每次类对象生成的时候被调用,通过 attrs 参数来获取生成类的所有属性和方法,如果属性是一个可调用的函数并且不是以 “my_” 开头,就会在生成类时抛出 TypeError 异常。

我们类 MyClass 中有一个以 “my_” 开头的方法 my_method 和一个以 “your_” 开头的方法 your_method,那么使用这个元类生成 MyClass 类时,就不会有任何问题。但是如果我们定义一个不符合要求的方法:

class MyBadClass(metaclass=MyMetaClass):
    def bad_method(self):
        print('This is bad_method.')

m = MyBadClass() # 会抛出 TypeError 异常

这个类的生成就会抛出 TypeError 异常。所以我们就可以通过元类来控制类生成时的行为了。

元类示例2:ORM实现

另一个元类的经典应用就是 ORM 实现。ORM 是指对象关系映射,将关系型数据库中的表(table)和行(row)映射为对象和属性的集合。我们可以通过自定义元类和描述符(descriptor)来实现简单的ORM框架。

class Field:
    def __init__(self, column_name, column_type):
        self.column_name = column_name
        self.column_type = column_type

    def __get__(self, instance, owner):
        return instance.__dict__[self.column_name]

    def __set__(self, instance, value):
        if not isinstance(value, self.column_type):
            raise TypeError('Value type incorrect.')
        instance.__dict__[self.column_name] = value


class ModelMetaClass(type):
    def __new__(cls, name, bases, attrs):
        if name == 'Model':
            return super().__new__(cls, name, bases, attrs)

        table_name = attrs.get('table_name', name)
        mappings = dict()

        for k, v in attrs.items():
            if isinstance(v, Field):
                mappings[k] = v

        for k in mappings.keys():
            attrs.pop(k)

        attrs['__mappings__'] = mappings
        attrs['__table__'] = table_name
        return super().__new__(cls, name, bases, attrs)


class Model(metaclass=ModelMetaClass):
    def __init__(self, *args, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

    def save(self):
        fields = []
        params = []
        args = []

        for k, v in self.__mappings__.items():
            fields.append(v.column_name)
            params.append('?')
            args.append(getattr(self, k))

        sql = 'INSERT INTO %s (%s) VALUES (%s);' % (self.__table__, ','.join(fields), ','.join(params))

        print('SQL: %s' % sql)
        print('ARGS: %s' % args)

在这个示例中,我们先定义了一个名为 Field 的描述符,用来存储列名和列类型。在 ModelMetaClass 中,我们定义了 new 方法,在每次生成类时都会被调用。在此方法中,我们从 attrs 属性中提取出所有列和对应的值,并将它们存储在一个叫做 mappings 的属性中。

在这个 Model 类中,我们有一个通过参数传递属性值并赋值方法的 init 方法和一个保存数据到数据库的 save 方法。在 save 方法中,我们把所有属性都封装成对应字段,生成 INSERT SQL语句并执行。

下面弄几个实例来验证:

class User(Model):
    name = Field('name', str)
    age = Field('age', int)


user = User(name='Bob', age=19)
user.save()

# >> SQL: INSERT INTO User (name,age) VALUES (?,?);
# >> ARGS: ['Bob', 19]

我们定义了一个 User 类,并制定了每个字段在数据库中对应的名称和类型。通过传递对象属性值创建 User 对象后,我们可以使用 save 方法插入数据。

class Blog(Model):
    blog_id = Field('id', str)
    name = Field('name', str)
    author = Field('author', str)
    content = Field('content', str)


blog = Blog(blog_id='10001', name='Python For Data Science', author='Alice', content='Hello world.')
blog.save()

# >> SQL: INSERT INTO Blog (id,name,author,content) VALUES (?,?,?,?);
# >> ARGS: ['10001', 'Python For Data Science', 'Alice', 'Hello world.']

同样的道理,我们定义了一个 Blog 类,并为每个字段在数据库中指定了名称和类型。然后创建一个 Blog 对象并使用 save 方法插入数据。

这就是使用元类和描述符简单实现 ORM 框架的全过程。可以看到,通过元类我们可以控制类的生成过程,从而达到一些特殊的目的。除了简单实现 ORM 框架,我们还可以使用元类来控制构建单例/连接池等常用模式,不过这些都超出了本篇介绍的范围。