Functional Programming in Python
文章目录
工具
工具列表
- itertools
其他内置函数
- any, all
- zip
- callable :: 判断是否可作为函数使用
- more_itertools
funcy
pipe
- GitHub - JulienPalard/Pipe: A Python library to use infix notation in Python
- 使用 Pipe(your_fun) 装换成可以通过
|调用的函数
whatever
- 构建 lambda 函数快捷工具
- GitHub - Suor/whatever: Easy anonymous functions by partial application of op…
- 类似工具 fn.py 中的 underscore _
pyrsistent (persistent)
- GitHub - tobgu/pyrsistent: Persistent/Immutable/Functional data structures fo…
- immutable data structure for functional programming
lambda 函数定义工具(underscore _)
whatever
fn.py underscore
lambdas
zip 特点
结果长度和短的 sequence 一致
1 2In [60]: list(zip([1,2,3], [2,3])) Out[60]: [(1, 2), (2, 3)]如何和最长的 sequence 一致, itertools.zip_longest
1 2In [72]: list(itertools.zip_longest([1,2,3], [2,3])) Out[72]: [(1, 2), (2, 3), (3, None)]
itertools
参考:
- 详细笔记:itertools notes
简介
- tee
- 复制 iterator
- islice
- 切边工具 slice tool, 类似 list 一样来 slice 切片 iterator
- pairwise
- 两两配对工具
- count
- 类似
start::step, 给的 start 和 step,无限增长的 iterator,
takewile 和 dropwhile 辨析
- 它们针对的是从头开始的逐个元素
takewhile, 如果 true, 保留; 如果 false, 停止保留
- 所以结果是开头的部分
dropwhile, 如果 true, 丢弃; 如果 false, 停止丢弃
- 所以结果是结尾的部分
| |
pipe 串联调用函数
工具:
funcy_pipe
pipe
用法:
| |
funcy
注意
sequence Vs. collection
- funcy 中面向
sequence的函数可以针对 generator 使用 - 面向
collection的对象,不能对 generator 使用 举例:
过滤工具: funcy.filter 和 funcy.select 不同, funcy.select 只能用于 list, set 等 collection
1 2 3 4 5In [158]: range(10) | fp.to_list| fp.select(X > 3) | fp.to_list Out[158]: [4, 5, 6, 7, 8, 9] In [159]: range(10) | fp.filter(X > 3) | fp.to_list Out[159]: [4, 5, 6, 7, 8, 9]
- funcy 中面向
函数工具(高阶函数)
参考:
以下函数都是接受函数作为参数,返回一个新的函数
- identity
- 返回参数本身
- constantly
- 永远返回 constantly 存储的参数
- caller
- 接收函数 f 作为参数,f (*args, **kwargs), 这里 args 和 kwargs 是 caller 存储的参数, 可以用于同样的参数,应用到不同的函数的使用案例
- juxt
- juxtapositon 把提供的函数
并列执行 - autocurry
- 自动 curry
- iffy
- 不确定函数,判断合格(predicate),执行 action, 否则执行 default
逻辑函数
- complement
- not predicate, 返回补集判断函数
- all_fn(*fs)
满足所有 fs 的 predicat 判断
- 类似函数 any_fn, none_fn, one_fn
- 注意:这些函数是
短路执行的,例如 all_fn, 有一个是 False 停止执行
- some_fn(*fs)
并列执行,第一个 True 的返回
- 注意:它不是短路执行的,而是
并列执行
- 注意:它不是短路执行的,而是
decorator 工具
可以通过一个普通的函数来创建一个高阶装饰器
参考:
例子:
| |
解释:
- 这里的 call 是一个 funcy.decorators.Call 对象
- Call._func 字段 :: 被装饰的原始函数
- Call._args 字段 ::
| |
flow, error 流程和异常处理
错误处理
工具:
异常捕获
- slient
- 错误返回 None, 忽略所有异常
- ignore
- 捕获指定异常,可以指定异常发生时的 default 值
- suppress
- 压制给定的 errors 异常
- nullcontext
- 啥也不干的占位符 content manager
重试
- retry
- 重试给定次数,捕获异常,设置 timeout
异常抛出
- raiser
- 抛出给定的异常
- reraiser
- 捕获一种异常,以指定的新异常抛出
异常发生,换一种处理方法
- fallback(*approaches)
依次尝试给定的 approaches, 如果发生异常
- approach 可以是 callable, 或者 (callable, Exception 子类)
- 全部失败,返回
None 一种实现方法:最后一个函数,抛出异常,即使用 raiser
1 2 3 4 5fallback( (partial(send_mail, ADMIN_EMAIL, message), SMTPException), partial(log.error, message), # Handle any Exception (raiser(FeedbackError, "Failed"), ()) # Handle nothing, 抛出异常 )
异常速率限定
- limit_error_rate(fails: int, timeout: int | timedelta)
重复调用次数和超额 timeout 限制 decorator
- 如果重复调用次数超额 (fails 限制), 触发计时,只有计时达到 timeout, 才可以再次执行
- 计时达到 timeout 之前执行,抛出
ErrorRateExceeded异常
执行次数
根据设定条件只执行函数一次,适合初始化的使用案例。
- once
- 无论函数参数如何变化,都只执行一次
- once_per_args
- 每种参数组合,只执行一次
- once_per('arg_names')
- 给的的参数名组合,只执行一次
| |
重试 retry
| |
执行时间限制
- throttle(period: int | timedelta)
只有给定时间段可以执行
- 第一次执行 now, 到 now + period 之间可以执行
- 超时( > now + period)禁止执行
genator 结果收集
- collecting
装饰器,收集 generator 的结果到一个 list
1 2 3 4 5 6 7In [2]: @funcy.collecting ...: def gen(): ...: for i in range(3): ...: yield i In [3]: gen() Out[3]: [0, 1, 2]- joining
str.join, bytes.join, 把 generator 结果 join 起来
1 2 3 4 5 6 7In [4]: @funcy.joining(' ') ...: def gen(): ...: for i in range(3): ...: yield i In [5]: gen() Out[5]: '0 1 2'- post_processing(fun)
把 fun 应用到 generator 的所有结果
1 2 3 4 5 6 7 8In [8]: @funcy.post_processing(list) ...: def gen(): ...: for i in range(3): ...: yield i ...: In [9]: gen() Out[9]: [0, 1, 2]- 相当于
list(gen())
- 相当于
应用 context manager
- wrap_with(context_manager)
- 装饰器,调用函数前,添加 context_manager 的 with 语句
funcy 使用案例
应用普通函数到 iterator 上的每一个元素
不同的解决方法:
装换成 list, 再使用 collection 系列工具,如 select select_value …
1 2In [348]: range(3) | fp.to_list | fp.select(lambda x: (x**2, x)) Out[348]: [0, 1, 2]使用 funcy.map
1 2In [350]: range(3) | fp.map(lambda x: (x**2, x)) | fp.to_list Out[350]: [(0, 0), (1, 1), (4, 2)]
应用多个普通函数到 iterator 上的每一个元素
使用 funcy.map + lambda x: [fun1(x), fun2(x), …]
1 2In [104]: range(3) | fp.map(lambda x: (x, x**2, x**3)) | fp.to_list Out[104]: [(0, 0, 0), (1, 1, 1), (2, 4, 8)]注意: 避免使用 funcy.juxt, 太麻烦
1 2 3 4from lambdas import _ as X In [102]: range(3) | fp.map(funcy.juxt(X, X**2, X**3)) | fp.map(list) | fp.to_list Out[102]: [[0, 0, 0], [1, 1, 1], [2, 4, 8]]
funcy_pipe
如何 map 使用一个 pipe_function
注意:
- funcy_pipe.PipeFirst()(*new_args, ***new_kwargs)
接收的参数会被累加到 self.function 的 args 和 kwargs 中,
- 这个
__call___,()运算符,不是函数执行,而是修改 self.function 的参数 - 因此 fp.map(a_pipe_fun) 实际上不会被执行,而是获取到新的 pipe_fun
正确的用法:
fp.pmap(a_pipe_function)1 2 3 4 5 6 7 8 9 10# 错误用法 In [140]: range(3) | fp.map(fp.inc) | fp.to_list Out[140]: [<funcy_pipe.pipe.PipeSecond at 0x7fa5f1029a10>, <funcy_pipe.pipe.PipeSecond at 0x7fa5f10281d0>, <funcy_pipe.pipe.PipeSecond at 0x7fa5f2238910>] # 正确用法 In [141]: range(3) | fp.pmap(fp.inc) | fp.to_list Out[141]: [1, 2, 3]
- 这个
funcy_pipe Vs. funcy_chain
funcy_chain
- 导致 ipython 卡住(补全时)
- not lazy, 会直接运行
funcy_pipe
- 支持原生的 layzy 运行
eg:
1 2 3 4 5 6 7 8 9 10 11# funcy_chain: 时间长, greedy In [222]: %time Chain(range(1000000)).map(lambda x: x*2) CPU times: user 115 ms, sys: 0 ns, total: 115 ms Wall time: 115 ms Out[222]: <funcy_chain.chain.Chain at 0x7fa5e3446c10> # funcy_pipe: 时间短, lazy load In [223]: %time range(1000000) | fp.map(lambda x: x*2) CPU times: user 29 µs, sys: 0 ns, total: 29 µs Wall time: 31.9 µs Out[223]: <map at 0x7fa5e3935390>解释:
- chain 直接把结果转成 list, 存储在 Chain._value 字段中
- pipe 遵循的原生的 iterator 和 generator 机制,返回的是一个 map 对象,需要执行 list(result) 才能获取到最终结果
文章作者
上次更新 2024-07-16 (7f33ae8)