17

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.?

duplode
  • 33,731
  • 7
  • 79
  • 150
Adeel Zafar Soomro
  • 1,492
  • 9
  • 15
  • 4
    one a side note, currying and partial-application are two different things. Currying implies transforming the function to work one argument at a time. So you when you apply the first argument you get a function than then you can apply the second argument. Partial application is just fixing some parameters to the function invocation – PuercoPop Apr 12 '13 at 18:23

2 Answers2

13

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.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
2

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

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
ncoghlan
  • 40,168
  • 10
  • 71
  • 80
  • This is certainly more elegant. However, I have a more general case in which _sort_by_score operates on a list of items_list (called piles_list), and the corresponding scoring function takes four arguments: piles_list_size, pile_position, items_list_size, item position. It isn't obvious to me how to use sorted with key in this case. Perhaps it is a question for another time. =) Thanks for your insight! – Adeel Zafar Soomro Feb 24 '11 at 03:57
  • @Sven: interesting trick, I never thought of that! Now I'm wondering if the language definition guarantees that behaviour... – ncoghlan Feb 24 '11 at 05:53
  • 1
    The [documentation of sorted](http://docs.python.org/library/functions.html#sorted) at least states that the key function is called only once per item. Since this has to be done before the actual sorting, I can't imagine how this could ever change, but of course there is no guarantee. – Sven Marnach Feb 24 '11 at 09:18
  • Yeah, I think it's one of those "technically an implementation detail, but no sane implementation will violate the assumption" deals. – ncoghlan Feb 24 '11 at 11:05