Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/guides/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Type System Guides
:caption: Contents:

libraries
lambda
writing_stubs
modernizing
unreachable
Expand Down
88 changes: 88 additions & 0 deletions docs/guides/lambda.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
*****************************************
The Trouble (Or Lack Thereof) With Lambda
*****************************************

Lambda expressions are a common and useful part of the Python programming language.
However, there is one problem with them: syntactically, they do not allow for type
annotations. While it is perfectly simple to write ``lambda x: x``, you cannot directly
indicate a type for x. (Type annotations are indicated by a colon, and so is the end
of the lambda parameter list. Where would the type annotation go?)

However, despite this infelicity, lambda expressions are not immune from static typing,
and in fact follow the same static type rules as everything else. Type checkers try
to deduce the type of the lambda arguments and return value, and if they can't they
fall back to ``Any``. Due to the inability to directly indicate types for these,
``Any`` tends to pop up quite often here. This means that many type errors may occur
here unnoticed, which is bad. For instance, the following example is a runtime type
error, but is uncaught in most (perhaps all) type checkers:

.. code-block:: python

f1 = lambda a, b: a + b
f1(1, "a")

(The alternative way of writing this, ``(lambda a, b: a + b)(1, "a")``, is typically
caught by type checkers, because it is simple and immediate enough that they are able
to deduce that a type error will occur.)

..
(This is an RST comment.)
A slightly more realistic example of an uncaught lambda type error is

.. code-block :: python
def apply(f, *x):
f(*x)
apply((lambda a, b: a + b), 1, "a")

since it doesn't immediately defeat the purpose of a lambda by binding it.
It also fails to get caught by mypy and pyright in their default modes, as
required for the example. However, it's a little bit harder to understand,
so we went with the other one.

There are some workarounds to this problem, which all involve assigning the lambda to
something, in one way or another, and annotating that. This is a bit unfortunate,
because the idiomatic use of a lambda involves not doing that. In fact, at that point
you might as well just define a normal function. Let's call that our first workaround.

``def f(x: object) -> object: return x``

The second workaround is equivalent: assigning the lambda to a variable, and annotating
the type of that variable with a Callable.

``f: Callable[[object], object] = lambda x: x``

..
(This is an RST comment. The following paragraph has been excised from the guide,
as most beginners will not know what a type comment is anyway — especially a function
type comment. However, the paragraph is left in this comment for greater context for
you, the future editor:)

Type comments on function definitions do not actually work on lambda, nor do
normal type comments help (although you can use a type comment on an assignment
to a variable with a lambda, of course; however this will have to be the Callable
syntax and not the function-arrow special one).

Most type checkers include an option to emit a warning if they aren't able to deduce
the type of an expression; this should be helpful if you want to avoid silent uncaught
type errors resulting from lambda expressions being deduced as ``Any``. For instance,
Mypy includes ``disallow_any_expr``/``--disallow-any-expr`` and Pyright includes
``reportUnknownLambdaType``. Both of those options are set to true in the respective
strict modes of those type checkers.

In conclusion:

1. There is no way to explicitly annotate lambda arguments or return values in the
lambdas themselves.

2. However, static typing rules still apply to lambdas, including type deduction.

3. Many lambdas get deduced as ``Any``, which might suppress the reporting of other
type errors.

4. However, many lambdas get deduced fine, and for those it's not a problem.

5. If you want to annotate the type of lambdas, you can bind them and annotate them
there.

6. Most type checkers have a setting that will warn you if anything gets deduced as
``Any``, and you can use that to avoid false negatives relating to lambda.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Guides
:maxdepth: 1

guides/libraries
guides/lambda
guides/writing_stubs
guides/modernizing
guides/unreachable
Expand Down