python高阶教程-修饰符与面向切面编程

本篇内容来自原创小册子《python高阶教程》,点击查看目录

从面向对象编程到面向切面编程

面向对象编程(Object Oriented Programming)是编程史上的一个跨越,它完成了过程与数据的封装,使得每个类都只完成自己特有的功能,提高的代码的可重用性。面向对象编程的主要特点是继承、多态与封装。

但是,在某些情况下,面向对象编程反而会降低代码可重用性。比如在和数据库相关的操作中,每个类初始化时都要完成数据库的连接,在使用完毕后释放连接。类似的操作会造成“固定流程”的代码在类中反复出现,这就催生了面向切面编程。

面向切面编程(Aspect Oriented Program)做了很好的补充,它可以完成代码的动态切入。一般上,我们把重复使用的代码片段成为切面,而这些代码片段切入的目标成为切入点。所以,面向切面编程就是把所有可能重复的代码、固定流程的代码分离出来做成切面,在需要时切入到目标类或者目标函数中。

在python中,面向切面编程是通过修饰符完成的,修饰符的符号是@.

简单的修饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def decorator(func):
print("func's name is ", func.__name__)
return func
# way 1
def a():
pass
a = decorator(a)
# way 2
@decorator
def b():
pass
# run it
a()
b()

运行上述代码后,输出为

1
2
func's name is a
func's name is b

这个例子比较简单,稍微解释下。方法一和方法二是等价的, decorator函数就是在执行func之前先打印func的名字,然后返回func的函数指针,这个指针成为了新的func. 所以后续在执行func时,首先会打印函数名,然后才会执行真正的func函数体。

函数有参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def decorator(func):
def wrapper(*arg, **kw):
print("func's name is", func.__name__)
return func(*arg, **kw)
return wrapper
# way 1
def a(arg):
print("arg of a is", arg)
a = decorator(a)
# way 2
@decorator
def b(arg):
print("arg of b is", arg)
# run it
a(1)
b(2)

运行上述代码后,运行结果为:

1
2
3
4
func's name is a
arg of a is 1
func's name is b
arg of b is 2

这次的代码稍微复杂了一些,但是从第一种修饰的方法来看,即a = decorator(a),会好很多。在函数有参数的情况下,我们执行a(1)就相当于执行了decorator(a)(1), 这就出现了函数的嵌套。

第一层函数为decorator,参数为a, 即被修饰的函数func,返回值为函数wrapper.

第二层函数为wrapper,参数为1,返回为被修饰的函数的返回值

结合decorator(a)(1),可知decorator(a)返回wrapper,然后再调用wrapper(1), 在wrapper(1)中再调用a(1).

修饰符有参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def decorator(arg_of_decorator):
def second(func):
def wrapper(*arg, **kw):
print("arg of decorator is", arg_of_decorator)
print("func's name is", func.__name__)
return func(*arg, **kw)
return wrapper
return second
# way 1
def a(arg):
print("arg of a is", arg)
a = decorator("arg os dec for a")(a)
# way2
@decorator("arg of dec for b")
def b(arg):
print("arg of b is", arg)
# run it
a(1)
b(2)

运行上述代码后,输出为:

1
2
3
4
5
6
arg of decorator is arg of dec for a
func's name is a
arg of a is 1
arg of decorator is arg of dec for b
func's name is b
arg of b is 2

同样的,我们使用方法一写出完整的调用链,即decorator("arg of dec for a")(a)(1),可以看出这里的函数嵌套是三层的,因为这里有三个不同层次的参数需要传递。

第一层为decorator, 参数为”arg of dec for a”,返回值为second

第二层为second, 参数为a, 返回值为wrapper

第三层为wrapper, 参数为1,即被修饰的函数的参数,返回值为被修饰的函数的返回值

用函数修饰类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def decorator(arg_of_decorator):
def second(class_name):
def wrapper(*arg, **kw):
print("class is", class_name)
print("arg of decorator is",
arg_of_decorator)
return class_name(*arg, **kw)
return wrapper
return second
@decorator("use function")
class test:
def __init__(self, num):
self.num = num
def print(self):
print(self.num)
ins = test(1)
ins.print()

运行上述代码后,输出为

1
2
3
class is <class '__main__.test'>
arg of decorator is use function
1

从调用链来看,全部代码执行完毕相当于decorator(“use function”)(test)(1).print(), 虽然连续进行了4次调用过程,但是由于最后的print是由对象调用方法来实现的,所以修饰符函数内部只有3次嵌套。

第一层是decorator,参数为”use function”, 返回值为second

第二层为second, 参数为test, 返回值为wrapper

第三层为wrapper,参数为1, 返回值为test(1)

用类修饰类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class decorator:
def __init__(self, arg_of_decorator):
print("dec init arg is", arg_of_decorator)
self.arg_of_decorator = arg_of_decorator
def __call__(self, class_name):
print("dec call arg is", class_name)
class second:
def __init__(self,class_name):
self.class_name = class_name
def __call__(self, *args):
return self.class_name(*args)
self.class_name = class_name
return second(self.class_name)
@decorator("use class")
class test:
def __init__(self,num):
self.num = num
def print(self):
print(self.num)
ins = test(2)
ins.print()

运行上述代码后,输出为:

1
2
3
dec init arg is use class
dec call arg is <class '__main__.test'>
2

完整的调用过程为decorator(“use class”)(test)(2).print(),同样的,修饰符的类有三个参数需要传递。分别是”use class”, test, 2

使用类的好处在于init方法和call方法可以各自传一个参数,由输出可知,init方法传入的是”use class”, call 方法传入的是test, 紧接着我们只需要嵌套定义一个类完成参数2的传入即可。所以使用嵌套类second,在嵌套init方法传入被修饰的类名test,在嵌套call 方法中传入参数2.

注意这里嵌套类的init方法不是必须的,即可以改为下述代码,输出不变

1
2
3
4
5
6
7
8
9
10
11
12
13
class decorator:
def __init__(self, arg_of_decorator):
print("dec init arg is", arg_of_decorator)
self.arg_of_decorator = arg_of_decorator
def __call__(self, class_name):
print("dec call arg is", class_name)
class second:
def __init__(self):
pass
def __call__(self, *args):
return class_name(*args)
self.class_name = class_name
return second()

那是否可以把*args参数用second类的嵌套init方法传入呢? 答案是不行。因为second类的init方法接受参数是在类的实例化也即调用类的时候完成的,这个过程在decorator类的return second中,这就要求decorator类完成三个层次的参数传入,是不可能实现的。

所以,两个嵌套类最多完成三个层次的参数传入。

最后从调用链的角度看第一个例子

如果从调用链来理解,写出调用关系,即执行a()后真正的执行过程为decorator(a)(),这里有一个函数的依次调用顺序。

第一层为decorator,参数为被修饰的函数,即func,返回值为被修饰的函数

第二层为func,即真正定义的函数。

这里不能说是两层的嵌套函数,因为decorator返回的是一个函数,而不是函数的执行结果。

那为什么不能返回函数的执行结果呢? 即如下代码:

1
2
3
def decorator(func):
print("func's name is ", func.__name__)
return func()

原因在于如果返回函数的执行结果,a = decorator(a)执行完毕后a就变成了一个数值而不是函数。

那为什么其他例子可以返回函数的执行结果呢?

因为其他例子的decorator里面有一个或者多个嵌套函数,在执行a = decorator(a)或者a= decorator(“arg”)(a)之后,返回的是一个函数的引用,decorator的最内层嵌套函数还没有执行

最内层的嵌套函数要一直等到a(1)出现时才执行。

0%