函数名的使用
函数名是一个变量,但它是⼀个特殊的变量,与括号配合可以执行函数。
1 2 3 4 5
| def func(): print('呵呵')
print(func)
|
一、函数名可以作为变量来使用
1 2 3 4 5
| def func(): print('呵呵')
a = func a()
|
二、作为容器的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| def func1(): print('func1') def func2(): print('func2') def func3(): print('func3') def func4(): print('func4')
lst = [func1, func2, func3, func4] for i in lst: i()
lst2 = [func1(), func2(), func3(), func4()] print("lst2的结果:", lst2)
""" func1 func2 func3 func4 lst2的结果: [None, None, None, None] """
|
三、作为参数使用
1 2 3 4 5 6 7 8 9
| def func1(): print('func1')
def func2(arg): print('start') arg() print('end')
func2(func1)
|
四、作为返回值使用
1 2 3 4 5 6 7 8 9
| def func1(): print('这里是func1')
def func2(): print('这里是func2') return func2
ret = func1() ret()
|
闭包
一、闭包的概念
一个内层函数中,引用了外层函数(非全局)的变量,这个内层函数就可以成为闭包。
1 2 3 4 5 6 7 8
| def func(): name = "cdc" def inner(): print(name) return inner ret = func() ret()
|
二、闭包的作用
2.1 保护变量,防止变量被修改
将变量定义在全局是十分不安全的,在函数内部可以通过 global 关键字随意修改全局变量的值
1 2 3 4 5 6 7 8 9 10 11 12 13
| name = "cdc"
def func1(): global name name = "tr" def func2(): if name == "cdc": print("OK") else: print("NO")
|
闭包是将变量定义在外层函数内部,即便想要修改变量值,也只能使用 nonlocal 关键字在当前作用域内进行修改
1 2 3 4 5 6 7
| def func1(): name = "cdc" def inner(): nonlocal name name = "tr" return inner
|
2.2 让变量在内存中常驻
对于一般的函数而言,在声明函数时只是将函数名放入命名空间,当调用函数时,才为函数内部的变量或者嵌套函数开辟新的内存,当函数调用结束后,函数内部的变量就会释放内存;当下一次 调用函数时,再重新为函数内部变量开辟新的内存。因此,重复调用复杂的函数时,会消耗一定的时间。
对于 闭包函数而言,内层函数需要调用外层函数的变量,一旦外层函数的变量被释放,内层函数将无法执行,因此为了能让内层函数正常运行,python解释器会让外层的变量常驻在内存中。
1 2 3 4 5 6 7 8
| def func1(): name = "cdc" def inner(): print(name) return inner
ret = func1() ret()
|
上述示例中,当 func1 函数执行结束后,理论上 name 变量将被释放,但是一旦 name 被释放,内层的 inner 将无法正常执行,因此 python 解释器会将 name 变量常驻内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| from urllib.request import urlopen
def craw_web(): content = urlopen("http://www.xiaohua100.cn/index.html").read() def inner(): return content return inner
fn = craw_web()
content = fn() print(content)
content2 = fn() print(content2)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| """ 小练习——编写代码实现func函数,使其实现以下效果: foo = func(8) print(foo(8)) # 输出64 print(foo(-1)) # 输出-8 """
def func(a): def inner(b): return a * b return inner
foo = func(8) print(foo(-1)) print(foo(8))
|
可以使用__closure__来检测一个函数是否闭包,若有返回值,则代表该函数是闭包,若返回None,则该函数不是闭包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| def func1(): name = "cdc" def inner1(): print(name) print(inner1.__closure__) return inner1
ret1 = func1() ret1()
def func2(): name = "cdc" def inner2(): print("哈哈") print(inner2.__closure__) return inner2
ret2 = func2() ret2()
|
装饰器
一、软件设计的原则
开放封闭原则(开闭原则):开放封闭原则是指对扩展代码的功能是开放的,但是对修改源代码是封闭的。这样的软件设计思路可以保证我们更好的开发和维护我们的代码。
二、装饰器原理
本质上就是一个函数,可用于在不改变函数的调用方式以及源码的基础上,为函数增加新的功能。
我们先来写一个例子,模拟一下约妹子出来吃饭:
1 2 3 4
| def yue(): print("给妹子发消息,约妹子出来吃饭") yue()
|
妹子的联系途径多种多样,我们可以通过微信来联系妹子:
1 2 3 4 5
| def yue(): print("打开微信") print("给妹子发消息,约妹子出来吃饭") yue()
|
显然,我们已经违背了开闭原则,对原来的函数源码进行了修改。况且社交工具多种多样,想要通过不同的途径来联系妹子,每一次都需要修改源码,十分的麻烦,因此我们可以再写一个函数:
1 2 3 4 5 6 7 8
| def yue(): print("给妹子发消息,约妹子出来吃饭")
def with_wechat(): print("打开微信") yue() with_wchat()
|
虽然我们避免了修改源码,但是当换一种社交工具时,又得再写新的函数。总结一句话就是如何在不改变函数的结构和调用方式的基础上,动态的给函数添加功能?可以用闭包的方法尝试一下:
1 2 3 4 5 6 7 8 9 10 11
| def yue(): print("给妹子发消息,约妹子出来吃饭")
def with_tools(func): def inner(): print(f"打开微信") func() return inner ret1 = with_tools(yue) ret1()
|
闭包虽然为改变原函数的结构,但还是改变了原来的调用方式,我们再稍作改动
1 2 3 4 5 6 7 8 9 10 11
| def yue(): print("给妹子发消息,约妹子出来吃饭")
def with_tools(func): def inner(): print(f"打开微信") func() return inner yue = with_tools(yue) yue()
|
将 with_tools 的赋值给一个变量 yue,再调用 yue 就恢复到了我们最开始的调用方式了,一切问题完美解决,其实这就是装饰器的一个雏形。看一下它的执行过程吧:
- 首先访问with_tools(“微信”, yue)
- 把yue函数赋值给了with_tools函数的形参func,记住后续执行func的话实际上是执行了最开始传入的yue函数。
- with_tools函数执行过程就是一句话,返回了inner函数。这个时候把inner函数赋值给了yue这个变量
- 执行yue的时候,相当于执行了inner函数,先打印打开微信再执行func,也就是我们最开始传入的yue函数
Python 中针对于上面的功能提供了一个快捷的写法,俗称装饰器语法糖。使用装饰器语法糖的写法,实现同样功能的代码如下:
1 2 3 4 5 6 7 8 9 10 11
| def with_tools(func): def inner(): print(f"打开微信") func() return inner @with_tools def yue(): print("给妹子发消息,约妹子出来吃饭") yue()
|
三、装饰器的使用
3.1 装饰有返回值的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| def func(fn): def inner(): print("我是cdc") ret = fn() print("再见") return ret return inner
@func def say_hello(): return "你好"
res = say_hello() print(res)
|
3.2 装饰带参数的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| def foo(func): def inner(x, y): print("这里是新功能...") func(x, y) return inner
@foo def f1(x, y): print("{}+{}={}".format(x, y, x+y))
f1(100, 200)
def foo(func): def inner(*args, **kwargs): print("这里是新功能...") func(*args, **kwargs) return inner
|
3.3 装饰器自身带参数
被装饰的函数可以带参数,装饰器同样也可以带参数。如果想让装饰器也带上参数,就必须在原来的闭包函数最外层再套一层函数,该函数就用于接收装饰器带来的参数,供内层函数使用。
1 2 3 4 5 6 7 8
| def d(name): def f1(func): def f2(*arg, **kwargs): print(name) func(*arg, **kwargs) return f2 return f1
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| def with_tools(tool): def inner1(func): def inner2(*args, **kwargs): print(f"打开{tool}") func(*args, **kwargs) return inner2 return inner1 @with_tools("微信") def yue(): print("给妹子发消息,约妹子出来吃饭")
yue()
|
3.4 最完整的装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13
| def func(a = None): def inner1(fn): def inner2(*args, **kwargs): print("新功能") ret = fn(*args, **kwargs) print("新功能") return ret return inner2 return inner1 @func() def foo(): pass
|
3.5 装饰器的修复技术
被装饰的函数最终都会失去本来的__doc__等信息, Python给我们提供了一个修复被装饰函数的工具。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| def with_tools(func): @wraps(func) def inner(): print(f"打开微信") func() return inner @with_tools def yue(): print("给妹子发消息,约妹子出来吃饭") yue() print(yue.__doc__) print(yue.__name__)
|