|
| 1 | +***************************************** |
| 2 | +The Trouble (Or Lack Thereof) With Lambda |
| 3 | +***************************************** |
1 | 4 |
|
| 5 | +:ref:`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 | +There are some workarounds to this problem, which all involve assigning the lambda to |
| 29 | +something, in one way or another, and annotating that. This is a bit unfortunate, |
| 30 | +because the idiomatic use of a lambda involves not doing that. In fact, at that point |
| 31 | +you might as well just define a normal function. Let's call that our first workaround. |
| 32 | + |
| 33 | +``def f(x: object) -> object: return x`` |
| 34 | + |
| 35 | +The second workaround is equivalent: assigning the lambda to a variable, and annotating |
| 36 | +the type of that variable with a :ref:`Callable` |
| 37 | + |
| 38 | +``f: Callable[[object], object] lambda x: x`` |
| 39 | + |
| 40 | +:ref:`Type comments on function definitions` do not actually work on lambda, nor do |
| 41 | +normal :ref:`Type comments` help (although you can use a type commment on an assignment |
| 42 | +to a variable with a lambda, of course; however this will have to be the Callable |
| 43 | +syntax and not the function-arrow special one). |
| 44 | + |
| 45 | +Most type checkers include an option to emit a warning if they aren't able to deduce |
| 46 | +the type of an expression; this should be helpful if you want to avoid silent uncaught |
| 47 | +type errors resulting from lambda expressions being deduced as ``Any``. |
| 48 | + |
| 49 | +In conclusion: |
| 50 | + |
| 51 | +1. There is no way to explicitly annotation lambda arguments or return values in the |
| 52 | +lmabdas themselves. |
| 53 | + |
| 54 | +2. However, static typing rules still apply to lambdas, including type deduction. |
| 55 | + |
| 56 | +3. Many lambdas get deduced as ``Any``, which might suppress the reporting of other |
| 57 | +type errors. |
| 58 | + |
| 59 | +4. However, many lambdas get deduced fine, and for those it's not a problem. |
| 60 | + |
| 61 | +5. If you want to annotate the type of lambdas, you can bind them and annotate them |
| 62 | +there. |
| 63 | + |
| 64 | +6. Most type checkers have a setting that will warn you if anything gets deduced as |
| 65 | +``Any``, and you can use that to avoid false negatives relating to lambda. |
0 commit comments