Is there any way to tell if a function object was a lambda or a def?
Asked Answered
T

3

11

Consider the two functions below:

def f1():
    return "potato"

f2 = lambda: "potato"
f2.__name__ = f2.__qualname__ = "f2"

Short of introspecting the original source code, is there any way to detect that f1 was a def and f2 was a lambda?

>>> black_magic(f1)
"def"
>>> black_magic(f2)
"lambda"
Tildi answered 4/6, 2019 at 20:37 Comment(3)
The code in the question reassigns __name__, though.Araldo
Note that the linked answer and @PeterDeGlopper's comment are both dependent on you not having done what the last line of your code did, which is to set the __name__ attribute of your lambdaLannielanning
Why would you need to?Egyptology
A
20

You could check the code object's name. Unlike the function's name, the code object's name cannot be reassigned. A lambda's code object's name will still be '<lambda>':

>>> x = lambda: 5
>>> x.__name__ = 'foo'
>>> x.__name__
'foo'
>>> x.__code__.co_name
'<lambda>'
>>> x.__code__.co_name = 'foo'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: readonly attribute

It is impossible for a def statement to define a function whose code object's name is '<lambda>'. It is possible to replace a function's code object after creation, but doing so is rare and weird enough that it's probably not worth handling. Similarly, this won't handle functions or code objects created by manually calling types.FunctionType or types.CodeType. I don't see any good way to handle __code__ reassignment or manually-created functions and code objects.

Araldo answered 4/6, 2019 at 20:46 Comment(7)
You can replace the code object altogether, though.Egyptology
Arguably, caring whether a function was created via a lambda expression or def statement is also rare and weird.Egyptology
@Egyptology arguably, it's fine if you replace the code object altogether, because if you do, you just replaced the function body itself, so it will reflect whether or not the function it was replaced by is a function or a lambda.Contour
@Contour No, you haven't. It's the same function object, but with a new body. You haven't replaced the global namespace, the closures, the default parameter values, etc.Egyptology
Cool. Works in Python 2.7 too.Tildi
@Egyptology possible use case- checking for non-anonymous lambda functions (generally bad practise) and changing them to def? using flake8 would no doubt be easier thoughJabberwocky
@Jabberwocky Just do a text search for lambda in that case. There's no point trying to find functions that were originally defined with lambda if you can't actually see where it was originally defined.Egyptology
V
1

You could use ast.NodeVisitor to achieve your goal without hardcoding any calls to the functions by operating on the sources layer, with it you can identify ALL Lambda, FunctionDef, AsyncFunctionDef functions definitions and print out it's location, name, etc. Please see code sample below:

import ast


class FunctionsVisitor(ast.NodeVisitor):

    def visit_Lambda(self, node):
        print(type(node).__name__, ', line no:', node.lineno)

    def visit_FunctionDef(self, node):
        print(type(node).__name__, ':', node.name)

    def visit_AsyncFunctionDef(self, node):
        print(type(node).__name__, ':', node.name)

    def visit_Assign(self, node):
        if type(node.value) is ast.Lambda:
            print("Lambda assignment to: {}.".format([target.id for target in node.targets]))
        self.generic_visit(node)

    def visit_ClassDef(self, node):
        # Remove that method to analyse functions visitor and functions in other classes.
        pass

def f1():
    return "potato"

f2 = f3 = lambda: "potato"
f5 = lambda: "potato"

async def f6():
    return "potato"

# Actually you can define ast logic in separate file and process sources file in it.
with open(__file__) as sources:
    tree = ast.parse(sources.read())
    FunctionsVisitor().visit(tree)

The output for code below is following:

FunctionDef : f1
Lambda assignment to: ['f2', 'f3'].
Lambda , line no: 27
Lambda assignment to: ['f5'].
Lambda , line no: 28
AsyncFunctionDef : f6
Vexatious answered 5/6, 2019 at 7:52 Comment(2)
This is introspecting the original source code - out of scope for the question.Tildi
@Tildi Cannot disagree, but actually there are not so much on SO on the ast theme, so I am trying to give a broad answer in order to help users which will need that introspection and will face that question in future and answering the actual question in the same time.Vexatious
V
-3

Here

def f1():
    return "potato"


f2 = lambda: "potato"


def is_lambda(f):
    return '<lambda>' in str(f.__code__)


print(is_lambda(f1))
print(is_lambda(f2))

output

False
True
Virgel answered 4/6, 2019 at 20:54 Comment(1)
This is effectively @user2357112's answer.Illusionism

© 2022 - 2024 — McMap. All rights reserved.