On Mon, Jul 23, 2018 at 11:12:45AM -0400, David Mertz wrote: > 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
That's one opinion. > (4) Inasmuch as there are semantic traps, they are announced by the use of > a class whose documentation would be pointed to for readers Operators have documentation too. If you think people won't read the documentation for the operator, what makes you think they're read the documentation for the class? > 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> There's the first bug right there. foo.bar.blim ought to raise AttributeError, since there is no blim attribute defined on foo.bar. Reminder: the proposal is for a null-coalescing operator, not an AttributeError suppressing operator. The PEP is explicit that catching AttributeError is rejected: https://www.python.org/dev/peps/pep-0505/#id21 > In [8]: NoneAware(foo).bar.blim.unbox() > In [9]: NoneAware(foo).bar.baz.blat.unbox() > Out[9]: 42 How many bugs will be caused by people forgetting to unbox when they're done? > In [10]: NoneAware(foo).bar.baz.blat > Out[10]: <none_aware.NoneAware at 0x11157d908> > In [11]: NoneAware(foo).bar.baz.flam.unbox() That ought to be AttributeError again, since there is no "flam" attribute defined on foo.bar.baz. > 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. But we aren't entering such a world, at least not in PEP 505. Attribute access can fail. spam.eggs = 42 spam?.eggs?.upper is still going to raise AttributeError, because eggs is not None, it is an int, and ints don't have an attribute "upper". > 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. How does your class implement short-circuit behaviour? > 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 we don't like operators like ?? because its too cryptic, but you're happy to have one-character class and property names. favorite = N(cfg, "Spam").user.profile.food.u What happens if you have an attribute that happens to be called "unbox" in your attribute look-up chain? result = NoneAware(something, "default").spam.foo.unbox.eggs.unbox() -- Steve _______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/