Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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"
like image 536
wim Avatar asked Jun 04 '19 20:06

wim


People also ask

What is the difference between def and lambda?

A lambda is an expression producing a function. A def is a statement producing a function.

What is the difference between a normal def function and lambda function?

Lambda Function, also referred to as 'Anonymous function' is same as a regular python function but can be defined without a name. While normal functions are defined using the def keyword, anonymous functions are defined using the lambda keyword. However,they are restricted to single line of expression.

What is lambda vs def in Python?

def is a keyword that doesn't return anything and creates a 'name' in the local namespace. lambda is a keyword that returns a function object and does not create a 'name' in the local namespace.

What are the characteristics of a lambda function?

The characteristics of lambda functions are: Lambda functions are syntactically restricted to return a single expression. You can use them as an anonymous function inside other functions. The lambda functions do not need a return statement, they always return a single expression.


2 Answers

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.

like image 172
user2357112 supports Monica Avatar answered Sep 30 '22 19:09

user2357112 supports Monica


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
like image 43
Andriy Ivaneyko Avatar answered Sep 30 '22 18:09

Andriy Ivaneyko