在 Python 中,部分函数应用(柯里化)与显式函数定义

发布于 2024-10-18 23:59:38 字数 1800 浏览 1 评论 0原文

在 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/等的风格吗?

In Python, is it considered better style to:

  • explicitly define useful functions in terms of more general, possibly internal use, functions; or,
  • use partial function application to explicitly describe function currying?

I will explain my question by way of a contrived example.

Suppose one writes a function, _sort_by_scoring, that takes two arguments: a scoring function and a list of items. It returns a copy of the original list sorted by scores based on each item's position within the original list. Two example scoring functions are also provided.

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

The function _sort_by_score is never called directly; instead, it is called by other single-argument functions that pass a scoring function and their lone argument (a list of items) to _sort_by_scoring and return the result.

# 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)

Obviously, this intent is better expressed in terms of function currying.

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

Usage (in either case):

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

Apparent advantages of the explicit function definition style:

  1. useful functions may be defined before the more general functions are, without raising NameErrors;
  2. helper functions (e.g., scoring functions) could be defined within the function definition body;
  3. possibly easier to debug;
  4. code looks nice by virtue of "explicit is better than implicit."

Apparent advantages of curried function definition style:

  1. expresses intent of functional programming idiomatically;
  2. code looks nice by virtue of succinctness.

For defining "useful" functions, which of the two styles is preferred? Are there other styles that are more idiomatic/Pythonic/etc.?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

贪恋 2024-10-25 23:59:38

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

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

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

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

在您的特定示例中,我可能不会使用任何一个,而是删除前导下划线并调用

sort_by_score(identity_scoring, foo)

对我来说最明确的名称。

If you want to have the curried functions as part of a public interface, use explicit function definitions. This has the following additional advantages:

  1. It is easier to assign a docstring to an explicit function definition. For partial() functions, you would have to assign to the __doc__ attribute, which is somewhat ugly.

  2. Real function definitions are easier to skim when browsing the module source.

I would use functools.partial() in a similar way to lambda expressions, i.e. for locally needed throw-away functions.

In your particular example, I'd probably use neither, drop the leading underscores and call

sort_by_score(identity_scoring, foo)

which seems the most explicit to me.

画离情绘悲伤 2024-10-25 23:59:38

稍微切题一下,通常希望让 sorted 内置函数尽可能多地完成装饰-排序-取消装饰工作。例如:(

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)]

仅作为答案发布,因为代码块不能用作注释。请参阅 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)

仅作为修订版发布,因为代码块不能用作注释。)

As a slight tangent, it's generally desirable to let the sorted builtin do as much the decorate-sort-undecorate work as is practical. For example:

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)]

(Only posted as an answer because blocks of code don't work as comments. See Sven's response for an answer to the actual question asked)

Edit by someone else: The Python sort function iterates through the list and generates the list of keys first. The key() function is called only once for each list item, in the order of the input list. Thus, you can also use the following implementation:

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)

(Only posted as a revision because blocks of code don't work as comments.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文