Python--深入理解Python特性 第3章
第3章 高效的函数
3.1 函数是Python的头等对象
Python中一切皆对象,函数也不例外。函数可以分配给变量或存储在数据结构中,还可以传递给其他函数或作为其他函数的返回值。
函数可以嵌套,并且可以捕获并携带父函数的一些状态。具有这种行为的函数称作闭包。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 工厂函数 def make_adder(n): def add(x): # 这里引用了父函数的n,这种情况称为词法闭包(lexical closure) return x + n return add plus_3 = make_adder(3) plus_5 = make_adder(5) print(plus_3(1)) print(plus_5(1))
对象可以设置为可调用的,因此很多情况下可以将对象作为函数对待。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Adder: def __init__(self, n): self.n = n def __call__(self, x): # 实现此方法,则类的实列对象可当中函数调用 return self.n + x plus_3 = Adder(3) plus_5 = Adder(5) # plus_3和plus_5实际是Adder的两个实例对象, # 这里直接把它们当作函数调用,是因为Adder实现了__call__方法 print(plus_3(1)) print(plus_5(1))
3.2 lambda是单表达式函数
lambda函数不必与名称绑定,因此常作为匿名函数。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 工厂函数 def make_adder(n): # 使用lambda匿名函数形成了一个闭包 return lambda x: x + n plus_3 = make_adder(3) plus_5 = make_adder(5) print(plus_3(1)) print(plus_5(1))
lambda函数不能使用普通的Python语句,因为lambda会计算其唯一的表达式,并隐式的把计算结果返回。
不要过度使用lambda函数,使用前请先问问自己:使用普通具名函数或者列表解析式是否更加清晰。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 错误的案例 class Car: # rev和crash应该使用常规的函数定义方法def来声明函数, # 那样会更清晰 rev = lambda self: print('Wroom!') crash = lambda self: print('Boom!') car = Car() car.crash() # 错误的案例 l_list = list(filter(lambda x: x % 2 == 0, range(16))) # 正确的案例 l_list = [x for x in range(16) if x % 2 == 0] # 注:尽可能在表达式清晰简单的时候才使用lambda函数 # 少敲一些代码并不重要,重要的是能够让代码清晰可读
3.3 装饰器的力量
装饰器用于定义可重用的组件,可以将其应用于可调用对象以修改其行为,同时无须永久修改可调用对象本身。
@语法只是在输入函数上调用装饰器的简写,不过@语法会在定义时就立即修饰该函数。在单个函数上应用多个装饰器的顺序是从底部到顶部(装饰器栈)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def decorator(func): print(f'func:{func.__name__}被装饰了') return func # 方式1 # def greet(): # print('in greet') # return 'Hello' # 方式2 使用@进行装饰 @decorator def greet(): print('in greet') return 'Hello' print('开始装饰') # 方式1 直接调用装饰函数 # greet = decorator(greet) greet() ''' 方式1 输出结果 开始装饰 func:greet被装饰了 in greet ''' # 使用@进行装饰时,在定义的时候就会立即调用 # 方式1可以把装饰结果赋值给其他变量,这样就可以保留原函数的原滋原味 ''' 方式2 输出结果 func:greet被装饰了 开始装饰 in greet '''
最好在自己的装饰器中使用functools.wraps将被装饰对象中的元数据转移到装饰后的对象中。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import functools def upper_decorator(func): # 使用functools.wraps可把func的元数据移到wrapper中 # @functools.wraps(func) # 1 def wrapper(): return func().upper() return wrapper @upper_decorator def greet(): """这是greet的注释""" return 'Hello' print(greet.__name__) print(greet.__doc__) ''' 注释1处代码 输出结果 wrapper None ''' ''' 不注释1处代码 输出结果 greet 这是greet的注释 '''
使用*和** 参数解包操作符完成带参函数的装饰
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def trace(func): def wrapper(*args, **kwargs): print(f'Trace: calling {func.__name__}() ' f'with {args}, {kwargs}') original_result = func(*args, **kwargs) print(f'Trace: {func.__name__}() ' f'returned {original_result}') return original_result return wrapper @trace def say(name, line): return f'{name}: {line}' result = say('Jane', line='Welcome') print(result) ''' 输出结果 Trace: calling say() with ('Jane',), {'line': 'Welcome'} Trace: say() returned Jane: Welcome Jane: Welcome '''
装饰器不是万能的,容易产生可怕且不可维护的代码,不应过度使用。
3.4 有趣的*args和**kwargs
*args和**kwargs 用于在Python中编写变长参数的函数
*args收集额外的位置参数组成元组。**kwargs收集额外的关键字参数组成字典。
实际起作用的语法是*和**,args和kwargs只是命名约定,也可以使用其他名称。建议遵循命名约定。
3.5 函数参数解包
*可用于将元组、列表和生成器等序列解包为位置参数。
如果使用*解包字典,则所有的键将以随机的顺序传递给函数。(我试了下,按key定义顺序输出,不是随机,用的python3.6,不知道是不是跟python版本有关)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
dic_t = {'b': 1, 'a': 2, 'c': 3} print(*dic_t) ''' 输出结果 b a c '''
**可用于将字典解包为关键字参数。
3.6 返回空值
如果函数没有明确的return,则函数返回None。
return、return None 或者不写return语句效果是一样的,返回的都是None。
如果函数本身确实不用返回值,可以不显示的写return语句。
如果函数有返回值,某些情况返回None,这种情况下,建议最后都显示的return None,可以使代码意图更明确。