元编程

为什么需要元编程

软件开发领域中最经典的口头禅就是“don’t repeat yourself”。 也就是说,任何时候当你的程序中存在高度重复(或者是通过剪切复制)的代码时,都应该想想是否有更好的解决方案。 在Python当中,通常都可以通过元编程来解决这类问题。

简而言之,元编程就是关于创建操作源代码(比如修改、生成或包装原来的代码)的函数和类。 主要技术是使用装饰器、类装饰器和元类。不过还有一些其他技术,包括签名对象、使用 exec() 执行代码以及对内部函数和类的反射技术等。

维基百科

  • Metaprogramming is a programming technique in which computer programs have the ability to treat programs as their data.
  • 元编程:一种计算机程序可以将代码看待成数据的能力。

如果能够将代码看做数据,那么代码就可以像数据一样在运行时被修改、更新和替换。元编程赋予了编程语言更加强大的表达能力,能够让我们将一些计算过程从运行时挪到编译时、通过编译期间的展开生成代码或者允许程序在运行时改变自身的行为。

总而言之,元编程其实是一种使用代码生成代码的方式。

wraps

一个装饰器就是一个函数,它接受一个函数作为参数并返回一个新的函数。

示例代码1:计算函数执行时间

@wraps(func) 注解是很重要的, 它能保留原始函数的元数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import time
from functools import wraps
def timethis(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

# 测试方法
@timethis
def countdown(n):
while n > 0:
n -= 1

# 调用测试方法
countdown(100000)

输出结果:countdown 0.007091522216796875

装饰器内部定义了一个使用 *args 和 **kwargs 来接受任意参数的函数。在这个函数里面调用了原始函数并将其结果返回,不过你还可以添加其他额外的代码(比如计时)。然后这个新的函数包装器被作为结果返回来代替原始函数。

需要强调的是装饰器并不会修改原始函数的参数签名以及返回值。 使用 args 和 **kwargs 目的就是确保任何参数都能适用。 而返回结果值基本都是调用原始函数 func(args, **kwargs) 的返回结果,其中func就是原始函数。

内置的装饰器比如 @staticmethod, @classmethod,@property 原理也是一样的。 例如,下面这两个代码片段是等价的:

1
2
3
4
5
6
7
8
9
10
class A:
@classmethod
def method(cls):
pass

class B:
# Equivalent definition of a class method
def method(cls):
pass
method = classmethod(method)