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?
CodePudding user response:
Depending on where pattern is defined, you can use inspect.getclosurevars
to get the variables, they should be in globals or nonlocal. It might be really hard to generalize and I' pretty sure you can achieve your end goal in a better way.
Example:
import inspect
x = lambda bar: print(foo bar)
inspect.getclosurevars(x)
CodePudding user response:
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.*'