Skip to content

Commit 4fb3203

Browse files
Document the trouble with lambdas in the guides (#2105)
1 parent 691e9e4 commit 4fb3203

3 files changed

Lines changed: 90 additions & 0 deletions

File tree

docs/guides/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Type System Guides
1212
:caption: Contents:
1313

1414
libraries
15+
lambda
1516
writing_stubs
1617
modernizing
1718
unreachable

docs/guides/lambda.rst

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
*****************************************
2+
The Trouble (Or Lack Thereof) With Lambda
3+
*****************************************
4+
5+
Lambda expressions are a common and useful part of the Python programming language.
6+
However, there is one problem with them: syntactically, they do not allow for type
7+
annotations. While it is perfectly simple to write ``lambda x: x``, you cannot directly
8+
indicate a type for x. (Type annotations are indicated by a colon, and so is the end
9+
of the lambda parameter list. Where would the type annotation go?)
10+
11+
However, despite this infelicity, lambda expressions are not immune from static typing,
12+
and in fact follow the same static type rules as everything else. Type checkers try
13+
to deduce the type of the lambda arguments and return value, and if they can't they
14+
fall back to ``Any``. Due to the inability to directly indicate types for these,
15+
``Any`` tends to pop up quite often here. This means that many type errors may occur
16+
here unnoticed, which is bad. For instance, the following example is a runtime type
17+
error, but is uncaught in most (perhaps all) type checkers:
18+
19+
.. code-block:: python
20+
21+
f1 = lambda a, b: a + b
22+
f1(1, "a")
23+
24+
(The alternative way of writing this, ``(lambda a, b: a + b)(1, "a")``, is typically
25+
caught by type checkers, because it is simple and immediate enough that they are able
26+
to deduce that a type error will occur.)
27+
28+
..
29+
(This is an RST comment.)
30+
A slightly more realistic example of an uncaught lambda type error is
31+
32+
.. code-block :: python
33+
def apply(f, *x):
34+
f(*x)
35+
apply((lambda a, b: a + b), 1, "a")
36+
37+
since it doesn't immediately defeat the purpose of a lambda by binding it.
38+
It also fails to get caught by mypy and pyright in their default modes, as
39+
required for the example. However, it's a little bit harder to understand,
40+
so we went with the other one.
41+
42+
There are some workarounds to this problem, which all involve assigning the lambda to
43+
something, in one way or another, and annotating that. This is a bit unfortunate,
44+
because the idiomatic use of a lambda involves not doing that. In fact, at that point
45+
you might as well just define a normal function. Let's call that our first workaround.
46+
47+
``def f(x: object) -> object: return x``
48+
49+
The second workaround is equivalent: assigning the lambda to a variable, and annotating
50+
the type of that variable with a Callable.
51+
52+
``f: Callable[[object], object] = lambda x: x``
53+
54+
..
55+
(This is an RST comment. The following paragraph has been excised from the guide,
56+
as most beginners will not know what a type comment is anyway — especially a function
57+
type comment. However, the paragraph is left in this comment for greater context for
58+
you, the future editor:)
59+
60+
Type comments on function definitions do not actually work on lambda, nor do
61+
normal type comments help (although you can use a type comment on an assignment
62+
to a variable with a lambda, of course; however this will have to be the Callable
63+
syntax and not the function-arrow special one).
64+
65+
Most type checkers include an option to emit a warning if they aren't able to deduce
66+
the type of an expression; this should be helpful if you want to avoid silent uncaught
67+
type errors resulting from lambda expressions being deduced as ``Any``. For instance,
68+
Mypy includes ``disallow_any_expr``/``--disallow-any-expr`` and Pyright includes
69+
``reportUnknownLambdaType``. Both of those options are set to true in the respective
70+
strict modes of those type checkers.
71+
72+
In conclusion:
73+
74+
1. There is no way to explicitly annotate lambda arguments or return values in the
75+
lambdas themselves.
76+
77+
2. However, static typing rules still apply to lambdas, including type deduction.
78+
79+
3. Many lambdas get deduced as ``Any``, which might suppress the reporting of other
80+
type errors.
81+
82+
4. However, many lambdas get deduced fine, and for those it's not a problem.
83+
84+
5. If you want to annotate the type of lambdas, you can bind them and annotate them
85+
there.
86+
87+
6. Most type checkers have a setting that will warn you if anything gets deduced as
88+
``Any``, and you can use that to avoid false negatives relating to lambda.

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Guides
2323
:maxdepth: 1
2424

2525
guides/libraries
26+
guides/lambda
2627
guides/writing_stubs
2728
guides/modernizing
2829
guides/unreachable

0 commit comments

Comments
 (0)