Skip to content
Advertisement

Get source function of lambda expression with captured variable

Having a lambda function that uses a regex:

import re

def make_func(pattern = "black.*"):
    func = lambda x: re.fullmatch(pattern, x)
    return func

I would like to retrieve the source function including its pattern for error reporting. The expected output would be something like: func = lambda x: re.fullmatch("black.*", x)

Knowing of the inspect module and the function getsource, I managed to solve part of my problem, but the pattern variable is not evaluated:

from inspect import getsource
print(getsource(func))

which yields func = lambda x: re.fullmatch(pattern, x). How can I get the concrete pattern that was captured by the function as well?

Advertisement

Answer

I’m not sure what you’re looking for exactly, but I think there are several ways to achieve what you want.

For example, you could store the pattern inside the lambda function object that you return, or update its docstring:

def make_func(pattern='black.*'):
    func = lambda x: re.fullmatch(pattern, x)
    # Option 1:
    func.pattern = pattern
    # Option 2:
    func.__doc__ = f'lambda x: re.fullmatch({pattern!r}, x)'
    return func

Using either of this options, the used pattern is still available in the function object so it can be used for error reporting. Note that this is bit hackish, and I’m not sure if this might cause problems in the long run.

By the way: assigning a lambda expression is an anti-pattern (see pycodestyle/flake8 rule E731 and/or PEP-8). You could just as well define and return a function:

import re

def make_func(pattern='black.*'):
    def func(x):
        return re.fullmatch(pattern, x)
    func.__doc__ = f're.fullmatch({pattern!r}, x)'
    return func

func = make_func()
print(func.__doc__)  # prints: re.fullmatch('black.*', x)

I think a nicer and more Pythonic solution is to create a callable object, which could be used as a function but also incorporates the original pattern:

import re

class FullPatternMatcher:

    def __init__(self, pattern):
        self.pattern = pattern

    def __call__(self, x):
        return re.fullmatch(self.pattern, x)

    def __str__(self):
        return f're.fullmatch({self.pattern!r}, x)'


def make_func(pattern='black.*'):
    return FullPatternMatcher(pattern)


func = make_func()
# just to demonstrate that the function is working:
assert func('white sheep') is None
assert func('black sheep') is not None
print(str(func))  # prints: re.fullmatch('black.*', x)

Finally, you could also use functools.partial, which has roughly the same effect as above solution, but using the standard library instead of a custom class. In this case the pattern is stored as args[0] inside the returned object:

import functools
import re

def pattern_match(pattern, x):
    return re.fullmatch(pattern, x)

def make_func(pattern='black.*'):
    return functools.partial(pattern_match, pattern)

func = make_func()
print(repr(func.args[0]))  # prints: 'black.*'
User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement