如何确定函数是否已由 `lambda` 或 `def` 声明?

How to find out if a function has been declared by `lambda` or `def`?

如果我声明两个函数 ab:

def a(x):
    return x**2

b = lambda x: x**2

我不能用type来区分它们,因为它们是同一类型。

assert type(a) == type(b)

此外,types.LambdaType 没有帮助:

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

可以像这样使用 __name__

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

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

但是,由于 __name__ 可以被修改,is_lambda_function 不能保证 return 正确的结果:

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

有没有比 __name__ 属性产生更可靠结果的方法?

AFAIK,你不能可靠地 Python 3.

Python 2 用来定义一堆函数类型。因此,方法、lambda 和普通函数都有自己的类型。

Python 3 只有一种类型,即function。使用 deflambda 声明常规函数确实有不同的副作用: def 将名称设置为函数的名称(和限定名称)并可以设置文档字符串,而 lambda 将名称(和限定名称)设置为 <lambda>,并将文档字符串设置为 None。但因为这是可以改变的...

如果函数是从常规 Python 源加载的(而不是在交互式环境中键入的),inspect 模块允许访问原始 Python 代码:

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))

这将打印:

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

顺便说一句,如果问题是函数序列化,声明为 lambda 的函数可以成功 pickle,只要你给它一个唯一的合格名称:

>>> g = lambda x: 3*x
>>> g.__qualname__ = "g"
>>> pickle.dumps(g)
b'\x80\x03c__main__\ng\nq\x00.'

我趁机潜入了cpython's source to see if I could find anything, and I am afraid I have to second :你不能。

简而言之,这是 lambda 在解释器中的旅程:

  1. 在解析过程中,lambda 与其他所有表达式一样,are read into an expr_ty 是一个包含每个表达式数据的巨大联合。
  2. 然后将此 expr_ty 转换为适当的类型(Lambda,在我们的例子中)
  3. 一段时间后我们降落在 the function that compiles lambdas
  4. 这个函数调用assemble, which calls makecode, which initializes a PyCodeObject(函数、方法和lambda,都在这里结束)。

从这里,我没有看到任何特定于 lambda 的东西。这一点,再加上 Python 允许您修改对象的几乎每个属性这一事实,使得 me/us 相信您想要做的事情是不可能的。

您可以查看__code__.co_name。它包含编译 function/lambda 时的名称:

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

而且,与 __name__ 相反,__code__.co_nameread-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

...所以结果将保持不变:

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