Re: PEP 505 (None-aware operators) for Python 3.11
Philipp Burch <[email protected]> Wed, 21 Sep 2022 14:33:50 +0200
| Newsgroups | gmane.comp.python.devel |
|---|---|
| Message-ID | <[email protected]> |
Hi Steven!
On 21.09.22 13:17, Steven D'Aprano wrote:
> The distinction you make between user-defined and non-user-defined
> classes doesn't hold water. If you allow that (say) `int|None` **might**
> be acceptable, then why would `Integer|None` **necessarily** be lazy and
> bad just because int is written in C and built into the intepreter while
> Integer is written in Python by a user?
>
> The laziness/badness of `SomeType|None` must depend on the semantics of
> SomeType, not the implementation language or who wrote it.
>
> "Our API was lazy and bad because it uses None, but then it got accepted
> into Python as a builtin, so now it is no longer lazy or bad." /s
I considered a while if "user-defined types/classes" is the right
wording, but didn't find a better alternative. The point I'm making is
that `X|None` could especially then hide bugs if X defines attributes
that will often be accessed in a chain. For int or float, there are only
a few methods, which are arguably rarely used and chaining is probably
not used at all. Plus, providing nulltypes for primitives would feel
very awkward.
Consider
```
x: int | None = 5
# ...
if x is not None:
y = 1 << x.bit_cont()
```
The typo with bit_cont() is hidden whenever x is None, so it might be
missed by unit tests with limited coverage. However, using a nullable
variant of it would probably make it look like this:
```
class Int(int):
# ...
class NoneInt(Int):
# ...
x = Int(5) # With the option for NoneInt()
# ...
x_bit_count = x.bit_cont()
if x_bit_count == NoneInt:
y = 1 << x_bit_count
```
This is certainly not pretty for something basic as an int, plus there
will most likely be a performance hit when replacing every int by Int(),
not to mention compatibility issues with APIs/libraries expecting plain int.
On the other hand, more complex classes like in the mentioned publisher
API could indeed profit from such null-types, since a chain like
```
book?.publisher?.owner?.namme
```
will hide the typo with namme whenever any of book, publisher or owner
is None, so there is much more potential here to miss it in a unit test.
This is actually the distinction that I wanted to make, not in the
technical sense if a class is defined in the standard library or not.
>> The problem is, that I never actually thought about his suggested way.
>
> Some people have, and found that it doesn't always simplify the API that
> much, or at all. If you end up replacing `obj is None` with `obj == NULL`
> or `obj.isnull()` then you haven't really gained much.
Certainly. But without None-aware operators, the gain in readability can
be large, if you can just write
```
name = book.publisher.owner.name
if name is not None: # No NoneStr here, see above
# ...
```
versus
```
if (book is not None) and (book.publisher is not None) and ...:
# ...
```
Just to be clear: I've been thankful for the comment and explanation of
Steve Dower, because I've never thought about it that way and now see
constructs in my code, where such a concept really would have simplified
things and improved robustness. For others to profit as well, I would be
glad to see this mentioned in the docs, so that it can be found even
when not actually looking for it.
But I certainly don't want to say that this concept must be followed
everywhere or that "X|None" is necessarily a bad thing. After all, I'd
still love to see Python supporting such None-aware operators.
Best regards,
Philipp