4

If I declare two functions a and b:

def a(x):
    return x**2

b = lambda x: x**2

I can not use type to differentiate them, since they're both of the same type.

assert type(a) == type(b)

Also, types.LambdaType doesn't help:

>>> import types
>>> isinstance(a, types.LambdaType)
True
>>> isinstance(b, types.LambdaType)
True

One could use __name__ like:

def is_lambda_function(function):
    return function.__name__ == "<lambda>"

>>> is_lambda_function(a)
False
>>> is_lambda_function(b)
True

However, since __name__ could have been modified, is_lambda_function is not guaranteed to return the correct result:

>>> a.__name__ = '<lambda>'
>>> is_lambda_function(a)
True

Is there a way which produces a more reliable result than the __name__ attribute?

finefoot
  • 9,914
  • 7
  • 59
  • 102

3 Answers3

6

AFAIK, you cannot reliably in Python 3.

Python 2 used to define a bunch of function types. For that reason, methods, lambdas and plain functions have each their own type.

Python 3 has only one type which is function. There are indeed different side effects where declaring a regular function with def and a lambda: def sets the name to the name (and qualified name) of the function and can set a docstring, while lambda sets the name (and qualified name) to be <lambda>, and sets the docstring to None. But as this can be changed...

If the functions are loaded from a regular Python source (and not typed in an interactive environment), the inspect module allows to access the original Python code:

import inspect

def f(x):
    return x**2

g = lambda x: x**2

def is_lambda_func(f):
    """Tests whether f was declared as a lambda.

Returns: True for a lambda, False for a function or method declared with def
Raises:
    TypeError if f in not a function
    OSError('could not get source code') if f was not declared in a Python module
                                         but (for example) in an interactive session
"""
    if not inspect.isfunction(f):
        raise TypeError('not a function')
    src = inspect.getsource(f)
    return not src.startswith('def') and not src.startswith('@') # provision for decorated funcs

g.__name__ = 'g'
g.__qualname__ = 'g'

print(f, is_lambda_func(f))
print(g, is_lambda_func(g))

This will print:

<function f at 0x00000253957B7840> False
<function g at 0x00000253957B78C8> True

By the way, if the problem was serialization of function, a function declared as a lambda can successfully be pickled, provided you give it a unique qualified name:

>>> g = lambda x: 3*x
>>> g.__qualname__ = "g"
>>> pickle.dumps(g)
b'\x80\x03c__main__\ng\nq\x00.'
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
2

You can check __code__.co_name. It contains what the name was at the time the function/lambda was compiled:

def a(x):
    return x**2

b = lambda x: x**2

def is_lambda_function(f):
    return f.__code__.co_name == "<lambda>"

>>> is_lambda_function(a)
False
>>> is_lambda_function(b)
True

And, contrary to __name__, __code__.co_name is read-only...

>>> a.__name__ = "<lambda>"
>>> b.__name__ = "b"

>>> a.__code__.co_name = "<lambda>"
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: readonly attribute

>>> b.__code__.co_name = "b"
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: readonly attribute

... so the results will stay the same:

>>> is_lambda_function(a)
False
>>> is_lambda_function(b)
True
Peter Nowee
  • 223
  • 1
  • 10
1

I took the chance to dive in cpython's source to see if I could find anything, and I am afraid I have to second Serge's answer: you cannot.

Briefly, this is a lambda's journey in the interpreter:

  1. During parsing, lambdas, just like every other expression, are read into an expr_ty, which is a huge union containing data of every expression.
  2. This expr_ty is then converted to the appropriate type (Lambda, in our case)
  3. After some time we land into the function that compiles lambdas
  4. This function calls assemble, which calls makecode, which initializes a PyCodeObject (functions, methods, as well as lambdas, all end up here).

From this, I don't see anything particular that is specific to lambdas. This, combined with the fact that Python lets you modify pretty much every attribute of objects makes me/us believe what you want to do is not possible.

BlackBear
  • 22,411
  • 10
  • 48
  • 86
  • 2
    To amplify your answer: Since `def` and `lambda` produce indistinguishable objects (particularly since you can freely edit `__name__` and `__qualname__`), then not only is it "not possible" to do what OP asks, it's *not meaningful*. How do you tell the difference between a list produced by a list comprehension versus a list created by the `list` function and populated by a `for` loop? How do you tell the difference between a string produced by single-quotes versus a string produced by double-quotes? You can't, because there *is no difference*, except at the source code level. – John Y Jan 08 '19 at 17:00