Is it possible to type hint a lambda function?

Question:

Currently, in Python, a function’s parameters and return types can be type hinted as follows:

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

Which indicates that the function takes two strings, and returns an integer.

However, this syntax is highly confusing with lambdas, which look like:

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

I’ve tried putting in type hints on both parameters and return types, and I can’t figure out a way that doesn’t cause a syntax error.

Is it possible to type hint a lambda function? If not, are there plans for type hinting lambdas, or any reason (besides the obvious syntax conflict) why not?

Asked By: Nathan Merrill

||

Answers:

You can, sort of, in Python 3.6 and up using PEP 526 variable annotations. You can annotate the variable you assign the lambda result to with the typing.Callable generic:

from typing import Callable

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

This doesn’t attach the type hinting information to the function object itself, only to the namespace you stored the object in, but this is usually all you need for type hinting purposes.

However, you may as well just use a function statement instead; the only advantage that a lambda offers is that you can put a function definition for a simple expression inside a larger expression. But the above lambda is not part of a larger expression, it is only ever part of an assignment statement, binding it to a name. That’s exactly what a def func(var1: str, var2: str): return var1.index(var2) statement would achieve.

Note that you can’t annotate *args or **kwargs arguments separately either, as the documentation for Callable states:

There is no syntax to indicate optional or keyword arguments; such function types are rarely used as callback types.

That limitation does not apply to a PEP 544 protocol with a __call__ method; use this if you need a expressive definition of what arguments should be accepted. You need Python 3.8 or install the typing-extensions project for a backport:

from typing_extensions import Protocol

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

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

For the lambda expression itself, you can’t use any annotations (the syntax on which Python’s type hinting is built). The syntax is only available for def function statements.

From PEP 3107 – Function Annotations:

lambda ‘s syntax does not support annotations. The syntax of lambda could be changed to support annotations, by requiring parentheses around the parameter list. However it was decided not to make this change because:

  • It would be an incompatible change.
  • Lambda’s are neutered anyway.
  • The lambda can always be changed to a function.

You can still attach the annotations directly to the object, the function.__annotations__ attribute is a writable dictionary:

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

Not that dynamic annotations like these are going to help you when you wanted to run a static analyser over your type hints, of course.

Answered By: Martijn Pieters

Since Python 3.6, you can (see PEP 526):

from typing import Callable
is_even: Callable[[int], bool] = lambda x: (x % 2 == 0)
Answered By: jan

For those just looking for a swift access to intellisense when writing your code, an almost-hack is to type-annotate the parameter just before the lambda declaration, do your work and only then shadow it with the parameter.

    x: YourClass
    map(lambda _ :  x.somemethod ...) # x has access to methods defined on YourClass

Then, right after:

    x: YourClass # can remove or leave
    map(lambda x:  x.somemethod, ListOfYourObjects) # inner x now shadows the argument
Answered By: rtviii

this works fine for me…

def fun()->None:
    def lam(i: int)->None:
        print("lam!", i)
    print("fun!")
    lam(1)
    lam(2)


fun()
lam()

prints

fun!
lam! 1
lam! 2
Traceback (most recent call last):
  File "/home/jail/prog.py", line 16, in <module>
    lam()
NameError: name 'lam' is not defined

tested on CPython 3.6.12 and 3.10.2

Answered By: hanshenrik

I strongly dislike assigning a lambda function to an identifier (See Is it pythonic: naming lambdas). If you want to do that nonetheless, you already have answers here.

If you don’t, and just want the type-checkers to do their thing, the answer is typing.cast:

from typing import cast, Callable

cast(Callable[[int], int], lambda x: x + 1)("foo")
# error: Argument 1 has incompatible type "str"; expected "int"
Answered By: Matthias Huschle
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.