介绍一下 “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 框架,我们还可以使用元类来控制构建单例/连接池等常用模式,不过这些都超出了本篇介绍的范围。