[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