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


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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.