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 <me...@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.
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/