On 2020-03-02 4:03 p.m., Andrew Barnert
wrote:
On Mar 2, 2020, at 09:26, Soni L. <fakedme+py@gmail.com>
wrote:
On 2020-03-02 2:04 p.m., Andrew Barnert
wrote:
On Mar 2, 2020, at 08:40, Soni
L. <fakedme+py@gmail.com> wrote:
> > All operations on
None should raise a NoneError,
So every function in every type
implemented in C or in Python, whether part of Python or
third-party, that has code like this:
if not isisntance(arg,
numbers.Integral):
raise TypeError(f"can
only spam integers, not '{arg!r}'")
… has to change to test if arg
is None and raise a different error. Otherwise, you’re not
going to catch the error from + in your example if
g.foo(h) is None.
None can have __radd__ or whatnot as well, no? (read:
please don't directly raise TypeError, [redacted].)
That will help some types, but not all, with +.
But, more importantly, that only helps with (reversible)
operators. It does nothing for int(x) raising TypeError when x
is not string/byteslike/number/something with __int__. Or when
it doesn’t meet the (different) requirements for the first or
the second argument of int.from_bytes. And so on for a bunch of
other methods and class methods. And that’s just one type; the
same is true for lots of other types, and plain old
functions—hundreds just in the builtins, zillions in third-party
code.
The fact that you have a partial solution for a tiny subset
of the problem doesn’t help. If you want to make all operations
that raise TypeError on None instead raise NoneError, you need
to come up with a way to do that, and I don’t see any way that
doesn’t involve rewriting zillions of lines of code both in
Python itself and in third-party libraries and applications.
> which should be a TypeError
for backwards compatibility.
But the most common errors
caused by not checking for None are AttributeError. If you
turn these into a subclass of TypeError, that won’t be
backward compatible. Others are ValueError, and that won’t
be backward compatible either.
Hm. So, my original idea was to have a NoneError and MI
(Multiple Inheritance) it with TypeError, ValueError,
LookupError, AttributeError, etc. Then I checked "None[1]"
and found that it raised TypeError, so I thought all of
None's unimplemented semantics provided TypeError, and
didn't realize attribute lookup was different. Oh well .-.
This is a pretty serious problem with the proposal. Do you
have an answer beyond “oh well”, or does that mean you’re giving
up on the idea, or that you think we should go ahead with the
idea even though we know it can’t work, or what?
Where does None do ValueError,
tho? I haven't seen that one.
The most recent ones I’ve seen came from NumPy and
TensorFlow, both of which raise ValueError from some methods if
you have an array of type object and have any None values, with
a message like "ValueError: None values not supported." I’m sure
there are others; this is just the first one that occurred to
me.
When I was thinking about this I was thinking of the direct
operations like attribute, indexing, etc, so what if we don't bother
with TypeError and ValueError that aren't related to direct
operations on None?
e.g.:
adding a None with something, or having None added to something, are
direct operations on None -> raise.
int(None) is *NOT* a direct operation on None -> doesn't raise.
(but may still raise NoneError if we decide to do so through
__int__. but honestly I kinda feel like __int__ is a mistake/wart
and might make a separate post about this if there's interest.)
indexing a None is a direct operation on None -> raise.
indexing *with* a None is *NOT* a direct operation on None - doesn't
raise. (this also solves some of the problems you listed below)
etc.
This seems more in-line with Python in general, and now I regret
saying that "All operations on None should raise a NoneError"
because I didn't realize how broad that actually was.
So to refine it down a bit: "All direct operations on None, such as
attribute access, indexing and mathematical operations, but not
integer conversion, should raise a NoneError". This includes weird
ones like "await None" and "yield from None" and perhaps even "with
None". Does this sound better?
[... hm, after writing this I realized I said "operations *on*
None", not "operations *with* None". it seems weird to consider
"int(None)" or "{}[None]" an operation *on* None. while they're
indeed operations *with* None, I wouldn't claim that e.g.
"[].append({})" is an operation *on* a dict just because there's a
dict, so why should I do so with None? I'd honestly say this one is
on you for misunderstanding me :v]
> we can then look into
merging the proposals of None-aware operators and
Exception-aware operators such that the Exception-aware
operators fallback to NoneError if no exception type is
provided.
How are you going to provide an
exception type for most of the None-aware operators? For
example, in a?[b][c]?[d], where do the exception types go?
Without solving that, how do you merge the two proposals?
> we should also look into
making "except" default to using NoneError instead of
BaseException, with a future flag.
Why? That sounds like a terrible
idea. Most uses of bare except are bad code, but that
doesn’t mean spuriously breaking all of that code and
forcing people to deal with a problem that may never have
come up in their production code is a good idea. And
meanwhile, the good uses of bare except—quick&dirty
code at the REPL, exception handlers that access the
current exception through other means, etc.—would all
break. And what code would benefit?
I don't know if these are good ideas, that's why I used
the expression "look into".
OK, but surely you must have some idea for why it might be
useful or you wouldn’t have suggested looking into it? So what
is that idea?
I have a feeling some ppl would appreciate one or the other (or
both), so I figured it was a good idea to mention the possibility.
fwiw, assuming we had
exception-aware operators, I believe a?[b][c]?[d] wouldn't
be possible, but I know a?[b]?[c]?[d] would become
a[b][c][d]?:None (or a[b][c][d]?NoneError:None to be
explicit.)
Even in that case it’s still not the same thing. For example, if
any of b, c, or d is None, the none-aware operators will still
raise, but your exception-aware version will not.
So, the fact that exception-aware operators could replace
some but not most uses of none-aware operators, and would be
inaccurate even when they can be used, doesn’t seem very
promising.
All in all, this whole NoneError thing seems like it could be
a useful design for a brand-new Python-like language, but I
can’t see how it can be retrofitted usefully into Python.
This might well be true! I searched around and couldn't find
anything about a hypothetical new NoneError exception, so I figured
it probably hasn't been posted before and thought it was worth a
shot.
_______________________________________________