0. 前言
- 参考资料:
- 《流畅的Python》第五、六、七章
- 《Python Cookbook》第七、九章
- 《深入理解Python特性》第三章
- TODO(目前不太关心的):
- 标准库里有很多内置的装饰器。
- 闭包没有具体展开。
1. 函数
1.1. 基本概念
- (编程语言中的)一等对象:运行时创建、能赋值给变量或数据结构中的元素、能作为参数传给函数、能作为函数返回结果。
- 函数是一等对象。
- 高阶函数:接受函数作为参数,或把函数作为结果返回的函数。
- 函数对象及其名称是相互独立的实体,换句话说,指向函数的变量和函数本身实际上是彼此独立的。
- 闭包:
- 即词法闭包,在程序流不在闭包范围内的情况下,也能记住封闭作用域中的值。
- 内部函数不仅可以从父函数返回,还可以捕获并携带父函数的某些状态。
- 普通对象也可以作为函数使用,即有些对象不是函数,也可以调用。
- 实现方式:实现
__call__
方法。 - 其他:Python内置函数
callable
用于检查一个对象是否可以调用。 - 可调用函数包括:用于定义的函数、内置函数、内置方法、方法、类、类的实例、生成器函数。
- 实现方式:实现
1.2. lambda 表达式
- 作用:快速声明小型匿名函数,根据功能也有人称之为单表达式函数。
- 匿名函数:猜测就是没有函数名的函数。
- 与匿名函数相对的是具名函数。
- lambda只是语法糖。
- 实例:
(lambda x, y: x+y)(5, 3)
- 使用场景:
- 可迭代对象进行排序时,使用lambda表达式定义简短的key函数,如
sorted(ruples, key=lambda x: x[1])
- 其他好像推荐用得比较少。
- 可迭代对象进行排序时,使用lambda表达式定义简短的key函数,如
- 注意事项:
- 不应过度使用 lambda。
- 使用前总是先问问自己:使用普通具名函数或列表解析式是否更加清晰。
- 在匿名函数中绑定变量的值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# 如果直接使用,则不会记住变量的值,而是跟变量一起变化
x = 10
a = labmda y: x + y
x = 20
b = lambda y: x + y
a(10) # 30
b(10) # 30
# 可以设置默认参数来实现
x = 10
a = labmda y, x=x: x + y
x = 20
b = lambda y, x=x: x + y
a(10) # 20
b(10) # 30
1.3. * 和 **
- 作为函数参数时,经常使用
*args
和**kwargs
这两种形式,功能是让函数接受可选参数,使得代码更灵活。- args和kwargs只是约定俗成的名称,可以修改。
*args
获取的对象是元组,**kwargs
获取的对象是字典。
- 可用于解包
*
可用于解包序列。**
可用于解包字典。- 高效参数解包有助于模块和函数编写更灵活。
- 以
*
打头的参数只能作为最后一个位置参数,但可以存在其他参数;以**
打头的参数只能作为最后一个参数出现。def f(x, *args, y)
是允许的。
- 单独的星号可以作为分隔符,用于分隔位置参数与命名关键字参数。
- 星号本身不是参数。
1.4. 变量作用域
- 从作用域角度看,变量分为局部变量和全局变量。
- 函数内部出现的变量默认为局部变量。
- 如果同名局部变量不存在,且存在同名全局变量,也不会直接调用全局变量。
- 如果想在局部作用域中使用全局变量,必须在局部作用域中先使用
global
关键字声明全局变量1
2
3
4
5
6
7
8
9a = 1
b = 2
def f():
c = 3
global b
print(a) # 报错,无法直接获取全局变量
print(b) # 正常,因为已经在局部作用域中声明了全局变量
print(c) # 正常,直接调用局部变量
2. 装饰器
2.1. 基本概念
- 作用:
- 用来临时扩展和修改可调用对象(函数、方法、类)的行为,同时又不会永久修改可调用对象本身。
- 将通用功能应用到现有的类或函数的行为上。
- 应用举例:日志、访问控制与授权、计算执行时间、限制请求速率、缓存。
- 本质:
- 定义内部函数,扩充原函数的功能,并替代原函数。
- 装饰器就是语法糖,简化上述过程。
- 多装饰器修饰应用顺序从下到上,可称为装饰器栈。
2.2. 基本形式举例
2.2.1 形式一:最简单的装饰器,不实现任何功能。
- 该装饰器不能实现任何功能,只是用于帮助理解装饰器本质。
- 从该实例可以看出,装饰器其实就是语法糖。
1
2
3
4
5
6
7
8
9
10
11def null_decorator(func):
return func
def greet():
return 'Hello!'
# 上面这种装饰器语法等价于下面的方法
def greet():
return 'Hello!'
greet = null_decorator(greet)
2.2.2. 形式二:装饰器的基本形式。
- 实际起作用的装饰器形式如下。
- 从该实例中可以参数,基本装饰器就是定义内部函数,通过内部函数来扩充源函数的功能。
- 通常,装饰器会把函数替换成另一个函数。
1
2
3
4
5
6
7
8
9
10def uppercase(func):
def wrapper():
original_result = func()
modified_result = original_result.upper()
return modified_result
return wrapper
def greet():
return 'Hello!'
2.2.3. 形式三:参数化装饰器。
- 直观的看,所谓参数化装饰器,其实就是定义了内部函数的内部函数。
- 另外一种理解参数化装饰器,其实就是构建一个装饰器工厂函数,把参数传给它,返回一个装饰器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22registry = set()
def register(active=True):
def decorate(func):
print('running register(active=%s)->decorate(%s)' % (active, func))
if active:
registry.add(func)
else:
registry.discard(func)
return func
return decorate
def f1():
print('running f1()')
def f2():
print('running f2()')
def f3():
print('running f3()')
2.2.4. 形式四:多种形式装饰器。
- 编写一个单独的装饰器,既可以使用
@decorator
,也可以使用@decorator(x, y, z)
1
2
3
4def logged(func=None, *, level=logging.DEBUG, name=None, message=None):
if func is None:
return partial(logged, level=level, name=name, message=message)
...
2.2.5. 形式五:类中定义装饰器。
- 其实
@property
也是使用类实现的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25class A:
def decorator1(self, func):
def wrapper(*args, **kwargs):
print('Decorator 1')
return func(*args, **kwargs)
return wrapper
def decorator2(cls, func):
def wrapper(*args, **kwargs):
print('Decorator 2')
return func(*args, **kwargs)
return wrapper
a = A()
def greet():
return 'Hello!'
def spam():
pass
2.3. 装饰器执行时间
- 装饰器的一个关键特性,是在被装饰的函数定义之后立即执行,通常是在导入时。
- 换个说法:函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行。
- 这突出了Python程序员所说的导入时和运行时之间的区别。
- 具体查看以下实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23def register(func):
print('running register(%s)' % func)
return func
def f1():
print('running f1()')
def f2():
print('running f2()')
def main():
print('running main()')
f1()
f2()
# 输出结果
# running register(<function f1 at xxx>)
# running register(<function f2 at xxx>)
# running main()
# running f1()
# running f2()
2.5. 无法调试(无法获取元数据)
- 如果使用最朴素的方法定义装饰器,可能会导致函数无法调试,即函数的元数据(如文档、原函数名称、参数列表)被隐藏。
- 解决方案:使用
functools.wraps
装饰器,如下例子。1
2
3
4
5def uppercase(func):
def wrapper():
return func().upper()
return wrapper
- 解决方案:使用