Home > Back-end >  Accessing __doc__ of function inside a lambda
Accessing __doc__ of function inside a lambda

Time:10-20

I would like to extract the docstring of a function once it has been wrapped in lambda.

Consider the following example:

def foo(a=1):
    """Foo docstring"""
    return a

dct = {
    "a": foo,
    "b": lambda: foo(2),
}

for k, v in dct.items()
    print(k, v(), v.__doc__)

I get:

a 1 Foo docstring
b 2 None

How can I reference the function called on "calling" the lambda one?

CodePudding user response:

There is no "good" way to do this. However, it is technically possible using the inspect module. Here is a very brittle and fragile implementation that fits your use case of getting the docstring of the first function called by a lambda:

import inspect
import re

def get_docstring_from_first_called_function(func):
    # the inspect module can get the source code
    func_source = inspect.getsource(func)

    # very silly regex that gets the name of the first function
    name_of_first_called_function = re.findall(r'\w |\W ', func_source.split("(")[0])[-1]

    # if the function is defined at the top level, it will be in `globals()`
    first_called_function = globals()[name_of_first_called_function]
    return first_called_function.__doc__


def foo(a=1):
    """Foo docstring"""
    return a

b = lambda: foo(2)

print(get_docstring_from_first_called_function(b))
> Foo docstring

As I said, this implementation is fragile and brittle. For instance, it breaks instantly if the first function called is not in globals. But if you find yourself in very dire straits, you can probably hack together a solution for your use case.

If at all possible, however, you should use functools instead

import functools

def foo(a=1):
    """Foo docstring"""
    return a

b = functools.partial(foo, 2)

print(b.func.__doc__)

CodePudding user response:

Well, trying to add features such as docs to smt which by definition should be "anonymous"... could be done only in a tricky way. I could achieve that using nested decorators.

Notice first that print('__doc__' in dir(lambda: '')) is True.

def add_doc_to_lambda(func, a, docs=''):
    # also provide custom docs parameter
    if docs == '':
        return (lambda l: (lambda _=setattr(l, '__doc__', func.__doc__): l)())(lambda: func(a))
    return (lambda l: (lambda _=setattr(l, '__doc__', docs): l)())(lambda: func(a))

def foo(a=1):
    """Foo docstring"""
    return a

dct = {
    "a": foo,
    "b": add_doc_to_lambda(foo, 2), # add_doc_to_lambda(foo, 2, 'lambda docs') # case of custom docs
}

for k, v in dct.items():
    print('>>> ', k, v(), v.__doc__)

Output

>>>  a 1 Foo docstring
>>>  b 2 Foo docstring   # lambda docs # case custom docs

Remark:

  1. add_doc_to_lambda is callable
print(dct['b'])
<function dec.<locals>.<lambda> at 0x7f6eda92ee50>
  1. Also a more straightforward way such as
l = lambda: foo(2)
l.__doc__ = foo.__doc__
print(l.__doc__)

works but assignment of lambda function to a variable is a bad practice.

CodePudding user response:

I suggest that a different mechanism for your lambda may be appropriate here. Anything that returns a properly wrapped FunctionType is going to have direct access to the correct __doc__. One common method would be to use functools.partial with functools.wraps:

from functools import partial, wraps

dct = {
    "a": foo,
    "b": wraps(foo)(partial(foo, 2)),
}
  • Related