<div dir="ltr">First, I apologize for the poor post. Your corrections were exactly correct: This is only relevant in the context of properties/descriptors, and the property swallows the error message and it isn't printed to screen.  I should not be typing without testing.<div><br></div><div>> <span style="font-size:12.8px">So... what precisely should be passed to __getattr__, and what exactly </span><span style="font-size:12.8px">are you going to do with it?</span></div><div><span style="font-size:12.8px">I'd say the error instance that caused __getattr__ to be raised should be passed into __getattr__, but I don't have a strong opinion on that. I'll assume that's true for the rest of this post, however.<br></span><div><div><br></div><div>To clarify my mistakes in my first post, your example illustrates what I wanted to show:</div><div><span style="font-size:12.8px">class A(object):</span><br style="font-size:12.8px"><span style="font-size:12.8px">    eggs = "text"</span><br style="font-size:12.8px"><span style="font-size:12.8px">    def __getattr__(self, name):</span><br style="font-size:12.8px"><span style="font-size:12.8px">        if name == 'cheese':</span><br style="font-size:12.8px"><span style="font-size:12.8px">            return "cheddar"</span><br style="font-size:12.8px"><span style="font-size:12.8px">        raise AttributeError('%s missing' % name)</span><br style="font-size:12.8px"><span style="font-size:12.8px">    @property</span><br style="font-size:12.8px"><span style="font-size:12.8px">    def spam(self):</span><br style="font-size:12.8px"><span style="font-size:12.8px">        return self.eggs.uper() # Oops.</span><br style="font-size:12.8px"><span style="font-size:12.8px">a = A()</span><br style="font-size:12.8px"><span style="font-size:12.8px">a.spam</span><br></div><div><span style="font-size:12.8px"><br></span></div><div><span style="font-size:12.8px">Traceback (most recent call last):</span><br style="font-size:12.8px"><span style="font-size:12.8px">  File "<stdin>", line 1, in <module></span><br style="font-size:12.8px"><span style="font-size:12.8px">  File "<stdin>", line 6, in __getattr__</span><br style="font-size:12.8px"><span style="font-size:12.8px">AttributeError: spam missing</span><span style="font-size:12.8px"><br></span></div><div><span style="font-size:12.8px"><br></span></div><div><span style="font-size:12.8px">This swallows the AttributeError from `eggs.uper()` and it isn't available.</span></div><div><span style="font-size:12.8px"><br></span></div><div><span style="font-size:12.8px">Even if it were available, I see your point that it may not be especially useful. In one iteration of my code I was looking through the stack trace using the traceback module to find the error I wanted, but I quickly decided that was a bad idea because I couldn't reliably find the error. However, with the full error, it would (I think) be trivial to find the relevant error in the stack trace. With the full stack trace, I would hope that you could properly do any error handling you wanted.</span></div><div><span style="font-size:12.8px"><br></span></div><div><span style="font-size:12.8px">However, if the error was available in __getattr__, we could at least `raise from` so that the error isn't completely swallowed. I.e. your example would be slightly modified like this:</span><span style="font-size:12.8px"><br></span></div><div><span style="font-size:12.8px">class A(object):</span><br style="font-size:12.8px"><span style="font-size:12.8px">    eggs = "text"</span><br style="font-size:12.8px"><span style="font-size:12.8px">    def __getattr__(self, name, error):</span><br style="font-size:12.8px"><span style="font-size:12.8px">        if name == 'cheese':</span><br style="font-size:12.8px"><span style="font-size:12.8px">            return "cheddar"</span><br style="font-size:12.8px"><span style="font-size:12.8px">        raise AttributeError('%s missing' % name) from error</span><span style="font-size:12.8px"><br></span></div><div><span style="font-size:12.8px">...</span></div><div><span style="font-size:12.8px"><br></span></div><div><span style="font-size:12.8px">which I think is useful.</span></div><div><span style="font-size:12.8px"><br></span></div><div><span style="font-size:12.8px"><br></span></div></div></div><div><span style="font-size:12.8px">> </span><span style="font-size:12.8px">That's a fairly long and heavy process, and will be quite annoying to</span></div><div><span style="font-size:12.8px">> those writing cross-version code using __getattr__, but it can be done.</span></div><div><span style="font-size:12.8px">This is another thing I hadn't considered, and I have no problem saying this non-backwards-compatible-<wbr>change just isn't worth it. Maybe if there are multiple small updates to error handling it could be worth it (which I believe I read was something the devs care quite a bit about atm), but I don't think that this change is a huge deal.</span></div><div><span style="font-size:12.8px"><br></span></div><div><span style="font-size:12.8px"><br></span></div><div><span style="font-size:12.8px">> </span><span style="font-size:12.8px">One good solution to this is a "guard point" around your property functions.</span></div><div><span style="font-size:12.8px">I am using a very similar decorator (modified from the one you gave me a month or so ago) in my code now, and functionally it works great.  There is something that feels a bit off / like a hack to me though, and maybe it's that I am simply "renaming" the error from one I can't handle (due to name conflicts) to one I can. But the modification is just name change -- if I can handle the RuntimeError correctly, I feel like I should have just been able to handle the original AttributeError correctly (because in practice they should be raising an error in response to the exact same problem).   That said, your decorator works great and gives me the functionality I needed.</span></div><div><span style="font-size:12.8px"><br></span></div><div><span style="font-size:12.8px"><br></span></div><div><span class="gmail-">> > It's a feature.  It's why Enum classes can have members named 'value' and<br>> > 'name'.<br></span>> Can you explain further? What's special about value and name?</div><div>I'm also very curious about this. I've read the Enum code a few times but it hasn't clicked yet.</div><div><br></div><div><br></div><div><span style="color:rgb(80,0,80);font-size:12.8px">> > Perhaps a better approach is to prevent descriptors from leaking</span><br style="color:rgb(80,0,80);font-size:12.8px"><span style="color:rgb(80,0,80);font-size:12.8px">> > AttributeError in the first place? Change the protocol so that if</span><br style="color:rgb(80,0,80);font-size:12.8px"><span style="color:rgb(80,0,80);font-size:12.8px">> > descriptor.__get__ raises AttributeError, it is caught and re-raised as</span><br style="color:rgb(80,0,80);font-size:12.8px"><span style="color:rgb(80,0,80);font-size:12.8px">> > RuntimeError, similar to StopIteration and generators.</span><br></div><div><span style="font-size:12.8px">> This can't be done globally, because that's how a descriptor can be</span><br style="font-size:12.8px"><span style="font-size:12.8px">> made conditional (it raises AttributeError to say "this attribute does</span><br style="font-size:12.8px"><span style="font-size:12.8px">> not, in fact, exist"). But it's easy enough - and safe enough - to do</span><br style="font-size:12.8px"><span style="font-size:12.8px">> it just for your own module, where you know in advance that any</span><br style="font-size:12.8px"><span style="font-size:12.8px">> AttributeError is a leak.</span><br></div><div><span style="font-size:12.8px">I have a pretty large codebase with a few different functionalities, so I'm hesitant to say "any AttributeError is a leak" in an object that might affect / be affected by other packages/functionalities.  One other thing I really like about the `noleak` decorator is that I can change the AttributeError to a custom MyCustomError, which allows me to handle MyCustomError precisely where it should be handled.  Also, maybe I'm just not fully understanding the locality of re-raising another error from descriptor.__get__ because I haven't completely wrapped my head around it yet.</span></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Mon, Jun 19, 2017 at 10:10 PM, Chris Angelico <span dir="ltr"><<a href="mailto:rosuav@gmail.com" target="_blank">rosuav@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span class="">On Tue, Jun 20, 2017 at 12:26 PM, Steven D'Aprano <<a href="mailto:steve@pearwood.info">steve@pearwood.info</a>> wrote:<br>
> On Tue, Jun 20, 2017 at 11:31:34AM +1000, Chris Angelico wrote:<br>
><br>
>> Why not just write cross-version-compatible code as<br>
>><br>
>> def __getattr__(self, name, error=None):<br>
>><br>
>> ? Is there something special about getattr?<br>
><br>
> You've still got to write it in the first place. That's a pain,<br>
> especially since (1) it doesn't do you any good before 3.7 if not later,<br>
> and (2) even if this error parameter is useful (which is yet to be<br>
> established), it's a pretty specialised use. Most of the time, you<br>
> already know the name that failed (its the one being looked up).<br>
<br>
</span>Gotcha, yep. I was just confused by your two-parter that made it look<br>
like it would be hard (or impossible) to write code that would work on<br>
both 3.6 and the new protocol.<br>
<span class=""><br>
> Perhaps a better approach is to prevent descriptors from leaking<br>
> AttributeError in the first place? Change the protocol so that if<br>
> descriptor.__get__ raises AttributeError, it is caught and re-raised as<br>
> RuntimeError, similar to StopIteration and generators.<br>
<br>
</span>This can't be done globally, because that's how a descriptor can be<br>
made conditional (it raises AttributeError to say "this attribute does<br>
not, in fact, exist"). But it's easy enough - and safe enough - to do<br>
it just for your own module, where you know in advance that any<br>
AttributeError is a leak. The way generators and StopIteration<br>
interact was more easily fixed, because generators have two legitimate<br>
ways to emit data (yield and return), but there's no easy way for a<br>
magic method to say "I don't have anything to return" other than an<br>
exception.<br>
<br>
Well, that's not strictly true. In JavaScript, they don't raise<br>
StopIteration from iterators - they always return a pair of values<br>
("done" and the actual value, where "done" is either false for a yield<br>
or true for a StopIteration). That complicates the normal case but it<br>
does make the unusual case a bit easier. Also, it's utterly and<br>
fundamentally incompatible with the current system, so it'd have to be<br>
a brand new competing protocol.<br>
<br>
ChrisA<br>
<div class="HOEnZb"><div class="h5">______________________________<wbr>_________________<br>
Python-ideas mailing list<br>
<a href="mailto:Python-ideas@python.org">Python-ideas@python.org</a><br>
<a href="https://mail.python.org/mailman/listinfo/python-ideas" rel="noreferrer" target="_blank">https://mail.python.org/<wbr>mailman/listinfo/python-ideas</a><br>
Code of Conduct: <a href="http://python.org/psf/codeofconduct/" rel="noreferrer" target="_blank">http://python.org/psf/<wbr>codeofconduct/</a><br>
</div></div></blockquote></div><br></div>