Как узнать, была ли функция объявлена ​​с помощью `лямбда` или `деф`?

Если я объявлю две функции 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__?


person finefoot    schedule 08.01.2019    source источник
comment
Есть ли какой-нибудь практический вариант использования, когда вам это действительно понадобится?   -  person user202729    schedule 08.01.2019
comment
Также могут быть некоторые объекты, которые нельзя вызывать, и встроенные функции Python.   -  person user202729    schedule 08.01.2019
comment
Наряду с другим комментарием выше, какая разница, является ли это определением или лямбдой?   -  person Jared Smith    schedule 08.01.2019
comment
@JaredSmith, например. Django не может сериализовать Lambdas.   -  person keepAlive    schedule 08.01.2019
comment
@jpp: ИМХО, это не дубликат! Принятый ответ на другой вопрос явно относится к Python2, в то время как Python3 в настоящее время используется чаще. И его нельзя применить в Python3...   -  person Serge Ballesta    schedule 08.01.2019
comment
@jpp stackoverflow.com/a/23852434/521776 этот ответ применим и к py3   -  person BlackBear    schedule 08.01.2019
comment
Каков ваш вариант использования?   -  person Joel Cornett    schedule 08.01.2019
comment
Это трудно понять без вашего варианта использования, но, вероятно, вы можете пригнуть ленту, то есть try что-то, что работает только с функциями определения, except конкретное возбужденное исключение и действовать по своему усмотрению.   -  person Chris_Rands    schedule 08.01.2019
comment
@Kanak В этом случае: попробуйте сериализовать его с помощью Django?   -  person tobias_k    schedule 08.01.2019
comment
Связано: stackoverflow.com/questions/12264834/   -  person John Y    schedule 08.01.2019


Ответы (3)


Насколько я знаю, вы не можете надежно работать в Python 3.

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

Python 3 имеет только один тип — function. Существуют действительно различные побочные эффекты, когда объявление обычной функции с def и lambda: 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

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

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

Вы можете проверить __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
person Peter Nowee    schedule 11.10.2020

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

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

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

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

person BlackBear    schedule 08.01.2019
comment
Чтобы усилить ваш ответ: поскольку def и lambda создают неразличимые объекты (в частности, поскольку вы можете свободно редактировать __name__ и __qualname__), то не только невозможно сделать то, что просит OP, но и не имеет смысла. Как вы определяете разницу между списком, созданным с помощью генератора списков, и списком, созданным функцией list и заполненным циклом for? Как вы определяете разницу между строкой, созданной с помощью одинарных кавычек, и строкой, созданной с помощью двойных кавычек? Вы не можете, потому что нет никакой разницы, кроме как на уровне исходного кода. - person John Y; 08.01.2019