views:

288

answers:

3

Partial application is cool. What functionality does functools.partial offer that you can't get through lambdas?

>>> sum = lambda x, y : x + y
>>> sum(1, 2)
3
>>> incr = lambda y : sum(1, y)
>>> incr(2)
3
>>> def sum2(x, y):
    return x + y

>>> incr2 = functools.partial(sum2, 1)
>>> incr2(4)
5

Is functools somehow more efficient, or readable?

A: 

I understand the intent quickest in the third example.

When I parse lambdas, I'm expecting more complexity/oddity than offered by the standard library directly.

Also, you'll notice that the third example is the only one which doesn't depend on the full signature of sum2; thus making it slightly more loosely coupled.

Jon-Eric
Hm, I'm actually of the opposite persuasion, I took a lot longer to parse the `functools.partial` call, whereas the lambdas are self-evident.
David Zaslavsky
+4  A: 

Well, here's an example that shows a difference:

In [132]: sum = lambda x, y: x + y

In [133]: n = 5

In [134]: incr = lambda y: sum(n, y)

In [135]: incr2 = partial(sum, n)

In [136]: print incr(3), incr2(3)
8 8

In [137]: n = 9

In [138]: print incr(3), incr2(3)
12 8

These posts by Ivan Moore expand on the "limitations of lambda" and closures in python:

ars
Good example. To me, this seems more a "bug" with lambda, actually, but I understand others may disagree. (Something similar happens with closures defined within a loop, as implemented in several programming languages.)
ShreevatsaR
The fix to this "early vs late binding dilemma" is to explicitly use early binding, when you want that, by `lambda y, n=n: ...`. Late binding (of names appearing _only_ in a function's body, not in its `def` or equivalent `lambda`) is anything **but** a bug, as I've shown at length in long SO answers in the past: you early-bind explicitly when that's what you want, use the late-binding default when *that* is what you want, and that's _exactly_ the right design choice given the context of the rest of Python's design.
Alex Martelli
Yeah, this is a good example.
Rosarch
@Alex Martelli: Yeah, sorry. I just fail to get used to late binding properly, perhaps because I think when defining functions that I'm actually defining something for good, and the unexpected surprises only cause me headaches. (More when I try to do functional things in Javascript than in Python, though.) I understand that many people *are* comfortable with late binding, and that it's consistent with the rest of Python's design. I would still like to read your other long SO answers, though -- links? :-)
ShreevatsaR
Alex is right, it's not a bug. But it's a "gotcha" that traps many lambda enthusiasts. For the "bug" side of the argument from a haskel/functional type, see Andrej Bauer's post: http://math.andrej.com/2009/04/09/pythons-lambda-is-broken/
ars
@ars: Ah yes, thanks for the link to Andrej Bauer's post. Yeah, the effects of late binding are certainly something that we mathematics-types (worse, with a Haskell background) keep finding grossly unexpected and shocking. :-) I'm not sure I'd go as far as Prof. Bauer and call it a design error, but it *is* hard for human programmers to completely switch between one way of thinking and another. (Or perhaps this is just my insufficient Python experience.)
ShreevatsaR
BTW, this issue (here and in Andrej Bauer's post) is a good reason to remove lambda from Python: it doesn't do the sensible thing that many would expect. ;-)
ShreevatsaR
@ShreevatsaR, too late to remove anything -- Python 3 is way finalized (wait another 20 years for Python 4;-). Re some of my answers above mentioned, "let me google it for you": http://www.google.com/search?q=%2Bearly+%2Blate+%2Bbinding+%2B"alex+martelli"+site:stackoverflow.com (I don't know how to do the equivalent search on SO directly, but this one has at least some relevant ones, it seems).
Alex Martelli
+7  A: 

What functionality does functools.partial offer that you can't get through lambdas?

Not much in terms of extra functionality (but, see later) -- and, readability is in the eye of the beholder. Most people who are familiar with functional programming languages (those in the Lisp/Scheme families in particular) appear to like lambda just fine -- I say "most", definitely not all, because Guido and I definitely are among those "familiar with" (etc) yet think of lambda as an eyesore anomaly in Python... he was repentant of ever having accepted it into Python and planned to remove it in Python 3, as one of "Python's glitches", and I fully supported him in that. (I love lambda in Scheme... but its limitations in Python, and the weird way it just doesn't fit in with the rest of the language, make my skin crawl).

Not so, however, for the hordes of lambda lovers -- who staged one of the closest things to a rebellion ever seen in Python's history, until Guido backtracked and decided to leave lambda in. Several possible additions to functools (to make functions returning constants, identity, etc) didn't happen (to avoid explicitly duplicating more of lambda's functionality), though partial did of course remain (it's no total duplication, nor is it an eyesore).

Remember that lambda's body is limited to be an expression, so it's got limitations. For example...:

>>> import functools
>>> f = functools.partial(int, base=2)
>>> f.args
()
>>> f.func
<type 'int'>
>>> f.keywords
{'base': 2}
>>> 

functools.partial's returned function is decorated with attributes useful for introspection -- the function it's wrapping, and what positional and named arguments it fixes therein. Further, the named arguments can be overridden right back (the "fixing" is rather, in a sense, the setting of defaults):

>>> f('23', base=10)
23

So, as you see, it's definely not as simplistic as lambda s: int(s, base=2)!-)

Yes, you could contort your lambda to give you some of this -- e.g., for the keyword-overriding,

>>> f = lambda s, **k: int(s, **dict({'base', 2}, **k))

but I dearly hope that even the most ardent lambda-lover doesn't consider this horror more readable than the partial call!-). And, the "attribute setting" part is even harder, because of the "body's a single expression" limitation of Python's lambda (plus the fact that assignment can never be part of a Python expression)... you end up "faking assignments within an expression" by stretching list comprehension well beyond its design limits...:

>>> f = [f for f in (lambda f: int(s, base=2),)
           if setattr(f, 'keywords', {'base': 2}) is None][0]

Now combine the named-arguments overridability, plus the setting of three attributes, into a single expression, and tell me just how readable that is going to be...!-)

Alex Martelli
Yeah, I'd say that the extra functionality of `functools.partial` that you mentioned makes it superior to lambda. Perhaps this is the topic of another post, but what is it on a design level that bothers you so much about `lambda`?
Rosarch
@Rosarch, as I said: first, it limitations (Python sharply distinguishes expressions and statements -- there's much you can't do, or can't do _sensibly_, within a single expression, and that's what a lambda's body _is_); second, its absolutely weird syntax sugar. If I could go back in time and change one thing within Python, it would be the absurd, meaningless, eyesore `def` and `lambda` keywords: make them both `function` (one name choice Javascript got _really_ right), and at least 1/3 of my objections would vanish!-). As I said, I have no objection to lambda _in Lisp_...!-)
Alex Martelli