[Python-ideas] PEP 505: None-aware operators
Steven D'Aprano
steve at pearwood.info
Fri Jul 20 01:22:35 EDT 2018
On Thu, Jul 19, 2018 at 05:32:48PM +0900, Stephen J. Turnbull wrote:
> To be honest, code transformations like this
>
> > class BaseUploadObject(object):
> > def find_content_type(self, filename):
> > ctype, encoding = mimetypes.guess_type(filename)
> > if ctype is None:
> > return 'application/octet-stream'
> > else:
> > return ctype
>
> to this
>
> > class BaseUploadObject(object):
> > def find_content_type(self, filename):
> > ctype, encoding = mimetypes.guess_type(filename)
> > return ctype ?? 'application/octet-stream'
>
> make me cringe.
I don't think that's a good use of ?? and that method should
either remain in the original form, or at most, be re-written using
return ctype if ctypes is not None else 'application/octet-stream'
(although I prefer the original form). There doesn't seem to be any
advantage to ?? in this example except to transform a four-line
imperative-style return statement to a one-line return statement,
trading off vertical space for horizontal. But there's no improvement in
expressiveness that I can see.
> Exactly one of two things is true:
>
> 1. mimetypes.guess_type guarantees that the only falsie it will ever
> return is None, or
>
> 2. it doesn't.
>
> In case 1, "ctype or 'application/octet-stream'" ALSO does the right
> thing.
It does the right thing, but it doesn't look like it does the right
thing.
The ?? operator promises to only transform None, and nothing but None.
To the reader, the `or` operator promises to transform any falsey value.
Is `or` too broad and do too much? There's no way of knowing.
Instead of treating `guess_type` as a black-box, the reader now has to
dig-deep into its documentation and/or implementation to find out which
falsey values it might return and whether or not they ought to be
treated the same as None.
> In case 2, ONLY "ctype or 'application/octet-stream'" does the
> right thing, as few callers of BaseUploadObject.find_content_type will
> be prepared for "", (), [], or any variety of 0 as the return value.
I doubt that guess_type (and hence find_content_type) will return 0 or
[] etc as part of their documented interface (as opposed to a bug), but
if they did, surely we ought to assume that the caller is prepared to
handle its output.
More likely is that it might return the empty string "". At face value I
would expect that a better interface for guess_type would be to return
the empty string rather than None.
But perhaps there is a legitimate reason to treat the empty string as
distinct from None, it should *not* be transformed.
On perhaps the function is avoiding the "failure looks like success"
anti-pattern. Who hasn't been bitten by writing something like this?
chunk = text[text.find('spam')+4:]
It looks okay at a glance: return the chunk of text immediately
following "spam". But if "spam" isn't present at all, it returns the
entire text minus the first three characters, which is probably not what
you wanted.
So there is a good argument to be made that guess_type ought to promise:
- return None if it cannot guess the type;
- otherwise return a non-empty string.
(or that the empty string is itself a legitimate output that ought to be
considered distinct from None and should *not* be transformed).
In that case, `or` reads wrongly, because it either wrongly transforms
"" into the default when it should not, or it suggests to the reader
that it might.
Either way, an explicit test for None -- whether we use "if ... is None"
or the proposed ?? operator -- is better than a test for falsey.
> So I would like to see various examples of code where
>
> 1. in the original code, treating a falsie that is not None the same
> way that None is treated is a detectable bug; and
I don't see that this is relevant. We already had this discussion, when
the ternary if was introduced, and a million times when discussing why
it is better to write "if spam is None" rather than "if not spam".
In other words, we ought to be comparing the expressiveness of
process(spam ?? something)
versus:
process(something if spam is None else spam)
(and similar variations), not against
process(spam if spam else something)
process(spam or something)
both of which do something different from the ?? operator.
--
Steve
More information about the Python-ideas
mailing list