Можно ли ввести подсказку лямбда-функции?

93

В настоящее время в Python параметры функции и типы возвращаемого значения могут иметь следующий тип:

def func(var1: str, var2: str) -> int:
    return var1.index(var2)

Это означает, что функция принимает две строки и возвращает целое число.

Однако этот синтаксис сильно сбивает с толку лямбда-выражения, которые выглядят так:

func = lambda var1, var2: var1.index(var2)

Я пробовал вводить подсказки типов как для параметров, так и для возвращаемых типов, и я не могу найти способ, который не вызывает синтаксической ошибки.

Можно ли ввести подсказку лямбда-функции? Если нет, то есть ли планы использовать лямбды с подсказками типов или по какой-либо причине (помимо очевидного конфликта синтаксиса), почему бы и нет?

Натан Меррилл
источник
Я думаю, что обычный вариант использования лямбда-выражения вложен в другую функцию (в отличие от обычных функций, которые определены в одном месте и вызываются где-то совершенно в другом). Если вы уже знаете типы в этой функции, то (в принципе) должно быть довольно легко выяснить, какие типы лямбда получит. Кроме того, изменение синтаксиса для поддержки аннотаций бы просто сделать ваш лямбда труднее читать.
mgilson
Почему вы хотите это сделать? Лямбда-синтаксис предназначен для функций «выброса» для использования в очень ограниченных контекстах, например, в качестве keyаргумента для sortedвстроенного. Я действительно не вижу смысла добавлять подсказки типа в таком ограниченном контексте. Кроме того, использование аннотаций переменных PEP-526 для добавления подсказок типа lambdaполностью упускает суть, ИМХО. lambdaСинтаксис предназначен для определения скрытых функций. Какой смысл использовать lambdaи сразу привязывать к переменной? Просто используйте def!
Лучано Рамальо

Ответы:

102

Вы можете вроде как в Python 3.6 и выше использовать аннотации переменных PEP 526 . Вы можете аннотировать переменную, которой вы назначаете lambdaрезультат, с помощью typing.Callableуниверсального :

from typing import Callable

func: Callable[[str, str], int] = lambda var1, var2: var1.index(var2)

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

Однако вместо этого вы можете просто использовать оператор функции; Единственное преимущество, которое lambdaпредлагает предложение, состоит в том, что вы можете поместить определение функции для простого выражения внутри более крупного выражения. Но указанная выше лямбда не является частью более крупного выражения, это только часть оператора присваивания, связывающего его с именем. Это именно то, чего def func(var1: str, var2: str): return var1.index(var2)можно достичь с помощью заявления.

Обратите внимание, что вы не можете аннотировать *argsили **kwargsаргументы отдельно, как указано в документации для Callable:

Не существует синтаксиса для указания необязательных аргументов или аргументов ключевого слова; такие типы функций редко используются как типы обратного вызова.

Это ограничение не распространяется на протокол PEP 544 с методом__call__ ; используйте это, если вам нужно выразительное определение того, какие аргументы следует принимать. Вам нужен Python 3.8 или установите typing-extensionsпроект для резервного копирования:

from typing-extensions import Protocol

class SomeCallableConvention(Protocol):
    def __call__(var1: str, var2: str, spam: str = "ham") -> int:
        ...

func: SomeCallableConvention = lambda var1, var2, spam="ham": var1.index(var2) * spam

Для lambdaвыражения самого , вы не можете использовать любые аннотации ( с синтаксисом , на котором строится тип намекая Пайтон). Синтаксис доступен только для defоператоров функций.

Из PEP 3107 - Аннотации функций :

Синтаксис лямбды не поддерживает аннотации. Синтаксис лямбда-выражения можно изменить для поддержки аннотаций, установив скобки вокруг списка параметров. Однако было решено не вносить это изменение, потому что:

  • Это было бы несовместимое изменение.
  • Лямбды все равно кастрированы.
  • Лямбда всегда можно изменить на функцию.

Вы по-прежнему можете прикреплять аннотации непосредственно к объекту, function.__annotations__атрибут является записываемым словарем:

>>> def func(var1: str, var2: str) -> int:
...     return var1.index(var2)
...
>>> func.__annotations__
{'var1': <class 'str'>, 'return': <class 'int'>, 'var2': <class 'str'>}
>>> lfunc = lambda var1, var2: var1.index(var2)
>>> lfunc.__annotations__
{}
>>> lfunc.__annotations__['var1'] = str
>>> lfunc.__annotations__['var2'] = str
>>> lfunc.__annotations__['return'] = int
>>> lfunc.__annotations__
{'var1': <class 'str'>, 'return': <class 'int'>, 'var2': <class 'str'>}

Конечно, не то, чтобы подобные динамические аннотации помогли вам, когда вы захотели запустить статический анализатор над подсказками типа.

Мартейн Питерс
источник
1
Если ответ, func: Callable[[str, str], int] = lambda var1, var2: var1.index(var2)то почему не лучший ответ def func(var1: str, var2: str) -> int: return var1.index(var2)???
Guido van Rossum
@GuidovanRossum: потому что это ответ на другой вопрос. :-) Здесь вопрос заключался в том, как применить подсказку типа к лямбде, которая дает тот же результат, что и определение функции. Тем не менее, я добавлю раздел, в котором рассказывается, почему вы можете не использовать лямбда.
Martijn Pieters
2
Нет, если понимать буквально, ответ должен быть «Нет, это невозможно, нет планов изменить это, и причины в основном синтаксические - и достаточно легко определить именованную функцию и аннотировать ее». Я также отмечаю, что предлагаемый обходной путь (создание переменной с определенным типом Callable) не очень полезен, если лямбда скрыта внутри большого вызова или структуры данных, поэтому это ни в коем случае не «лучше», чем определение функции. (Я бы сказал, что это хуже, потому что нотация Callable не очень удобочитаема.)
Гвидо ван Россум
Решение также отвлекает, выглядя так, как будто оно определяет переменную с определенным типом Callable, которому могут быть назначены разные лямбды в течение его времени существования. Это отличается от определения функции (по крайней мере, mypy не работает, если вы переопределяете или переназначаете def, но не переменной типа Callable) и дальше от исходного вопроса «Мне просто нужна лямбда с аннотациями типов». Я продолжаю утверждать, что в этом случае нет никакой пользы от сохранения лямбда-формы вместо def. (И я бы сказал, что даже если бы не было аннотаций типов.)
Гвидо ван Россум
1
@GuidovanRossum: Могу я предложить вам написать свой собственный ответ? Учитывая ваш особый статус отца языка, ваш ответ легко превзойдет этот, учитывая время, и вы можете написать именно то, что, по вашему мнению, должно быть в нем.
Martijn Pieters
47

Начиная с Python 3.6 вы можете (см. PEP 526 ):

from typing import Callable
is_even: Callable[[int], bool] = lambda x: (x % 2 == 0)
Ян
источник
Я не понимаю этого ответа: где вы задаете тип x?
stenci
3
@stenci is_evenФункция - это, Callableкоторая ожидает один intаргумент, поэтому x - это int.
янв.
Теперь я понимаю. На первый взгляд я подумал, что вы аннотируете только тип, возвращаемый лямбдой, а не тип ее аргументов.
stenci
4
на самом деле это не аннотирует саму лямбда: вы не можете получить эти аннотации из лямбда-объекта, как это можно сделать для аннотированной функции
cz
3
mypy 0.701 с Python 3.7 правильно проверяет тип: is_even('x')вызывает ошибку типа.
Конрад Рудольф
-2

Нет, это невозможно, нет планов изменить это, и причины в основном синтаксические.

Барт Лауэрс
источник