¿Es posible escribir sugerencia de una función lambda?

85

Actualmente, en Python, los parámetros de una función y los tipos de retorno se pueden insinuar de la siguiente manera:

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

Lo que indica que la función toma dos cadenas y devuelve un número entero.

Sin embargo, esta sintaxis es muy confusa con lambdas, que se ven así:

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

Intenté poner sugerencias de tipo tanto en parámetros como en tipos de retorno, y no puedo encontrar una forma que no cause un error de sintaxis.

¿Es posible escribir una sugerencia de una función lambda? Si no es así, ¿hay planes para aplicar lambdas de sugerencia de tipo, o por alguna razón (además del obvio conflicto de sintaxis) por qué no?

Nathan Merrill
fuente
Creo que el caso de uso habitual de una lambda está anidado dentro de otra función (en contraste con las funciones regulares que se definen en un lugar y se llaman en algún lugar completamente diferente). Si ya conoce los tipos en esa función, entonces (en principio), debería ser bastante fácil averiguar qué tipos va a recibir esa lambda. Además, modificar la sintaxis para admitir anotaciones haría que su lambda sea más difícil de leer.
mgilson
¿Por qué querrías hacer eso? La sintaxis lambda es para funciones "desechables" para su uso en contextos muy restringidos, por ejemplo, como el keyargumento de la función sortedintegrada. Realmente no veo ningún sentido en agregar sugerencias de tipo en contextos tan limitados. Además, el uso de anotaciones de variables PEP-526 para agregar sugerencias de tipo no entiende por lambdacompleto, en mi humilde opinión. La lambdasintaxis está destinada a definir funciones anónimas . ¿Cuál es el punto de usar lambday vincularlo inmediatamente a una variable? ¡Solo usa def!
Luciano Ramalho

Respuestas:

92

Puede, en cierto modo, en Python 3.6 y versiones posteriores usando anotaciones de variables PEP 526 . Puede anotar la variable a la que asigna el lambdaresultado con el typing.Callablegenérico :

from typing import Callable

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

Esto no adjunta la información de sugerencia de tipo al objeto de función en sí, solo al espacio de nombres en el que almacenó el objeto, pero esto suele ser todo lo que necesita para fines de sugerencia de tipo.

Sin embargo, también puede usar una declaración de función en su lugar; la única ventaja que lambdaofrece a es que puede poner una definición de función para una expresión simple dentro de una expresión más grande. Pero la lambda anterior no es parte de una expresión más grande, solo es parte de una declaración de asignación, vinculándola a un nombre. Eso es exactamente lo def func(var1: str, var2: str): return var1.index(var2)que lograría una declaración.

Tenga en cuenta que no se puede hacer anotaciones *argso **kwargsargumentos por separado o bien, como la documentación de Callablelos estados:

No hay sintaxis para indicar argumentos opcionales o de palabra clave; estos tipos de funciones rara vez se utilizan como tipos de devolución de llamada.

Esa limitación no se aplica a un protocolo PEP 544 con un método__call__ ; utilice esto si necesita una definición expresiva de qué argumentos deben aceptarse. Necesita Python 3.8 o instalar el typing-extensionsproyecto para un backport:

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

Para la lambdaexpresión en , no puede usar ninguna anotación (la sintaxis en la que se construye la sugerencia de tipo de Python). La sintaxis solo está disponible para defdeclaraciones de función.

De PEP 3107 - Anotaciones de funciones :

La sintaxis de lambda no admite anotaciones. La sintaxis de lambda podría cambiarse para admitir anotaciones, requiriendo paréntesis alrededor de la lista de parámetros. Sin embargo, se decidió no realizar este cambio porque:

  • Sería un cambio incompatible.
  • De todos modos, los Lambda están castrados.
  • La lambda siempre se puede cambiar a una función.

Aún puede adjuntar las anotaciones directamente al objeto, el function.__annotations__atributo es un diccionario escribible:

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

No es que las anotaciones dinámicas como estas vayan a ayudarlo cuando desee ejecutar un analizador estático sobre sus sugerencias de tipo, por supuesto.

Martijn Pieters
fuente
1
Si la respuesta es func: Callable[[str, str], int] = lambda var1, var2: var1.index(var2), ¿por qué no hay una mejor respuesta def func(var1: str, var2: str) -> int: return var1.index(var2)?
Guido van Rossum
@GuidovanRossum: porque esa es la respuesta a una pregunta diferente . :-) La pregunta aquí era cómo aplicar sugerencias de tipo a una lambda que da el mismo resultado que definir una función. Aún así, agregaré una sección que explica por qué es posible que no desee utilizar una lambda.
Martijn Pieters
1
No, tomado literalmente, la respuesta debería ser "No, no es posible, no hay planes para cambiar esto, y las razones son principalmente sintácticas, y es bastante fácil definir una función con nombre y anotarla". También noto que la solución propuesta (crear una variable con un tipo invocable específico) no es muy útil si el lambda está enterrado dentro de una gran llamada o estructura de datos, por lo que no es en ningún sentido "mejor" que definir una función. (Yo diría que es peor porque la notación Callable no es muy legible.)
Guido van Rossum
La solución también distrae al parecer que está definiendo una variable con un cierto tipo invocable a la que se le podrían asignar diferentes lambdas durante su vida útil. Eso es diferente a definir una función (al menos mypy se resiste si redefine o reasigna una definición pero no una variable de tipo invocable) y más lejos de la pregunta original "Solo quiero una lambda con anotaciones de tipo". Sigo afirmando que no hay ningún beneficio en mantener la forma lambda en lugar de la forma def en este caso. (Y yo diría que incluso si no hubiera anotaciones de tipo.)
Guido van Rossum
1
@GuidovanRossum: ¿Puedo invitarte a escribir tu propia respuesta entonces? Con su condición específica como padre del idioma, una respuesta suya fácilmente superaría a esta, con el tiempo, y puede escribir exactamente lo que cree que debería estar en él.
Martijn Pieters
43

Desde Python 3.6, puede (ver PEP 526 ):

from typing import Callable
is_even: Callable[[int], bool] = lambda x: (x % 2 == 0)
ene
fuente
No entiendo esta respuesta: ¿dónde establece el tipo de x?
stenci
3
@stenci La is_evenfunción es una Callableque espera un intargumento, por lo que x es una int.
enero
Entiendo ahora. A primera vista, pensé que estaba anotando solo el tipo devuelto por el lambda, no el tipo de sus argumentos.
stenci
4
esto no es en realidad una anotación de la lambda en sí: no puede recuperar estas anotaciones del objeto lambda como puede para una función anotada
cz
3
mypy 0.701 con Python 3.7 tipografía correcta comprueba esto: is_even('x')provoca un error de tipo.
Konrad Rudolph
-1

No, no es posible, no hay planes para cambiar esto, y las razones son principalmente sintácticas.

Bart Louwers
fuente