python 中的清理操作管道
Clean pipeline of operations in python
我有一个很长的管道,它对字符串列表进行各种操作 input_list
。管道将每个单词映射为小写,替换下划线,过滤掉特定单词,删除重复项,并剪辑到一定长度。
result = list(set(filter(lambda x : x != word, map(lambda x : x.lower().replace('_',' '), input_list))))[:clip_length]
我的问题是它不是很可读:它不是很清楚这个管道的输入是什么以及应用操作的顺序。看着有点痛,除非得到很好的评论,否则我以后可能不知道它做了什么。
有什么方法可以在 python 中编写一个管道,让我可以清楚地看到哪些操作按什么顺序发生,什么进什么出?更具体地说,我希望能够编写它以便操作从右到左或从左到右进行,而不是从内到外。
这是函数式风格,你可以从最内层的表达式向最外层的表达式阅读。
将其放在多行中并加上一些注释有助于提高可读性:
result = list( # (5) convert to list
set( # (4) convert to set (remove dupes)
filter(
lambda x: x != word, # (3) filter items != to word
map(
lambda x: x.lower().replace('_',' '), # (2) apply transformation
input_list # (1) take input_list
)
)
)
)[:clip_length] # (6) limit number of results
这是一个品味问题。我倾向于喜欢这样的单一表达式,具有最小的格式,可以让它很好地适应:
result = list(set(filter(lambda x : x != word,
map(lambda x : x.lower().replace('_',' '), input_list))))[:clip_length]
等效的命令式处理是:
result = set()
for x in input_list:
x = x.lower().replace('_', ' ')
if x != word:
result.add(x)
result = list(result)[:clip_length]
嗯,它很实用,但没有(一致的)风格。 "problem" 是用于这些表达式的各种语法。
- 调用 func 是使用普通前缀表示法完成的
f(arg)
- 获取子数组使用特殊语法
arr[n?:m?]
,而不是函数 slice(n,m)
set
是一个完全不同的类型,但它是中间使用的,因为集合恰好有 一些 我们想要的行为 - 我们想要的是 "unique" 个可迭代的元素,所以我们的函数应该被称为 unique
。如果我们碰巧使用 set
来实现 unique
,那很好,但这不是 reader 的关注点,他们的思想没有这些干扰
x.lower()
是 lower
在中缀位置的动态调用。比较前缀位置 lower(x)
。这同样适用于 s.replace(pat,rep)
与 replace(s, pat, rep)
map
和 filter
但是确实有功能接口 map(f,iter)
和 filter(f,iter)
但是要编写一个像您分享的程序这样的程序,有点错过了函数式风格最强大和最通用的特征:函数。是的,函数式编程也是关于组成漂亮的表达式链,但不能以可读性为代价!如果可读性开始受到伤害,请使用...使其变得更好......一个功能:D
考虑这个使用 uniform 函数式风格的程序。它仍然是一个常规的 python 程序。
def program (word = '', clip_length = 5, input = ''):
make_words = \
compose ( lower
, partial (replace, '_', ' ')
)
process = \
compose ( partial (map, make_words)
, partial (filter, lambda x: x != word)
, unique
, partial (take, clip_length)
)
return process (input)
print (program ('b', 4, 'A_a_a_B_b_b_c_c_c_d_e'))
# ['d', ' ', 'e', 'a']
# Note, your output may vary. More on this later.
现在是依赖项。每个函数仅对其参数和 returns 输出进行操作。
def partial (f, *xs):
return lambda *ys: f (*xs, *ys)
def compose (f = None, *fs):
def comp (x):
if f is None:
return x
else:
return compose (*fs) (f (x))
return comp
def take (n = 0, xs = []):
return xs [:n]
def lower (s = ''):
return s .lower ()
def replace (pat = '', rep = '', s = ''):
return s .replace (pat, rep)
def unique (iter):
return list (set (iter))
真的,这个问题不能为其中的一些要点设置一个更好的舞台。我将重新审视原始问题(以及上面的程序)中使用的 set
的选择,因为存在一个巨大的问题:如果您多次重新 运行 我们的程序,我们将得到不同的输出。换句话说,我们没有 referential transparency。那是因为 Python 的集合是无序的,当我们从有序列表转换为集合,然后再返回列表时,不能保证我们总是会得到相同的元素。
以这种方式使用 set
显示了如何使用现有语言功能解决唯一性问题的良好直觉,但我们想要恢复引用透明性。在我们上面的程序中,我们通过调用 unique
函数清楚地编码了我们获取输入唯一元素的意图。
# deterministic implementation of unique
def unique (iter):
result = list ()
seen = set ()
for x in iter:
if x not in seen:
seen .add (x)
result .append (x)
return result
现在当我们运行我们的程序时,我们总是得到相同的结果
print (program ('b', 4, 'A_a_a_B_b_b_c_c_c_d_e'))
# ['a', ' ', 'c', 'd']
# always the same output now
这让我想到了另一点。因为我们将 unique
抽象到它自己的函数中,所以我们自动获得了一个范围来定义它的行为。我选择在 unique
的实现中使用命令式风格,但这很好,因为它仍然是一个纯函数和函数的消费者无法区分。只要program
有效,你可以想出100种unique
的其他实现,没关系。
函数式编程是关于函数的。语言是你的驯服。它仍然是一个常规的 python 程序。
def fwd (x):
return lambda k: fwd (k (x))
def program (word = '', clip_length = 5, input = ''):
make_words = \
compose ( lower
, partial (replace, '_', ' ')
)
fwd (input) \
(partial (map, make_words)) \
(partial (filter, lambda x: x != word)) \
(unique) \
(partial (take, clip_length)) \
(print)
program ('b', 4, 'A_a_a_B_b_b_c_c_c_d_e')
# ['a', ' ', 'c', 'd']
在 repl.it
上触摸并试验此程序
我有一个很长的管道,它对字符串列表进行各种操作 input_list
。管道将每个单词映射为小写,替换下划线,过滤掉特定单词,删除重复项,并剪辑到一定长度。
result = list(set(filter(lambda x : x != word, map(lambda x : x.lower().replace('_',' '), input_list))))[:clip_length]
我的问题是它不是很可读:它不是很清楚这个管道的输入是什么以及应用操作的顺序。看着有点痛,除非得到很好的评论,否则我以后可能不知道它做了什么。
有什么方法可以在 python 中编写一个管道,让我可以清楚地看到哪些操作按什么顺序发生,什么进什么出?更具体地说,我希望能够编写它以便操作从右到左或从左到右进行,而不是从内到外。
这是函数式风格,你可以从最内层的表达式向最外层的表达式阅读。
将其放在多行中并加上一些注释有助于提高可读性:
result = list( # (5) convert to list
set( # (4) convert to set (remove dupes)
filter(
lambda x: x != word, # (3) filter items != to word
map(
lambda x: x.lower().replace('_',' '), # (2) apply transformation
input_list # (1) take input_list
)
)
)
)[:clip_length] # (6) limit number of results
这是一个品味问题。我倾向于喜欢这样的单一表达式,具有最小的格式,可以让它很好地适应:
result = list(set(filter(lambda x : x != word,
map(lambda x : x.lower().replace('_',' '), input_list))))[:clip_length]
等效的命令式处理是:
result = set()
for x in input_list:
x = x.lower().replace('_', ' ')
if x != word:
result.add(x)
result = list(result)[:clip_length]
嗯,它很实用,但没有(一致的)风格。 "problem" 是用于这些表达式的各种语法。
- 调用 func 是使用普通前缀表示法完成的
f(arg)
- 获取子数组使用特殊语法
arr[n?:m?]
,而不是函数slice(n,m)
set
是一个完全不同的类型,但它是中间使用的,因为集合恰好有 一些 我们想要的行为 - 我们想要的是 "unique" 个可迭代的元素,所以我们的函数应该被称为unique
。如果我们碰巧使用set
来实现unique
,那很好,但这不是 reader 的关注点,他们的思想没有这些干扰x.lower()
是lower
在中缀位置的动态调用。比较前缀位置lower(x)
。这同样适用于s.replace(pat,rep)
与replace(s, pat, rep)
map
和filter
但是确实有功能接口map(f,iter)
和filter(f,iter)
但是要编写一个像您分享的程序这样的程序,有点错过了函数式风格最强大和最通用的特征:函数。是的,函数式编程也是关于组成漂亮的表达式链,但不能以可读性为代价!如果可读性开始受到伤害,请使用...使其变得更好......一个功能:D
考虑这个使用 uniform 函数式风格的程序。它仍然是一个常规的 python 程序。
def program (word = '', clip_length = 5, input = ''):
make_words = \
compose ( lower
, partial (replace, '_', ' ')
)
process = \
compose ( partial (map, make_words)
, partial (filter, lambda x: x != word)
, unique
, partial (take, clip_length)
)
return process (input)
print (program ('b', 4, 'A_a_a_B_b_b_c_c_c_d_e'))
# ['d', ' ', 'e', 'a']
# Note, your output may vary. More on this later.
现在是依赖项。每个函数仅对其参数和 returns 输出进行操作。
def partial (f, *xs):
return lambda *ys: f (*xs, *ys)
def compose (f = None, *fs):
def comp (x):
if f is None:
return x
else:
return compose (*fs) (f (x))
return comp
def take (n = 0, xs = []):
return xs [:n]
def lower (s = ''):
return s .lower ()
def replace (pat = '', rep = '', s = ''):
return s .replace (pat, rep)
def unique (iter):
return list (set (iter))
真的,这个问题不能为其中的一些要点设置一个更好的舞台。我将重新审视原始问题(以及上面的程序)中使用的 set
的选择,因为存在一个巨大的问题:如果您多次重新 运行 我们的程序,我们将得到不同的输出。换句话说,我们没有 referential transparency。那是因为 Python 的集合是无序的,当我们从有序列表转换为集合,然后再返回列表时,不能保证我们总是会得到相同的元素。
以这种方式使用 set
显示了如何使用现有语言功能解决唯一性问题的良好直觉,但我们想要恢复引用透明性。在我们上面的程序中,我们通过调用 unique
函数清楚地编码了我们获取输入唯一元素的意图。
# deterministic implementation of unique
def unique (iter):
result = list ()
seen = set ()
for x in iter:
if x not in seen:
seen .add (x)
result .append (x)
return result
现在当我们运行我们的程序时,我们总是得到相同的结果
print (program ('b', 4, 'A_a_a_B_b_b_c_c_c_d_e'))
# ['a', ' ', 'c', 'd']
# always the same output now
这让我想到了另一点。因为我们将 unique
抽象到它自己的函数中,所以我们自动获得了一个范围来定义它的行为。我选择在 unique
的实现中使用命令式风格,但这很好,因为它仍然是一个纯函数和函数的消费者无法区分。只要program
有效,你可以想出100种unique
的其他实现,没关系。
函数式编程是关于函数的。语言是你的驯服。它仍然是一个常规的 python 程序。
def fwd (x):
return lambda k: fwd (k (x))
def program (word = '', clip_length = 5, input = ''):
make_words = \
compose ( lower
, partial (replace, '_', ' ')
)
fwd (input) \
(partial (map, make_words)) \
(partial (filter, lambda x: x != word)) \
(unique) \
(partial (take, clip_length)) \
(print)
program ('b', 4, 'A_a_a_B_b_b_c_c_c_d_e')
# ['a', ' ', 'c', 'd']
在 repl.it
上触摸并试验此程序