8

For partial function application, I know there are several ways to do that in Python. However, they seems not to preserve the original function's docstring.

Take functools.partial as example:

from functools import partial

def foo(a, b, c=1):
    """Return (a+b)*c."""
    return (a+b)*c

bar10_p = partial(foo, b=10)
print bar10_p.__doc__

partial(func, *args, **keywords) - new function with partial application 
of the given arguments and keywords.

Let's try fn.py:

from fn import F

def foo(a, b, c=1):
    """Return (a+b)*c."""
    return (a+b)*c

bar10_F = F(foo, b=10)
print bar10_F.__doc__

Provide simple syntax for functions composition
(through << and >> operators) and partial function
application (through simple tuple syntax).

Usage example:

>>> func = F() << (_ + 10) << (_ + 5)
>>> print(func(10))
25
>>> func = F() >> (filter, _ < 6) >> sum
>>> print(func(range(10)))
15

Is there any Python package/module providing partial application with preserved docstring?

UPDATE

As @Kevin and @Martijn Pieters mentioned, the function signature has changed such that it is not suggested to stick to the original function's docstring. I realized that I'm looking for an updated docstring with something like foo() with a default b value of 10 (Thanks for Kevin's simple but direct example.).

Community
  • 1
  • 1
Drake Guan
  • 14,514
  • 15
  • 67
  • 94
  • 1
    Why do you want to preserve the docstring? The parameters have changed, so the original docstring will be incorrect or misleading. You should probably just write a new one via `bar10_F.__doc__ = ...`. – Kevin Dec 08 '14 at 16:49
  • You are right. I should update my question. – Drake Guan Dec 08 '14 at 16:50

4 Answers4

15

__doc__ is writable, on partial objects as well as on functions; simply copy it over:

bar10_p = partial(foo, b=10)
bar10_p.__doc__ = func.__doc__

or use the functools.update_wrapper() function to do the copying for you; it'll copy a few other pieces of metadata for you too:

from functools import update_wrapper

bar10_p = partial(foo, b=10)
update_wrapper(bar10_p, foo)
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    I would strongly discourage this. The partial object has a different signature from the original, so the copied docstring will mislead. `update_wrapper()` is even worse because (I think?) it will make `help()` and `inspect` lie about the signature. – Kevin Dec 08 '14 at 16:54
  • 1
    @Kevin: yet `functools.wraps()` is widely used for decorators that also can change the signature. The docstring does not necessarily dictate the signature of the function! This depends *heavily* on the actual contents of the docstring. – Martijn Pieters Dec 08 '14 at 16:55
  • They *can* change the signature, but that doesn't mean they should. – Kevin Dec 08 '14 at 16:56
  • @Kevin: as such, 'strongly discouraging' is a rather heavy handed response here. – Martijn Pieters Dec 08 '14 at 16:56
  • If I'm reading your nicely-formatted API reference docs, and I'm told the `foo()` function takes two arguments, I expect to pass it two arguments. If I get a `TypeError: too many arguments`, I will be rather confused. – Kevin Dec 08 '14 at 16:58
  • 1
    @Kevin: doesn't that make assumptions about the docstrings for the callables the OP is wrapping that we cannot make? That's what introspection is for (which `help()` does, it includes the actual signature of the callable). – Martijn Pieters Dec 08 '14 at 17:00
  • A docstring will document all parameters, assuming it's properly written. – Kevin Dec 08 '14 at 17:00
  • @Kevin: There is a risk with any proxy not quite reflecting the underlying item quite correctly. That doesn't mean that all proxying should therefor be avoided. – Martijn Pieters Dec 08 '14 at 17:00
  • 1
    @Kevin: that is a style choice, not a choice made by the Python language. In practice this is not true. To dismiss copying a docstring across because in an ideal world docstrings should document all arguments is rather overzealous. – Martijn Pieters Dec 08 '14 at 17:02
3

Just write a new __doc__.

bar10_p = partial(foo, b=10)
bar10_p.__doc__ = """foo() with a default b value of 10.

See foo().

"""

Your function has a different interface from the original, so it should not copy the docstring exactly.

Kevin
  • 28,963
  • 9
  • 62
  • 81
3

With makefun you can do it:

from makefun import partial

def foo(a, b, c=1):
    """Return (a+b)*c."""
    return (a + b) * c

bar10_p = partial(foo, b=10)

assert bar10_p(0) == 10
assert bar10_p(0, c=2) == 20 
help(bar10_p)

It yields:

Help on function foo in module makefun.tests.test_so:

foo(a, c=1)
    <This function is equivalent to 'foo(a, c=1, b=10)', see original 'foo' doc below.>
    Return (a+b)*c.

Note that if you have any comment on how the docstring should be updated, do not hesitate to propose an issue on the git repo !

(I'm the author by the way)

smarie
  • 4,568
  • 24
  • 39
2

Partial has access to func method which is the original function. So through original function, you have access to original function docstring.

Try this:

from math import cos
from functools import partial

cos_partial = partial(cos, 0.5)
print(cos_partial.func.__doc__)