元编程是一种编写计算机程序的技术, 这些程序可以将自己看做数据, 因此可以在运行时可以对它进行内省, 生成, 修改.
因此我们可以区分Python元编程的两种主要方法
专注于语言对基本元素(函数, 类, 类型)内省的能力和对运行时创建或修改的能力
允许程序直接处理代码本身, 例如原始的纯文本格式, 或是抽象语法树(AST)
在python中与之相关的工具有:
第一类方法:
装饰器, 允许向现有函数、方法、类中添加附加功能.
类的特殊方法/属性, 允许修改实例的创建过程, 或是访问和修改类/实例的属性.
元类, 允许完全重新设计python面向对象编程范式的实现
第二类方法:
动态代码生成相关
装饰器
一个装饰器, 接收一个函数, 并返回一个新的函数.
典型的装饰器定义如下:
1
2
3
4
5def (func):
def warpper(*args,**kw):
...
return func(*args,**kw)
rerurn warpper
装饰器语法
1
2
3def func(*args, **kwargs):
...
等同于
1func = decorator(func)
一个装饰器函数至少接收一个函数参数func, 并在内部定义新函数warpper, 在warpper的函数体中调用func函数以完成func原本的功能, 并将func的返回值作为自己的返回值, 然后还可以添加其他代码完成额外功能, 最后将warpper函数返回,并赋值给原函数名
注意装饰器语法完全等同于func = decorator(func), 现在func这个变量名已经指向了新函数warpper,即增强版的func, 在完成func原本的功能之余, 还增加了新的特性. 为了保证warpper函数可以接收任何类型和任意数量的参数, 其参数定义为(*args, **kwargs), 并解构传递至func中.
我们的目的是让warpper函数完美替代原本的func, 现在func变量指向了warpper函数, 但是warpper的一些元数据未改变, 例如__name__属性仍然为”warpper”而不是”func”, 即func.__name__ = warpper.
这显然是不合适的. 为了使warpper函数看起来完全就是func, functools模块中提供了一个装饰器@functools.warps, 该装饰器接收一个额外的参数, 该参数应当传入一个函数, 该装饰器会将被装饰的函数的所有元属性更改为与额外参数(另一个函数)完全相同.
所以一个典型的装饰器定义可能如下:
1
2
3
4
5
6def (func):
@functools.warps(func)
def wapper(*args,**kws):
...
return func(*args,**kws)
return wapper
类的特殊方法
使用new方法来覆写实例创建过程
在类的实例化过程中, __init__是一个实例方法, 它接收一个实例(self), 并将其初始化
而创建该类实例的是另一个方法, __new__方法
__new__方法是用来创建实例的静态方法, 因为其特殊性, 所以无需使用@staticmethod来声明.
__new__方法至少接收一个cls参数, 代表当前类
__new__方法必须返回一个实例, 否则实例不会被创建
__new__方法一般返回当前类的实例, 但也可以返回其他类的实例
__new__方法如果返回的不是当前类的实例, __init__方法不会被调用
如果要覆写__new__, 一般使用super().__new__()来获取实际生成的实例, 并在完成自定义逻辑后将其返回. 例如实现单例模式:
1
2
3
4
5
6
7
8class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
以上单例模式的实现有两个重要缺陷:线程不安全以及子类实例化异常
在python中实现单例模式应该直接使用模块的单例性, 将模块级变量定义为需要单例的类的实例
其他可以访问类/实例属性的一些方法
内置函数dir()和vars()
dir(cls) 返回继承自基类的和自己的所有属性的列表
dir(obj) 返回实例和从类中继承的所有属性的列表
vars(cls) 返回类自己的(不包含继承的)所有属性的键和值, 与实际属性相关联, 写有效
vars(obj) 返回实例自己的所有属性的键和值, 与实际属性相关联, 写有效
类和实例的属性
cls.__dict__ 一个字典, 包含类自己的所有属性的键和值, 与实际属性相关联, 写有效
obj.__dict__ 一个字典, 包含实例自己的所有属性的键和值, 与实际属性相关联, 写有效
实际上, vars调用就是返回__dict__属性的值
元类
元类(metaclass)是一个python特性.
元类是定义其他类的原型. 在python中一切皆对象, 所以定义了实例对象的类本身, 也是对象. 进一步考虑, 如果它也是对象, 那么一定有与之相关联的类, 事实上, 所有类定义的基类都是内置的type类.
普通类是type的实例, 而元类是type的子类.
一个继承而不是实例化自type的类, 我们称之为元类(metaclass)
一般语法:
type()可以作为class语句的动态等效.
1
2
3
4def method(self):
return 1
klass = type('MyClass', (object, ), {'method':method})
等效于
1
2
3class klass:
def method(self):
return 1
元类中有四个方法需要关注:
__prepare__(mcs, name, bases, **kwargs) 返回一个空的命名空间
__new__(mcs, name, bases, namespace, **kwargs) 创建一个类
__init__(cls, name, bases, namespace, **kwargs) 初始化一个类
__call__(cls, *args, **kwargs) 让一个类成为callable
参数解释:
mcs 指元类本身, 调用此参数的方法接收的是元类本身
cls 指创建的类, 接收此参数的方法时对类的操作方法
name 指要创建的类的name属性
bases 要创建的类的父类, 元组
namespace 命名空间, 即attrs
动态代码生成exec(object, globals, locals): 用于手动执行代码
eval(expression, globals, locals):用于求表达式值
complie(source, filename, mode):编译任意Python代码
Python语法首先被转换为抽象语法树(Abstract Syntax Tree, AST),然后被编译为字节码. 抽象语法树是对源代码抽象语法结构的一种树状表示. 使用内置的ast模块可以得到Python代码的原始AST, 在传递给complie() 调用之前, 可以对AST进行修改, 以便向现有语法中添加新的语义. 也可以使用纯人工方式创建AST, 不需要解析任何源码, 这样就可以为自定义的领域特定语言创建Python字节码.