我想将函数列表
fs = [ f, g, h ]
按顺序应用于字符串 text=' abCdEf '
类似
f( g( h( text) ) )
。
这可以通过以下代码轻松完成:
# initial text
text = ' abCDef '
# list of functions to apply sequentially
fs = [str.rstrip, str.lstrip, str.lower]
for f in fs:
text = f(text)
# expected result is 'abcdef' with spaces stripped, and all lowercase
print(text)
似乎
functools.reduce
应该在这里完成这项工作,因为它在每次迭代时“消耗”函数列表。
from functools import reduce
# I know `reduce` requires two arguments, but I don't even know
# which one to chose as text of function from the list
reduce(f(text), fs)
# first interaction should call
y = str.rstrip(' abCDef ') --> ' abCDef'
# next iterations fails, because tries to call ' abCDef'() -- as a function
不幸的是,这段代码不起作用,因为每次迭代都会返回一个 string 而不是函数,并且失败并显示
TypeError
: 'str' object is not callable
.
map
、reduce
或 list comprehension
来解决此问题的方法?reduce
可以采用三个参数:
reduce(function, iterable, initializer)
这三个论点一般来说是什么?
function
是两个参数的函数。我们将这两个参数称为 t
和 f
。t
将以 initializer
开头;然后将继续作为上次调用 function
的返回值。f
,取自 iterable
。在我们的案例中,这三个论点是什么?
f
将成为函数之一;t
必须是文本;function
的返回必须是结果文本;function(t, f)
必须是f(t)
。最后:
from functools import reduce
# initial text
text = ' abCDef '
# list of functions to apply sequentially
fs = [str.rstrip, str.lstrip, str.lower]
result = reduce(lambda t,f: f(t), fs, text)
print(repr(result))
# 'abcdef'
这是一个替代解决方案,它允许您组合任意数量的函数并保存组合的函数以供重用:
import functools as ft
def compose(*funcs):
return ft.reduce(lambda f, g: lambda x: f(g(x)), funcs)
用途:
In [4]: strip_and_lower = compose(str.rstrip, str.lstrip, str.lower)
In [5]: strip_and_lower(' abCDef ')
Out[5]: 'abcdef'
In [6]: strip_and_lower(" AJWEGIAJWGIAWJWGIWAJ ")
Out[6]: 'ajwegiajwgiawjwgiwaj'
In [7]: strip_lower_title = compose(str.title, str.lower, str.strip)
In [8]: strip_lower_title(" hello world ")
Out[8]: 'Hello World'
请注意,函数的顺序很重要;这就像数学函数组合一样,即
(f . g . h)(x) = f(g(h(x))
,因此函数从右到左应用。
如果您希望静态分析起作用,您需要添加类型注释。我们可以使用泛型和闭包来代替不可类型化的 lambda:
import functools as ft
from typing import Callable, TypeVar
T = TypeVar("T")
Composable = Callable[[T], T]
def compose(*funcs: Composable[T]) -> Composable[T]:
def compose(f: Composable[T], g: Composable[T]) -> Composable[T]:
def f_of_g(x: T) -> T:
return f(g(x))
return f_of_g
return ft.reduce(compose, funcs)
strip_and_lower = compose(str.rstrip, str.lstrip, str.lower)
vscode 中的结果:
你可以试试这个:
import functools
text = ' abCDef '
fs = [str.rstrip, str.lstrip, str.lower]
text = functools.reduce(lambda store, func: func(store), fs, text)
print(text)
我认为您误解了reduce 的工作原理。减少将可迭代减少为单个值。回调函数可以接受两个参数,一个存储和一个元素。
reduce函数首先创建一个store变量。然后,循环遍历可迭代对象,使用存储变量和当前元素调用函数,并将存储更新为返回值。最后,该函数返回存储值。最后一个参数是 store 变量的开头。
因此在代码片段中,它循环遍历函数数组,并调用其上的相应函数。然后 lambda 将返回处理后的值,更新存储。
既然您还要求
map
解决方案,这里就是一个。我的 values
包含单元素迭代,values[0]
具有原始值,values[i]
具有应用第一个 i
函数后的值。
text = ' abCDef '
fs = [str.rstrip, str.lstrip, str.lower]
values = [[text]]
values += map(map, fs, values)
result = next(values[-1])
print(repr(result)) # prints 'abcdef'
但我不推荐这个。我主要好奇我是否能做到。现在我将尝试考虑如何避免构建该辅助列表。
好吧,我找到了一种没有辅助列表的方法,只占用 O(1) 内存。它将带有 tee 和 chain 的映射与自馈迭代器纠缠在一起,并使用双端队列来驱动它并获取最终元素。显然只是为了好玩。这是交替运行上述解决方案和新解决方案各三次的输出。它显示结果和峰值内存使用情况。我重复该列表 10000 次以提高内存使用量。
result = 'abcdef' 3,126,760 bytes map_with_black_magic
result = 'abcdef' 12,972 bytes map_with_blacker_magic
result = 'abcdef' 3,031,048 bytes map_with_black_magic
result = 'abcdef' 12,476 bytes map_with_blacker_magic
result = 'abcdef' 3,031,048 bytes map_with_black_magic
result = 'abcdef' 8,052 bytes map_with_blacker_magic
代码(在线尝试!):
def map_with_black_magic(text, fs):
values = [[text]]
values += map(map, fs, values)
return next(values[-1])
def map_with_blacker_magic(text, fs):
parts = [[[text]]]
values = chain.from_iterable(parts)
it1, it2 = tee(values)
parts.append(map(list, map(map, fs, it1)))
return deque(it2, 1)[0][0]
from itertools import tee, chain
from collections import deque
import tracemalloc as tm
text = ' abCDef '
fs = [str.rstrip, str.lstrip, str.lower] * 10000
for func in [map_with_black_magic, map_with_blacker_magic] * 3:
tm.start()
result = func(text, fs)
memory = tm.get_traced_memory()[1]
tm.stop()
print(f'{result = !r}{memory:12,} bytes ', func.__name__)