[Python-ideas] A better (simpler) approach to PEP 505

David Mertz mertz at gnosis.cx
Mon Jul 23 13:21:53 EDT 2018


Btw. I *do* realize that the semantics of my suggested NoneAware class is
different from the coalescing syntax.  I just look at whether attribute
access succeeds or fails in that code, not whether the starting value is
specifically None (nor any particular sentinel).

I believe that that behavior better fits the ACTUAL need underlying these
ideas (i.e. potentially deeply nested but messy data; from JSON or similar
sources).  If we wanted to match the semantics of PEP 505 more exactly, it
would be easy enough to compare a current level of the hierarchy to None
specifically rather than check more generically "is this a thing that has
that attribute?"

However, if the PEP 505 semantics really are the ones most desirable (i.e.
including raising AttributeError on things that are
not-None-but-don't-have-attribute), that's perfectly straightforward to
implement using existing syntax and an API very close to what I suggest.

On Mon, Jul 23, 2018 at 11:12 AM David Mertz <mertz at gnosis.cx> wrote:

> The need addressed by PEP 505 is real; it's also MUCH more niche and
> uncommon than something that would merit new syntax.  Moreover, the actual
> legitimate purpose served by the PEP 505 syntax is easily served by
> existing Python simply by using a wrapper class.
>
> Here is a way of solving the "deep attribute access to messy data" problem
> that is:
>
> (1) Much more explicit
> (2) Requires no change in syntax
> (3) Will not be a bug magnet
> (4) Inasmuch as there are semantic traps, they are announced by the use of
> a class whose documentation would be pointed to for readers
>
> The API that could be useful might be something like this:
>
> In [1]: from none_aware import NoneAware
> In [2]: from types import SimpleNamespace
> In [3]: foo = SimpleNamespace()
> In [4]: foo.bar = SimpleNamespace()
> In [5]: foo.bar.baz = SimpleNamespace()
> In [6]: foo.bar.baz.blat = 42
> In [7]: NoneAware(foo).bar.blim
> Out[7]: <none_aware.NoneAware at 0x11156a748>
> In [8]: NoneAware(foo).bar.blim.unbox()
> In [9]: NoneAware(foo).bar.baz.blat.unbox()
> Out[9]: 42
> In [10]: NoneAware(foo).bar.baz.blat
> Out[10]: <none_aware.NoneAware at 0x11157d908>
> In [11]: NoneAware(foo).bar.baz.flam.unbox()
> In [12]: NoneAware(foo).bar.baz.flam
> Out[12]: <none_aware.NoneAware at 0x1115832b0>
>
>
> The particular names I use are nothing special, and better ones might be
> found.  I just called the class NoneAware and the "escape" method
> `.unbox()` because that seemed intuitive at first brush.
>
> I don't disagree that needing to call .unbox() at the end of the chained
> attribute access is a little bit ugly.  But it's a lot less ugly than large
> family of new operators.  And honestly, it's a nice way of being explicit
> about the fact that we're entering then leaving a special world where
> attribute accesses don't fail.
>
> I haven't implemented the equivalent dictionary lookups in the below.
> That would be straightforward, and I'm sure my 5 minute throwaway code
> could be improved in other ways also.  But something better than this in
> the standard library would address ALL the actual needs described in PEP
> 505.  Even the pattern Steve Dower is especially fond of like:
>
> favorite = cfg?.user?.profile?.food ?? "Spam"
>
>
> (i.e. a configuration may be incomplete at any level, if levels are
> missing default favorite food is Spam).  We could simply spell that:
>
> favorite = NoneAware(cfg, "Spam").user.profile.food.unbox()
>
>
> I think that's 14 characters more in this example, but still compact.  We
> could get that down to 2 characters if we used one-letter names for the
> class and method.  I suppose down to zero characters if .unbox() was a
> property.
>
> So completely toy implementation:
>
> class NoneAware(object):
>     def __init__(self, thing, sentinel=None):
>         self.thing = thing
>         self.sentinel = sentinal
>
>     def __getattr__(self, attr):
>         try:
>             return NoneAware(getattr(self.thing, attr))
>         except AttributeError:
>             return NoneAware(self.sentinel)
>
>     def unbox(self):
>         return self.thing
>
> --
> Keeping medicines from the bloodstreams of the sick; food
> from the bellies of the hungry; books from the hands of the
> uneducated; technology from the underdeveloped; and putting
> advocates of freedom in prisons.  Intellectual property is
> to the 21st century what the slave trade was to the 16th.
>


-- 
Keeping medicines from the bloodstreams of the sick; food
from the bellies of the hungry; books from the hands of the
uneducated; technology from the underdeveloped; and putting
advocates of freedom in prisons.  Intellectual property is
to the 21st century what the slave trade was to the 16th.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180723/e185d86d/attachment-0001.html>


More information about the Python-ideas mailing list