Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get the code of an unnamed lambda used as a function argument #583

Open
bersbersbers opened this issue Mar 20, 2023 · 7 comments
Open

Get the code of an unnamed lambda used as a function argument #583

bersbersbers opened this issue Mar 20, 2023 · 7 comments

Comments

@bersbersbers
Copy link

Inspired by https://stackoverflow.com/q/75787845, I tried dill.source, and it appears to have the same issue(?) as inspect:

import dill.source

def decorator_with_lambda(lambda_argument):
    print("Printing lambda_argument ...")
    print(lambda_argument)
    print("Printing source of lambda_argument ...")
    print(dill.source.getsource(lambda_argument))
    def decorator(inner_fun):
        return inner_fun
    return decorator

@decorator_with_lambda(lambda: True)
def function() -> None:
    pass  # This should not be printed as part of lambda_argument!

function()

Output:

Printing lambda_argument ...
<function <lambda> at 0x000001E3CD2F04A0>
Printing source of lambda_argument ...
@decorator_with_lambda(lambda: True)
def function() -> None:
    pass  # This should not be printed as part of lambda_argument!

What I would like to be printed is lambda: True or even True, but instead I am getting the code of all of function.

Others have pointed out my lambda_argument wasn't a lambda at all, but I believe the output shows it is one.

@mmckerns
Copy link
Member

What I've found in other cases is dill.source can't get the source of "unnamed" lambdas. If you name them (i.e. give them an entry in the namespace), then dill.source works as expected. The same seems to apply in your case:

>>> foo = lambda : True
>>> import dill.source
>>> def decorator_with_lambda(lambda_argument):
...     print("Printing lambda_argument ...")
...     print(lambda_argument)
...     print("Printing source of lambda_argument ...")
...     print(dill.source.getsource(lambda_argument))
...     def decorator(inner_fun):
...         return inner_fun
...     return decorator
... 
>>> @decorator_with_lambda(foo)
... def function() -> None:
...   pass
... 
Printing lambda_argument ...
<function <lambda> at 0x100b903a0>
Printing source of lambda_argument ...
foo = lambda : True

>>> 

@bersbersbers
Copy link
Author

Thanks! Is there a chance that dill.source will be able to handle this any better than inspect, or are the two so closely knit together that I should not expect a resolution unless it comes through the Python standard library?

@mmckerns
Copy link
Member

mmckerns commented Mar 20, 2023

I'm not sure what you mean. inspect can't get the source correctly while dill.source does. If inspect begins to handle this case, we will certainly leverage it -- but otherwise, I'd expect new development to be done in dill.

>>> foo = lambda : True
>>> import inspect
>>> def decorator_with_lambda(lambda_argument):
...     print("Printing lambda_argument ...")
...     print(lambda_argument)
...     print("Printing source of lambda_argument ...")
...     print(inspect.getsource(lambda_argument))
...     def decorator(inner_fun):
...         return inner_fun
...     return decorator
... 
>>> @decorator_with_lambda(foo)
... def function() -> None:
...     pass
... 
Printing lambda_argument ...
<function <lambda> at 0x106d8bee0>
Printing source of lambda_argument ...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in decorator_with_lambda
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/inspect.py", line 997, in getsource
    lines, lnum = getsourcelines(object)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/inspect.py", line 979, in getsourcelines
    lines, lnum = findsource(object)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/inspect.py", line 798, in findsource
    raise OSError('could not get source code')
OSError: could not get source code
>>> 

@bersbersbers
Copy link
Author

bersbersbers commented Mar 21, 2023

Ah, now I see what you mean.

inspect can't get the source correctly while dill.source does

I think this holds true for working on the REPL. My original code in https://stackoverflow.com/q/75787845, saved as some bug.py, works fine when using python bug.py.

So there may be a couple of intertwined issues, getting code from unnamed lambdas and getting code from unnamed lambdas on the REPL. My use case has nothing to with the REPL, so I am primarily interested in whether there is chance we will be able to extract an unnamed lambda's body inside a Python code file, be it using dill.source or inspect. Having looked at inspect, the current implementation seems to very much line-based, and I wonder if the problem can be tackled at all (consider an example with two lambdas being defined on the same line). I'd hope that something like PEP 657 may allow closer inspection at one point, compare https://docs.python.org/3/whatsnew/3.11.html#whatsnew311-pep657

@mmckerns
Copy link
Member

If I remember correctly, I probably didn't account for the case of extracting the source of an unnamed lambda where the reference to the lambda was extracted from a containing object. I assume this case needs to be added to the source parser (potentially using some dummy name for the lambda).

@mmckerns mmckerns changed the title Get the code of just a lambda argument used in a decorator Get the code of an unnamed lambda argument in a decorator Mar 21, 2023
@mmckerns mmckerns changed the title Get the code of an unnamed lambda argument in a decorator Get the code of an unnamed lambda used as a function argument Mar 21, 2023
@mmckerns
Copy link
Member

Note that this case (an unnamed lambda) should also fail:

>>> import dill
>>> 
>>> class Foo(object):
...   def __init__(self, func):
...     self.func = func
...   def __call__(self, *args, **kwds):
...     return self.func(*args, **kwds)
... 
>>> f = Foo(lambda x:x*x)
>>> f(2)
4
>>> dill.source.getsource(f.func)

However, if the lambda is named, it works.

@mmckerns
Copy link
Member

similar, but different #221

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants