>>> def factorial(n):
... """return n!"""
... return 1 if n < 2 else n * factorial(n - 1)
...
>>> factorial(42)
1405006117752879898543142606244511569936384000000000
>>> factorial.__doc__
'return n!'
>>> type(factorial)
>>> fact = factorial
>>> fact
>>> fact(5)
120
>>> list(map(fact, range(10)))
[1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880]
有了一等函数,就可以使用函数式风格编程。函数式编程的特点之一是使用高阶函数
接受函数为参数,或者把函数作为结果返回的函数是高阶函数(higher-order function),map 函数就是一例
按单词长度排序:
>>> fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
>>> sorted(fruits, key=len)
['fig', 'apple', 'cherry', 'banana', 'raspberry', 'strawberry']
按韵脚排序:
>>> def reverse(word):
... return word[::-1]
...
>>> sorted(fruits, key=reverse)
['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']
>>> sorted(fruits, key=lambda x: x[::-1])
['banana', 'apple', 'fig', 'raspberry', 'strawberry', 'cherry']
lambda 句法只是语法糖:与 def 语句一样,lambda 表达式会创建函数对象。这是 Python 中几种可调用对象的一种
python 定义了 7 中可调用对象:
不仅 Python 函数是真正的对象,任何 Python 对象都可以表现得像函数。为此,只需实现实例方法 call
class BingoCage:
def __init__(self, item):
self._item = list(item)
shuffle(self._item)
def pick(self):
try:
return self._item.pop()
except IndexError:
raise LookupError('pick from empty BingoCage')
def __call__(self):
return self.pick()
>>> b1 = BingoCage(range(3))
>>> b1.pick()
2
>>> b1()
3
>>> callable(b1)
True
列出常规对象没有而函数有的属性:
注解:为函数声明中的参数和返回值附加元数据
def clip(text:str, max_len:'int > 0'=80) -> str:
operator 模块为多个算术运算符提供了对应的函数,从而避免编写 lambda a, b: a*b
这种平凡的匿名函数
from functools import reduce
from operator import mul
def fact(n):
return reduce(mul, range(1, n + 1))
if __name__ == '__main__':
print(fact(5))
itemgetter 的常见用途:根据元组的某个字段给元组列表排序
from collections import namedtuple
Customer = namedtuple('Customer', 'name fidelity')
class LineItem:
def __init__(self, product, quantity, price):
self.product = product
self.quantity = quantity
self.price = price
def total(self):
return self.price * self.quantity
class Order: # 上下文
def __init__(self, customer, cart, promotion=None):
self.customer = customer
self.cart = list(cart)
self.promotion = promotion # function: dynamic bind
def total(self):
if not hasattr(self, '__total'):
self.__total = sum(item.total() for item in self.cart)
return self.__total
def due(self):
if self.promotion is None:
discount = 0
else:
discount = self.promotion(self)
return self.total() - discount
def __repr__(self):
fmt = ''
return fmt.format(self.total(), self.due())
def fidelity_promo(order):
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
def bulk_item_promo(order):
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * .1
return discount
def large_order_promo(order):
distinct_items = {item.product for item in order.cart}
if len(distinct_items) >= 10:
return order.total() * .07
return 0
if __name__ == '__main__':
joe = Customer('John Doe', 0)
ann = Customer('Ann Smith', 1100)
cart = [LineItem('banana', 4, .5),
LineItem('apple', 10, 1.5),
LineItem('watermelon', 5, 5.0)]
# 为了把折扣策略应用到 Order 实例上,只需把促销函数作为参数传入
Order(joe, cart, fidelity_promo)
使用 globals() 获取全局符号表:
promos = [globals()[name] for name in globals() if name.endwith('_promo') and name != 'best_promo']
def best_promo(order):
return max(promo(order) for promo in promos)
或者内省 promotions 模块,构建策略函数列表:
promos = [func for name, func in inspect.getmemebers(promotions, inspect.isfunction)]
“命令”设计模式也可以通过把函数作为参数传递而简化
函数装饰器用于在源码中“标记”函数,以某种方式增强函数的行为
def deco(fn):
def inner():
print("inner")
return inner
@deco
def foo():
print("foo")
if __name__ == '__main__':
foo() # "inner"
registry = []
def register(fn):
print(f'running register({fn})')
registry.append(fn)
return fn
@register
def f1():
print('running f1()')
@register
def f2():
print('running f2()')
def f3():
print('running f3()')
if __name__ == '__main__':
print('running main')
print('registry -> ', registry)
f1()
f2()
f3()
running register()
running register()
running main
registry -> [, ]
running f1()
running f2()
running f3()
Process finished with exit code 0
register 在模块中其他函数之前运行(两次)
Python 编译函数的定义体时,它判断 b 是局部变量,因为在函数中给它赋值了
Python 不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量
闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。函数是不是匿名的没有关系,关键是它能访问定义体之外定义的非全局变量
class Averager:
def __init__(self):
self.series = []
def __call__(self, new_value):
self.series.append(new_value)
total = sum(self.series)
return total / len(self.series)
def make_averager():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total / len(series)
return averager
调用 make_averager 时,返回一个 averager 函数对象。每次调用 averager 时,它会把参数添加到系列值中,然后计算当前平均值
综上,闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定
def make_averager():
count = 0
total = 0
def averager(new_value):
nonlocal count, total
count += 1
total += new_value
return total / count
return averager
下面定义了一个装饰器,它会在每次调用被装饰的函数时计时,然后把经过的时间、传入的参数和调用的结果打印出来:
import time
def clock(func):
def clocked(*args):
t0 = time.perf_counter()
result = func(*args) # func is a freevar
elapsed = time.perf_counter() - t0
name = func.__name__
arg_str = ', '.join(repr(arg) for arg in args)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
return clocked
@clock
def snooze(seconds):
time.sleep(seconds)
@clock
def factorial(n):
return n if n < 2 else n * factorial(n - 1)
if __name__ == '__main__':
print('*' * 40, 'Calling snooze(.123)')
snooze(.123)
print('*' * 40, 'Calling factorial(6)')
print('6! =', factorial(6))
pass
**************************************** Calling snooze(.123)
[0.12306906s] snooze(0.123) -> None
**************************************** Calling factorial(6)
[0.00000069s] factorial(1) -> 1
[0.00001145s] factorial(2) -> 2
[0.00001898s] factorial(3) -> 6
[0.00002650s] factorial(4) -> 24
[0.00003265s] factorial(5) -> 120
[0.00004110s] factorial(6) -> 720
6! = 720
设计模式中定义的装饰器模式:动态地给一个对象添加一些额外的职责。这与函数装饰器是相同的
改进后的装饰器:
def clock(func):
@functools.wraps(func)
def clocked(*args, **kwargs):
t0 = time.time()
result = func(*args, **kwargs) # func is a freevar
elapsed = time.time() - t0
name = func.__name__
arg_lst = []
if args:
arg_lst.append(', '.join(repr(arg) for arg in args))
if kwargs:
pairs = [f'{k}={w}' for k, w in sorted(kwargs.items())]
arg_lst.append(', '.join(pairs))
arg_str = ', '.join(arg_lst)
print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
return result
return clocked
@clock
def fibonacci(n):
return n if n < 2 else fibonacci(n - 2) + fibonacci(n - 1)
进行了大量重复运算:
[0.00000030s] fibonacci(0) -> 0
[0.00000040s] fibonacci(1) -> 1
[0.00004730s] fibonacci(2) -> 1
[0.00000030s] fibonacci(1) -> 1
[0.00000040s] fibonacci(0) -> 0
[0.00000030s] fibonacci(1) -> 1
[0.00002010s] fibonacci(2) -> 1
[0.00003850s] fibonacci(3) -> 2
[0.00010310s] fibonacci(4) -> 3
[0.00000020s] fibonacci(1) -> 1
[0.00000020s] fibonacci(0) -> 0
[0.00000020s] fibonacci(1) -> 1
[0.00001670s] fibonacci(2) -> 1
[0.00003420s] fibonacci(3) -> 2
[0.00000010s] fibonacci(0) -> 0
[0.00000030s] fibonacci(1) -> 1
[0.00001650s] fibonacci(2) -> 1
[0.00000020s] fibonacci(1) -> 1
[0.00000020s] fibonacci(0) -> 0
[0.00000020s] fibonacci(1) -> 1
[0.00001700s] fibonacci(2) -> 1
[0.00003310s] fibonacci(3) -> 2
[0.00006550s] fibonacci(4) -> 3
[0.00011620s] fibonacci(5) -> 5
[0.00023630s] fibonacci(6) -> 8
使用 lru_cache 改善性能后:
@functools.lru_cache() # () 接受参数,functools.lru_cache(maxsize=128, typed=False)
@clock
def fibonacci(n):
return n if n < 2 else fibonacci(n - 2) + fibonacci(n - 1)
[0.00000030s] fibonacci(0) -> 0
[0.00000040s] fibonacci(1) -> 1
[0.00004980s] fibonacci(2) -> 1
[0.00000050s] fibonacci(3) -> 2
[0.00006920s] fibonacci(4) -> 3
[0.00000050s] fibonacci(5) -> 5
[0.00008840s] fibonacci(6) -> 8
8
因为 Python 不支持重载方法或函数,使用 @singledispatch 装饰的普通函数会变成泛函数(generic function):根据第一个参数的类型,以不同方式执行相同操作的一组函数
import time
from functools import singledispatch
from collections import abc
import numbers
import html
@singledispatch
def htmlize(obj):
content = html.escape(repr(obj))
return f'{content}
'
@htmlize.register(str)
def _(text):
content = html.escape(text).replace('\n', '
\n')
return '{0}
'.format(content)
@htmlize.register(numbers.Integral)
def _(n):
return '{0} (0x{0:x})
'.format(n)
@htmlize.register(tuple)
@htmlize.register(abc.MutableSequence)
def _(seq):
inner = '\n
'.join(htmlize(item) for item in seq)
return '\n- ' + inner + '
\n
'
装饰器是函数,因此可以组合起来使用
解析源码中的装饰器时,Python 把被装饰的函数作为第一个参数传给装饰器函数。那怎么让装饰器接受其他参数呢?答案是:创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上。
registry = set()
def register(active=True):
def decorate(func):
print(f'running register(active={active}) -> decorate({func})')
if active:
registry.add(func)
else:
registry.discard(func)
return func
return decorate
@register(active=False)
def f1():
print('running f1()')
@register()
def f2():
print('running f2()')
def f3():
print('running f3()')
if __name__ == '__main__':
print(registry)
pass
register() 要返回 decorate,然后把它应用到被装饰的函数上