Python metaclass 的原理和应用
元编程(meta programming)是一项很神奇的能力,可以通过代码在运行时动态生成代码。
元类(meta classes)是 Python 提供的一种元编程的能力。在 Python 中,类也是一种对象,那么类这种对象就是元类的实例,所以我们可以在运行时通过实例化元类动态生成类。
使用 type “函数”
首先我们来了解一下 type,type 可以作为函数使用,用来获得对象的类型:
>>> class Foo:
... pass
>>> obj = Foo()
>>> obj.__class__
<class '__main__.Foo'>
>>> type(obj)
<class '__main__.Foo'>
>>> obj.__class__ is type(obj)
True
实际上 type 并不是一个函数,而是一个类,我们可以使用 type(type) 来确定一下:
>>> type(type)
<class 'type'>
type 实际上不只是类,而是一个“元类”。我们接下来要可以看到,所有的元类都需要继承自 type。type 是所以类的元类,所以在上面的例子中 x 是 Foo 的实例,Foo 是 type 的实例,type 又是他自己的实例。
用 type 动态创建类
如果传递给 type 的参数是三个的时候,type 的语义就不再是返回给定参数的类,而是实例化生成一个新的类。
type(name: str, bases: tuple, namespace: dict)
第一个参数是新生成的类的名字;第二个参数是新生成的类的基类列表;第三个参数是要个这个类绑定的属性的列表,比如说这个类的一些方法。实际上 class Foo 这种语法只是使用 type 生成类的语法糖而已。
最简单的一个例子,比如我们要创建 Foo[0..9] 这些类,可以这样做:
classes = []
for i in range(10):
cls = type("Foo%s" % i, tuple(), {})
classes.append(cls)
# 就像使用普通类一样初始化 Foo0
foo0 = clssses[0]()
如果要实现类的方法,一定要记得同样是要使用 self 变量的。在 Python 中 self 只是一个约定俗成的变量,而不是关键字。
def __init__(self, name):
self.name = name
def print_name(self):
print(self.name)
Duck = type("Duck", tuple(), {"__init__": __init__, "print_name": print_name})
duck = Duck("Donald")
duck.print_name()
# Donald
创建自己的元类
首先我们来回顾一下 Python 中类的初始化过程:
foo = Foo()
当这条语句运行的时候,Python 会依次调用 Foo 的 __new__
和 __init__
方法。其中 __new__
方法在 __init__
之前调用,并返回已经创建好的新对象,而 __init__
函数是没有返回结果的。一般情况下,我们都会覆盖 __init__
方法来对新创建的对象做一些初始化操作。
现在回归到元类上,进入烧脑部分。前面我们说过元类的实例化就是类,所以大致相当于:
Foo = MetaFoo(name, bases, attrs) # MetaFoo 默认情况下是 type
foo = Foo()
默认情况下,所有类的元类是 type,也就是在这个类是通过 type 来创建的,这和前面说的通过 type 来动态创建类也是一致的。
那么怎样定义一个 MetaFoo 呢?只需要继承自 type 就行了。因为元类的实例化就是类的创建过程,所以在元类中,我们可以修改 __new__
来在 __init__
之前对新创建的类做一些操作。
>>> class MetaFoo(type):
... def __new__(cls, name, bases, namespace):
... x = super().__new__(cls, name, bases, namespace) # super实际上就是 type
... x.bar = 100 # 为这个类增加一个属性
... return x
...
>>> Foo = MetaFoo("Foo", tuple(), {}) # MetaFoo 在这里就相当于 type 了,可以动态创建类
>>> Foo.bar
100
>>> foo = Foo()
>>> foo.bar
100
在这里我们创建了 MetaFoo 这个元类,他会给新创建的类增加一个叫做 bar 的属性。
在实际的代码中,我们一般还是不会直接动态生成类的,还是调用 class Foo
语法来生成类比较常见一点,这时候可以指定 metaclass 参数就好了。可以通过 Foo(metaclass=MetaFoo) 这种方式来指定元类。
class Foo(metaclass=MetaFoo):
pass
这种定义和上面的元类用法效果完全是一致的。
一个现实世界的元类例子
在 django.models 或者 peewee 等 ORM 中,我们一般使用类的成员变量来定义字段,这里就用到了元类。
class Field:
pass
class IntegerField(Field):
pass
class CharField(Field):
pass
class MetaModel(type):
def __new__(meta, name, bases, attrs):
# 这里最神奇的是:用户定义的类中的 bases 和 attrs 都会作为参数传递进来
fields = {}
for key, value in attrs.items():
if isinstance(value, Field):
value.name = '%s.%s' % (name, key)
fields[key] = value
for base in bases:
if hasattr(base, '_fields'):
fields.update(base._fields)
attrs['_fields'] = fields
return type.__new__(meta, name, bases, attrs)
class Model(metaclass=MetaModel):
pass
这样用户使用的时候就可以这样定义:
>>> class A(Model):
... foo = IntegerField()
...
>>> class B(A):
... bar = CharField()
...
>>> B._fields
{'foo': Integer('A.foo'), 'bar': String('B.bar')}
程序在执行的时候就可以直接访问 X._fields
,而不用每次都通过反射遍历一次,从而提高效率以及做一些验证。
不过,其实这个完全可以通过装饰器来实现:
def model(cls):
fields = {}
for key, value in vars(cls).items():
if isinstance(value, Field):
value.name = '%s.%s' % (cls.__name__, key)
fields[key] = value
for base in cls.__bases__:
if hasattr(base, '_fields'):
fields.update(base._fields)
cls._fields = fields
return cls
@model
class A():
foo = IntegerField()
class B(A):
bar = CharField()
但是用装饰器的话,就失去了一些类型继承的语义信息。
总结与思考
Python 中的元编程还是一种很强大的特性,但是也比较复杂,有时候很难以理解。实际上,过分的动态特性也导致了 Python 的解释器和静态分析、自动补全等很难优化,因为有好多信息必须到运行时才能知道。
实际上近些年新开发的语言越来越多地加入了静态类型的特性,比如 swift, rust, go 等。就连 Python 本身也增加了 type hinting 的功能,很遗憾的是,这个功能不是强制性的,所以也很难用来提升性能。
元类这块应该是我在 Python 语言方面了解的最后一大块知识了。接下来除了写业务代码不会再深究 Python 了,研究 Golang 去了~
Au revoir, Python!
参考
- https://realpython.com/python-metaclasses/
- https://stackoverflow.com/questions/392160/what-are-some-concrete-use-cases-for-metaclasses
- https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/
- https://stackoverflow.com/questions/2608708/what-is-the-difference-between-type-and-type-new-in-python
相关文章
- 在pycharm中如何新建Python文件?_github下载的python源码项目怎么用
- python要不要装pycharm-Python和pyCharm安装
- python秒级创建httpserver和fp文件服务器
- Python开发者必知的13个Python GUI库
- python编程前景_Python前景如何,学完后可以从事方向?
- Python定义计算矩阵转置的函数
- python全局变量赋值_Python全局变量和局部变量[通俗易懂]
- 【说站】python数据模块类如何定义
- 【说站】python使用insert添加列表元素
- 遗传算法的应用实例python实现_遗传算法Python解决一个问题
- python分析人口出生率代码_国家统计局居然也能用的上Python?人口数据Python脚本了解一下?…[通俗易懂]
- 关于python中lambda函数的描述_Python全局变量
- python定义函数求和_Python定义函数实现累计求和操作
- Django框架开发001期 Python+Django开发教程,开启你的第一个django网站应用
- 万能的Python背后:这6大原因让它爆火
- 手把手教你入门Python中的Web开发框架,干货满满!!
- Swath数据几何校正(Python)
- 干货 | 轮廓逼近原理与OpenCV应用(附Python-OpenCV文档下载)
- 新手学Python可行吗?需要什么基础?(新手学python可行吗?需要什么基础)
- Python计算woe及应用
- Python字面量是什么?Python常见的字面量类型及各类字面量输出
- python-Python与MongoDB数据库-使用Python执行MongoDB查询(三)
- python-Python与PostgreSQL数据库-使用Python执行PostgreSQL查询(一)
- ElasticSearch 数据导入导出Python工具详解编程语言
- python提取url中的域名和端口号详解编程语言
- python进阶详解元类及其应用1编程语言
- python全栈开发-Day10 装饰器(闭合函数的应用场)详解编程语言
- Linux上的Python之旅(linux自带python)
- 星球大战与 Python 之间的那些事
- 利用Neo4j和Python进行无缝图形数据库支持(neo4j python)
- 从 Python 连接到 MySQL:实现更多强大的数据库应用(python和mysql)
- Linux 下 Python 升级:轻松完成升级操作(linux下升级python)
- python快速查找算法应用实例