Как узнать, была ли функция объявлена ​​`lambda` или`def`?

Если я объявлю две функции a а также b:

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 не гарантируется возвращение правильного результата:

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

Есть ли способ, который дает более надежный результат, чем __name__ атрибут?

2 ответа

Решение

AFAIK, вы не можете надежно в Python 3.

Python 2 используется для определения группы типов функций. По этой причине методы, лямбды и простые функции имеют каждый свой тип.

Python 3 имеет только один тип, который function, Есть действительно разные побочные эффекты, когда объявление регулярной функции с def и lambda: def устанавливает имя в имя (и полное имя) функции и может установить строку документации, в то время как lambda устанавливает имя (и полное имя), чтобы быть <lambda>и устанавливает для строки документа значение Нет. Но как это можно изменить...

Если функции загружаются из обычного исходного кода 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

Кстати, если проблема заключалась в сериализации функции, функция, объявленная как лямбда, может быть успешно выбрана при условии, что вы дадите ей уникальное квалифицированное имя:

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

Вы можете проверить __code__.co_name. Он содержит то, что имя было во время компиляции функции / лямбда:

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_nameэто только для чтения...

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

Я воспользовался возможностью погрузиться в исходники cpython, чтобы посмотреть, смогу ли я найти что-нибудь, и я боюсь, что должен ответить Сержу: вы не можете.

Вкратце, это лямбда-путешествие в интерпретаторе:

  1. Во время синтаксического анализа лямбды, как и любое другое выражение, считываются в expr_ty, который представляет собой огромный союз, содержащий данные каждого выражения.
  2. это expr_ty затем преобразуется в соответствующий тип (в нашем случае лямбда)
  3. Через некоторое время мы попадаем в функцию, которая компилирует лямбды
  4. Эта функция вызывает ассемблер, который вызывает makecode, который инициализирует PyCodeObject (здесь заканчиваются функции, методы, а также лямбда-выражения).

Из этого я не вижу ничего специфического для лямбд. Это, в сочетании с тем фактом, что Python позволяет вам изменять практически все атрибуты объектов, заставляет меня / нас верить в то, что вы хотите сделать, невозможно.

Другие вопросы по тегам