元编程(一)
文章目录
一、什么是元编程?
百度百科的定义是:元编程(Metaprogramming)是指某类计算机程序的编写,这类计算机程序编写或者操纵其他程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作。很多情况下与手工编写全部代码相比工作效率更高。
元编程通常有两种方式起作用。一种方式是通过应用程序接口(API)来暴露运行时引擎的内部信息。另一种方法是动态执行包含编程命令的字符串。因此,“程序能编写程序”。虽然两种方式都能用,但大多数元编程主要靠其中一种。
接下来我们看一个例子,它使用的是第二种方式,即动态执行包含编程命令的字符:
#!/bin/bash
# metaprogram
echo '#!/bin/bash' > program
for ((I=1; I<=992; I++)) do
echo "echo $I" >> program
done
chmod +x program
这个程序会生成一个新的脚本文件,它将1. 指定脚本用bash解释的语句 2. 992条打印语句 写入到该文件中,这些语句就是其他的程序。
二、Python 元编程
接下来我们看看python中元编程的定义:
元编程的主要目标是创建函数和类,并用它们来操纵代码(比如说修改、生成或者包装已有的代码)。
关于元编程的两种方式对应的特性有:
- 应用程序接口暴露运行时引擎的内部信息:包括装饰器、类装饰器以及元类、对象签名
- 动态执行包含编程命令的字符串:用exec()来执行代码以及检查函数和类的内部结构
这里我们主要介绍两种比较主要的元编程方式,装饰器和元类编程。
装饰器
装饰器模式的定义
装饰器(Decorator)模式是一种设计模式,它的定义是:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
Python装饰器
在Python中装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。
定义一个装饰器
要定义一个装饰器,计算其他函数的运行时间,只需要编写以下代码:
import time
from functools import wraps
def cal_used_time(func):
"""
Decorator that reports the execution time.
"""
@wraps(func) # 需要使用该装饰器去完成一个自定义的装饰器,否则函数的元信息会丢失
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs) # 被装饰函数正常执行
end = time.time()
print(func.__name__, end-start) # 打印执行时间
return result
return wrapper # 返回装饰后的函数
@cal_used_time
def add(x, y):
return x + y
装饰器的使用方法是一个语法糖,如下写法是等价的:
# 使用装饰器语法糖
@cal_used_time
def add(x, y):
return x + y
# 等价写法
def add(x, y):
return x + y
cal_used_time(add)
使用wraps装饰器编写自定义装饰器
编写装饰器时注意需要使用wraps包裹装饰器,否则原函数的元信息会丢失。
打印以下信息:print(add.__name__)
使用wraps时,打印出来的信息是add
,如果不使用wraps,那么打印出来的信息将是wrapper
,也就是我们定义的装饰器返回的wrapper函数。
那么wraps这个装饰器做了些什么呢?我们可以从源码来看看:
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
'__annotations__')
WRAPPER_UPDATES = ('__dict__',)
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Decorator factory to apply update_wrapper() to a wrapper function
Returns a decorator that invokes update_wrapper() with the decorated
function as the wrapper argument and the arguments to wraps() as the
remaining arguments. Default arguments are as for update_wrapper().
This is a convenience function to simplify applying partial() to
update_wrapper().
"""
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
我们在使用wraps装饰器时,会将原来的函数传递进去(@wraps(func)
),作为wrapped参数,然后wraps返回一个带有部分初始参数的update_wrapper,
@wraps(func) # 返回update_wrapper,partial会把除了wrapper外的参数全部初始化好
def wrapper(*args, **kwargs):
...
return wrapper
update_wrapper有四个参数,wrapper是最终返回的装饰完成的函数,wrapped是被装饰函数,而assigned是需要保留的被装饰函数的元信息,它会原封不动地复制到wrapper中,updated则会把wrapped的值更新到wrapper中。经过上一步的操作,返回的update_wrapper实际上已经定义好了wrapped、assigned和updated参数。
返回的update_wrapper会对wrapper函数进行进行装饰,即update_wrapper(wrapper),这里就把wrapper作为参数传到update_wrapper的wrapper形参上,然后构造一个新的wrapper,并返回,这样就把被装饰函数的元信息保留了下来,另外可以通过wrapper.__wrapped__
来访问被装饰函数。这就是wraps装饰器的作用。
def update_wrapper(wrapper,
wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Update a wrapper function to look like the wrapped function
wrapper is the function to be updated
wrapped is the original function
assigned is a tuple naming the attributes assigned directly
from the wrapped function to the wrapper function (defaults to
functools.WRAPPER_ASSIGNMENTS)
updated is a tuple naming the attributes of the wrapper that
are updated with the corresponding attribute from the wrapped
function (defaults to functools.WRAPPER_UPDATES)
"""
for attr in assigned:
try:
value = getattr(wrapped, attr)
except AttributeError:
pass
else:
setattr(wrapper, attr, value)
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
# Issue #17482: set __wrapped__ last so we don't inadvertently copy it
# from the wrapped function when updating __dict__
wrapper.__wrapped__ = wrapped
# Return the wrapper so this can be used as a decorator via partial()
return wrapper
元类
所有类都是type的实例,但是元类还是type的子类,因此可以作为制造类的工厂。具体来说,元类可以通过实现__init__
方法定制实例。
type的用法
type的用法一:获取数据是哪种类型 : type(object) -> the object’s type
type的用法二:创建类:type(object_or_name, bases, dict)
怎么使用元类来创建类
当我们在定义类的时候,使用的就是type的用法二,实际上是调用了type的__new__
方法,下面的写法是等价的:
class Man:
def __init__(self, age):
self.age = age
man_type = type("Man", (object,), {"__init__": Man.__init__})
所以python加载类的时候,是先把属性和方法加载完毕后,再调用type的new方法
当我们给自己定义的类加上元类时,实际上就是把元类从type改为我们自定义的元类:
class CustomMetaClass(type):
def __new__(cls, clsname, bases, clsdict):
print("custom meta replace type")
return type.__new__(cls, clsname, bases, clsdict)
class Man(metaclass=CustomMetaClass)
def __init__(self, age):
self.age = age
# 实际上就变成了 man_type = CustomMetaClass("Man", (object,), {"__init__": Man.__init__})
元类的特殊方法:
方法名称 | 参数 | 返回值 | 作用 |
---|---|---|---|
__call__ | cls, clsname, bases, clsdict | 返回一个类 | 调用类时触发 |
__new__ | cls, clsname, bases, clsdict | 返回一个类 | 创建一个新的类 |
__init__ | cls, clsname, bases, clsdict | 无返回值 | 初始化类 |
__prepare__ | cls, clsname, bases | 返回命名空间 | 创建命名空间,即obj.__dict__ |
正常初始化时调用顺序是:__prepare__
-> __new__
-> __init__
,prepaere
产生的命名空间就是后续的clsdict
三、总结:
本篇作为一个总概篇,介绍了元编程的定义,及其主要的两种方式。因为装饰器和元类编程的内容都挺多的,所以各作为单独的篇章放在后面。