
[Michael Cuthbert:]
I'm rather surprised though that the typing advantages of the pep have not been emphasized enough.
[Chris Angelico]
That would be because I personally don't use that kind of strict typing, so it's not something I'm really qualified to talk about. Would you like to write up a paragraph or two about it? I could incorporate it verbatim, if you like.
Sure! * * * Late-bound arg defaults also help with proper typing, especially for established code-bases that are incrementally adding typing to their code. For instance, take this untyped example that might take a list of musical pitches and put them in a particular order: def order_pitches(pitches=None): if pitches is None: pitches = [] ... # do reordering referencing "pitches" a lot pitches.sort(key=lambda p: (p.octave, p.name)) return pitches When the editor moves to a typed version, something like this would seem reasonable: def order_pitches(pitches: list[Pitch]|None = None) -> list[Pitch]: ... # same code as above. However, some type checkers (for instance, that of PyCharm 2022) will continue to reveal the type as "list[Pitch]|None" even after type narrowing. (Mypy will correctly type narrow in this example but not in code that checks for `if not hasattr(pitches, '__iter__')` and in many other more complex examples). In this case, authors sometimes need to resort to rewriting code with a new variable name: def order_pitches(pitches: list[Pitch]|None = None) -> list[Pitch]: non_none_pitches: list[Pitch] if pitches is None: non_none_pitches = [] else: non_none_pitches = cast(list[Pitch], pitches) ... # rest of code must be rewritten to use "non_none_pitches" The typed definition also seems to imply that "None" is an acceptable calling type for the function rather than just being a stand-in for an omitted call. A type-checker will allow `order_pitches(None)` to pass, perhaps preventing later refactoring to use a sentinel such as: def order_pitches(pitches: list[Pitch]|MISSING = MISSING) -> list[Pitch]: if pitches is MISSING: pitches = [] ... With the PEP, the process of adding typing does not impact the code, nor imply that "None" is a fine calling signature. def order_pitches(pitches: list[Pitch] => []) -> list[Pitch]: ... # pitches is always list[Pitch] and needs no narrowing pitches.sort(...) return pitches * * * It is true that the tools for automatic type-narrowing continue to get better (in looking up code that broke the type-narrowing with a "no_none..." variable, I found that 2 of 4 places I found where I had done this workaround two years ago no longer needed it for either mypy or PyCharm), but there are still numerous cases where the process of typing around a sentinel type that will be replaced by the correct type for computing still unnecessarily exist.