|
| 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. |
0 commit comments