【问题标题】:In Python, partial function application (currying) versus explicit function definition在 Python 中,部分函数应用(currying)与显式函数定义
【发布时间】:2011-07-02 18:26:41
【问题描述】:

在 Python 中,是否被认为是更好的样式:

  • 根据更一般的、可能是内部使用的功能明确定义有用的功能;或者,
  • 使用偏函数应用显式描述函数柯里化?

我将通过一个人为的例子来解释我的问题。

假设有人编写了一个函数 _sort_by_scoring,它接受两个参数:一个评分函数和一个项目列表。它根据每个项目在原始列表中的位置返回按分数排序的原始列表的副本。还提供了两个示例评分函数。

def _sort_by_score(scoring, items_list):
    unsorted_scored_list = [(scoring(len(items_list), item_position), item) for item_position, item in enumerate(items_list)]
    sorted_list = [item for score, item in sorted(unsorted_scored_list)]
    return sorted_list

def _identity_scoring(items_list_size, item_position):
    return item_position

def _reversed_scoring(items_list_size, item_position):
    return items_list_size - item_position

永远不会直接调用函数_sort_by_score;相反,它由其他单参数函数调用,这些函数将评分函数及其唯一参数(项目列表)传递给 _sort_by_scoring 并返回结果。

# Explicit function definition style
def identity_ordering(items_list):
    return _sort_by_score(_identity_scoring, items_list)

def reversed_ordering(items_list):
    return _sort_by_score(_reversed_scoring, items_list)

显然,这个意图用函数柯里化来表达更好。

# Curried function definition style
import functools
identity_ordering = functools.partial(_sort_by_score, _identity_scoring)
reversed_ordering = functools.partial(_sort_by_score, _reversed_scoring)

用法(在任何一种情况下):

>>> foo = [1, 2, 3, 4, 5]
>>> identity_ordering(foo)
[1, 2, 3, 4, 5]
>>> reversed_ordering(foo)
[5, 4, 3, 2, 1]

显式函数定义风格的明显优势:

  1. 可以在更通用的函数之前定义有用的函数,而不会引发 NameErrors;
  2. 可以在函数定义体中定义辅助函数(例如评分函数);
  3. 可能更容易调试;
  4. 代码看起来不错,因为“显式优于隐式”。

柯里化函数定义风格的明显优势:

  1. 以惯用方式表达函数式编程的意图;
  2. 代码简洁美观。

对于定义“有用”的功能,两种风格中的哪一种更受欢迎?还有其他更惯用/Pythonic/等的样式吗?

【问题讨论】:

  • 一个旁注,柯里化和部分应用是两个不同的东西。柯里化意味着将函数转换为一次处理一个参数。因此,当您应用第一个参数时,您会得到一个函数,然后您可以应用第二个参数。部分应用只是为函数调用修复了一些参数

标签: python functional-programming currying partial-application


【解决方案1】:

作为一个小切线,通常希望让 sorted 内置函数尽可能多地执行 decorate-sort-undecorate 工作。例如:

def _sort_by_score(scoring, items_list):
    num_items = len(items_list)
    def score(entry):
        return scoring(num_items, entry[0])
    return [item for position, item in sorted(enumerate(items_list), key=score)]

(仅作为答案发布,因为代码块不能用作 cmets。有关实际问题的答案,请参阅 Sven 的回复)

由其他人编辑:Python 排序函数遍历列表并首先生成键列表。对于每个列表项,key() 函数仅按输入列表的顺序调用一次。因此,您还可以使用以下实现:

def _sort_by_score(scoring, items_list):
    num_items = len(items_list)
    index = itertools.count()
    def score(entry):
        return scoring(num_items, next(index))
    return sorted(items_list, key=score)

(仅作为修订发布,因为代码块不能用作 cmets。)

【讨论】:

  • 这当然更优雅。但是,我有一个更一般的情况,其中 _sort_by_score 对 items_list 列表(称为piles_list)进行操作,并且相应的评分函数采用四个参数:piles_list_size、pile_position、items_list_size、项目位置。在这种情况下,如何使用 sorted with key 对我来说并不明显。也许这是另一个问题。 =) 感谢您的洞察力!
  • @Sven:有趣的把戏,我从来没想过!现在我想知道语言定义是否能保证这种行为......
  • documentation of sorted 至少声明每个项目只调用一次键函数。由于这必须在实际排序之前完成,我无法想象这会如何改变,但当然不能保证。
  • 是的,我认为这是“技术上是实现细节,但没有理智的实现会违反假设”的交易之一。
【解决方案2】:

如果您想将柯里化函数作为公共接口的一部分,请使用显式函数定义。这具有以下额外优势:

  1. 将文档字符串分配给显式函数定义更容易。对于partial() 函数,您必须分配给__doc__ 属性,这有点难看。

  2. 真正的函数定义在浏览模块源代码时更容易浏览。

我会以与 lambda 表达式类似的方式使用 functools.partial(),即用于本地需要的一次性函数。

在您的特定示例中,我可能都不会使用,删除前导下划线并调用

sort_by_score(identity_scoring, foo)

这对我来说似乎是最明确的。

【讨论】:

  • 而且...我可以放弃我的答案,因为 Sven 打败了我 :)
  • @ncoghlan:我们两天前不是讨论过非常相似的事情吗? :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-20
  • 2016-07-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多