
一、函数名的应用
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())
