网站首页 文章专栏 python高阶教程-修饰符与面向切面编程
本篇内容来自原创小册子《python高阶教程》,点击查看目录。
面向对象编程(Object Oriented Programming)是编程史上的一个跨越,它完成了过程与数据的封装,使得每个类都只完成自己特有的功能,提高的代码的可重用性。面向对象编程的主要特点是继承、多态与封装。
但是,在某些情况下,面向对象编程反而会降低代码可重用性。比如在和数据库相关的操作中,每个类初始化时都要完成数据库的连接,在使用完毕后释放连接。类似的操作会造成“固定流程”的代码在类中反复出现,这就催生了面向切面编程。
面向切面编程(Aspect Oriented Program)做了很好的补充,它可以完成代码的动态切入。一般上,我们把重复使用的代码片段成为切面,而这些代码片段切入的目标成为切入点。所以,面向切面编程就是把所有可能重复的代码、固定流程的代码分离出来做成切面,在需要时切入到目标类或者目标函数中。
在python中,面向切面编程是通过修饰符完成的,修饰符的符号是@
.
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()
运行上述代码后,输出为
func's name is a func's name is b
这个例子比较简单,稍微解释下。方法一和方法二是等价的, decorator函数就是在执行func之前先打印func的名字,然后返回func的函数指针,这个指针成为了新的func. 所以后续在执行func时,首先会打印函数名,然后才会执行真正的func函数体。
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)
运行上述代码后,运行结果为:
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).
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)
运行上述代码后,输出为:
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,即被修饰的函数的参数,返回值为被修饰的函数的返回值
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()
运行上述代码后,输出为
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)
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()
运行上述代码后,输出为:
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方法不是必须的,即可以改为下述代码,输出不变
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返回的是一个函数,而不是函数的执行结果。
那为什么不能返回函数的执行结果呢? 即如下代码:
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)出现时才执行。