diff --git a/docs/guides/index.rst b/docs/guides/index.rst index b13e0348..70dfdac9 100644 --- a/docs/guides/index.rst +++ b/docs/guides/index.rst @@ -12,6 +12,7 @@ Type System Guides :caption: Contents: libraries + lambda writing_stubs modernizing unreachable diff --git a/docs/guides/lambda.rst b/docs/guides/lambda.rst new file mode 100644 index 00000000..65c6074a --- /dev/null +++ b/docs/guides/lambda.rst @@ -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. diff --git a/docs/index.rst b/docs/index.rst index 19117d05..0fcbf9a7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,6 +23,7 @@ Guides :maxdepth: 1 guides/libraries + guides/lambda guides/writing_stubs guides/modernizing guides/unreachable