一、函数名的应用 1、函数名是一个特殊的变量,函数名存放的是函数的内存地址 def func(): print(‘hello‘) print(func) # <function func at 0x000001A228B01E18> 2、函数名可以作为一个变量 def func(): print(‘hello‘) f = func # 把函数名当成变量赋值给另外一个变量 f() # 通过变量f调用函数 3、函数名可以作为容器类型的元素 def func1(): print(‘func1‘) def func2(): print(‘func2‘) def func3(): print(‘func3‘) def func4(): print(‘func4‘) list1 = [func1, func2, func3, func4] for f in list1: f() 4、函数名可以作为函数的参数 def func1(): print(‘func1‘) def func2(arg): print(‘func2‘) arg() # 执行传递进来的arg func2(func1) # 把func1当成参数传递给func2 5、函数名可以作为函数的返回值 def func1(): print(‘func1‘) def func2(): print(‘func2‘) return func2 # 把func2当成返回值返回 ret = func1() # 调用func1,把返回值赋值给ret ret() # 调用ret 二、闭包 1、定义 内层函数对外层函数(非全局)的变量的引用,这个内层函数就成为闭包。 在Python中,我们可以使用__closure__来检测函数是否是闭包。 有cell元素的是闭包。 例如: def func(): name = ‘番薯‘ def func2(): print(name) # 引用外层函数的变量 func2() print(func2.__closure__) # (<cell at 0x000002591EA794F8: str object at 0x000002591EACB500>) func() print(func.__closure__) # None 2、闭包的例子 2-1、这里并没有引用外层函数的变量,而是把外层函数的变量传给func2,所以不算闭包 def func1(): name = ‘番薯‘ def func2(arg): print(arg) func2(name) print(func2.__closure__) func1() 2-2、 def func1(): name = ‘番薯‘ def func2(): print(name) # 引用外层函数的变量,形成闭包 func2() print(func2.__closure__) func1() 2-3、 def func1(name): def func2(): print(name) # 引用外层函数的变量,形成闭包 func2() print(func2.__closure__) func1(‘番薯‘) 3、闭包的应用 3-1、把内部函数(闭包)当成返回值返回就可以使用闭包了 def func1(): name = ‘番薯‘ def func2(): print(name) return func2 # 把内部函数当成是返回值返回 ret = func1() # 把返回值赋值给变量ret ret() # 调用内部函数 3-2、多层嵌套的闭包 def func1(): def func2(): def func3(): print(‘func3‘) return func3 return func2 f2 = func1() # func2 f3 = f2() # func3 f3() 4、闭包的好处 可以在任何时间从外界访问到内部函数。 但是我们知道一个函数执行完毕后,这个函数中的变量以及局部命名空间中的内容都是会被销毁的。 在闭包中如果变量被销毁了,那内部函数就不能正常执行了。 所以一旦这个内部函数引用了外层函数中的变量形成了闭包,那么这个变量将不会随着外层函数的结束而销毁,它会在内存中保留。 也就是说,闭包可以保留变量的引用。 三、装饰器初识 1、开放封闭原则 软件设计的原则: 开闭原则, 又被成为开放封闭原则。 开放封闭原则是指对扩展代码的功能是开放的, 但是对修改源代码是封闭的。 这样的软件设计思路可以保证我们更好的开发和维护我们的代码。 比如:你开了一家老字号(下面的代码就相当于源码) def old_shop(): print(‘离百年老字号还差99年‘) old_shop() 然后你的老店要新增空调(相当于新增功能): def old_shop(): print(‘新增空调‘) print(‘离百年老字号还差99年‘) old_shop() 这样添加功能确实是可以的,但是违背了开放封闭原则,直接在源码上进行修改了。 那怎么办呢?新建一个函数不就好了 def old_shop_with_conditioner(): print(‘新增空调‘) print(‘离百年老字号还差99年‘) old_shop_with_conditioner() 但是,问题又来了,你的老字号很火,开了很多分店,每家分店都是调用了之前的old_shop函数开店, 那么你修改了之后,所有调用原来函数的分店都需要重新调用新的函数,这么做其实是很番薯的行为。 那么如何在不改变函数的结构和调用方式的基础上,动态的给函数添加功能呢? 可以利用闭包 def old_shop(): print(‘离百年老字号还差99年‘) def a(func): def b(): print(‘新增空调‘) func() return b ret = a(old_shop) # 内层的b函数 ret() 然后问题又来了,现在虽然没有直接修改源码,但是函数名还是改变了,那又怎么办? 重命名不就行了吗: def old_shop(): print(‘离百年老字号还差99年‘) def a(func): def b(): print(‘新增空调‘) func() return b old_shop = a(old_shop) # 内层的b函数 old_shop() 这样不就遵循了开发封闭原则了吗,即没有修改源码,扩展代码又是开放的,也没有改变函数原来的调用方式 2、装饰器语法糖 刚才上面的代码只是装饰器的原理和雏形,Python中针对于上面的功能提供了一个快捷的写法,俗称装饰器语法糖。 使用装饰器语法糖的写法,实现同样功能的代码如下: def a(func): # a是我们定义的装饰器函数,func是被装饰的函数(old_shop) def b(): print(‘新增空调‘) func() return b @a # 相当于把被装饰的函数old_shop当成参数传给a,然后把返回值b再重新赋值给被装饰的函数名old_shop def old_shop(): print(‘离百年老字号还差99年‘) old_shop() # 相当于调用了内层函数b 四、装饰器进阶 1、装饰带返回值的函数 def wrapper(func): def inner(): print(‘新功能‘) # 你要新增的功能 result = func() # 拿到被装饰函数的返回值 return result # 返回被装饰的函数的返回值 return inner @wrapper def f(): return ‘Hello‘ ret = f() print(ret) 2、装饰带参数的函数 def wrapper(func): def inner(x, y): # 实际执行函数的参数 print(‘新功能‘) r = func(x, y) return r return inner @wrapper def my_sum(x, y): return x + y ret = my_sum(1, 2) # inner(10, 20) print(ret) 但是一般来说,我们把参数设置成动态参数会更便于拓展 若是三个数相加,或者四个数相加,只需要修改my_sum的参数就可以了 def wrapper(func): def inner(*args, **kwargs): print(‘新功能‘) r = func(*args, **kwargs) return r return inner @wrapper def my_sum(x, y, z): return x + y + z ret = my_sum(1, 2, 3) print(ret) 3、带参数的装饰器 装饰器如果要带参数的话,可以嵌套更多层: def outer(arg): def wrapper(func): def inner(*args, **kwargs): print(‘欢迎来到%s‘ % arg) func(*args, **kwargs) return inner return wrapper @outer(‘英雄联盟‘) # 会先执行outer,然后返回函数名wrapper,相当于@wrapper,在闭包内还能使用最外层的函数变量 def lol(): print(‘这里是召唤师峡谷‘) @outer(‘地下城与勇士‘) # @wrapper def dnf(): print(‘这里是阿拉德大陆‘) lol() dnf() 4、装饰器修复技术 被装饰的函数最终都会失去本来的__doc__等信息,就是说,如果这个函数被装饰了, 那么它里面的文档信息(注释信息,通常注释信息很重要,注释写明了改函数的功能和参数的信息等)就会消失, Python给我们提供了一个修复被装饰函数的工具,用于找回这些信息。 from functools import wraps def wrapper(func): @wraps(func) def inner(*args, **kwargs): print(‘这是新功能‘) func(*args, **kwargs) return inner @wrapper def f1(x, y): """ 这里写这个函数的主要功能 :param x: 这个参数的类型 :param y: 这个参数的类型 :return: 返回值 """ print(‘我是帅哥‘) print(f1.__doc__) # 打印这个函数的文档信息(注释内容) print(f1.__name__) # 打印这个函数名 5、多个装饰器装饰同一函数 def wrapper1(func): print(‘w1‘) def inner1(): print(‘inner1‘) return ‘<i>{}</i>‘.format(func()) return inner1 def wrapper2(func): print(‘w2‘) def inner2(): print(‘inner2‘) return ‘<b>{}</b>‘.format(func()) return inner2 @wrapper1 @wrapper2 def f1(): return "小明" ret = f1() print(ret) 结果: w2 w1 inner1 inner2 <i><b>小明</b></i> 分析: 在装饰阶段会直接执行装饰函数,并拿到返回值,即 @wrapper2 ---> wrapper2(f1) ---> print(‘w2‘) ---> return inner2 ---> 把变量f1重新指向inner2 @wrapper1 ---> wrapper1(f1)[此时的f1实际上是inner2] ---> print(‘w1‘) ---> return inner1 ---> 把变量f1重新指向inner1 然后执行f1()相当于执行inner1() print(‘inner1‘) return ‘<i>{}</i>‘.format(func()) 此时的func是传进来的参数inner2,所以又去执行inner2 print(‘inner2‘) return ‘<b>{}</b>‘.format(func()) 此时的func是传进来的参数f1[被装饰的f1],所以拿到返回值<b>小明</b>,拼接到inner1的返回值那里,最后 <i><b>小明</b></i> 五、数码暴龙进化装饰器 1、类装饰器 我们除了可以使用函数装饰函数外,还可以用类装饰函数。 class Page(object): def __init__(self, a=None): self.a = a self.mode = "装饰" def __call__(self, *args, **kwargs): if self.mode == "装饰": self.func = args[0] # 默认第一个参数是被装饰的函数 self.mode = "调用" return self # 当self.mode == "调用"时,执行下面的代码(也就是调用使用类装饰的函数时执行) if self.a: print("欢迎来到{}页面。".format(self.a)) else: print("欢迎来到首页。") self.func(*args, **kwargs) @Page() def index(name): print("Hello {}.".format(name)) @Page("电影") def movie(name): print("Hello {}.".format(name)) if __name__ == ‘__main__‘: index(‘番薯‘) movie(‘番薯‘) 2、装饰类 上面所有的例子都是装饰一个函数,返回一个可执行函数。Python中的装饰器除了能装饰函数外,还能装饰类。 可以使用装饰器,来批量修改被装饰类的某些方法 # 定义一个类装饰器 class D(object): def __call__(self, cls): class Inner(cls): # 重写被装饰类的f方法 def f(self): print(‘Hello 番薯‘) return Inner @D() class C(object): # 被装饰的类 # 有一个实例方法 def f(self): print("Hello world.") if __name__ == ‘__main__‘: c = C() c.f() 六、装饰器小结(重) 1、装饰器的标准结构 from functools import wraps def wrapper(func): # func:被装饰的函数 @wraps(func) # 把func指向的函数的__doc__、__name__等属性复制到inner上面 def inner(*args, **kwargs): # *args和**kwargs是被装饰函数的参数 print(‘新功能‘) r = func(*args, **kwargs) print(‘新功能也可以在这里‘) return r return inner @wrapper # 此时会执行wrapper,所以wrapper必须定义在这一行之前 def hello(): print(‘Hello World!‘) hello() 2、带参数的装饰器 from functools import wraps def outer(k=None): def wrapper(func): @wraps(func) def inner(*args, **kwargs): if k == ‘start‘: print(‘节目开始‘) r = func(*args, **kwargs) return r else: print(‘节目还未开始‘) return inner return wrapper @outer(‘start‘) def hello(): """这里是hello函数""" print(‘Hello World!‘) hello() print(hello.__doc__) print(hello.__name__) 3、多个装饰器同时装饰一个函数 """ 给Hello World!包两层标签, <div><p>Hello World!</p></div> """ from functools import wraps def wrapper1(func): # 包p标签 @wraps(func) def inner1(*args, **kwargs): r = func(*args, **kwargs) return ‘<p>{}</p>‘.format(r) return inner1 def wrapper2(func): # 包div标签 @wraps(func) def inner2(*args, **kwargs): r = func(*args, **kwargs) return ‘<div>{}</div>‘.format(r) return inner2 @wrapper2 @wrapper1 def hello(): return "Hello World!" print(hello())