Чи можна ввести натяк на лямбда-функцію?


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повністю пропустити суть, IMHO. lambdaСинтаксис призначений для визначення прихованих функцій. Який сенс використовувати lambdaі негайно прив’язувати його до змінної? Просто використовуйте def!
Luciano Ramalho

Відповіді:


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: адже це відповідь на інше питання. :-) Питання тут полягало в тому, як застосувати натяк на тип до лямбда-сигналу, який дає той самий результат, що і визначення функції. Тим не менше, я додам розділ, який охоплює, чому ви можете не використовувати лямбду.
Мартін Пітерс

2
Ні, якщо взяти буквально, відповідь повинна бути: "Ні, це неможливо, це не планується змінювати, і причини в першу чергу синтаксичні - і досить просто визначити названу функцію та анотувати це". Я також зауважую, що запропонований обхідний шлях (створити змінну з певним типом Callable) не дуже корисний, якщо лямбда закопана всередині великого виклику або структури даних, тому це ні в якому сенсі не "краще", ніж визначення функції. (Я б стверджував, що це гірше, оскільки нотація, що викликається, не дуже читається.)
Гвідо ван Россум

Рішення також відволікає увагу, виглядаючи так, ніби воно визначає змінну з певним типом Callable, якому можна було призначити різні лямбди протягом свого життя. Це відрізняється від визначення функції (принаймні mypy balks, якщо ви перевизначаєте або перепризначаєте def, але не змінну типу Callable), і далі від вихідного питання "Я просто хочу лямбду з анотаціями типу". Я продовжую стверджувати, що збереження лямбда-форми замість форми деф у цьому випадку не дає ніякої вигоди. (І я б сказав, що навіть якби не було анотацій типу.)
Гвідо ван Россум

1
@GuidovanRossum: Чи можу я запросити вас написати свою власну відповідь? З вашим конкретним статусом батька мови, ваша відповідь легко перевершить цю, заданий час, і ви зможете написати саме те, що, на вашу думку, має бути в ній.
Мартін Пітерс

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 - це an 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.