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.
> So... what precisely should be passed to __getattr__, and what exactly are you going to do with it?
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.
To clarify my mistakes in my first post, your example illustrates what I wanted to show:
class A(object):
eggs = "text"
def __getattr__(self, name):
if name == 'cheese':
return "cheddar"
raise AttributeError('%s missing' % name)
@property
def spam(self):
return self.eggs.uper() # Oops.
a = A()
a.spam
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __getattr__
AttributeError: spam missing
This swallows the AttributeError from `eggs.uper()` and it isn't available.
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.
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:
class A(object):
eggs = "text"
def __getattr__(self, name, error):
if name == 'cheese':
return "cheddar"
raise AttributeError('%s missing' % name) from error
...
which I think is useful.
> That's a fairly long and heavy process, and will be quite annoying to
> those writing cross-version code using __getattr__, but it can be done.
This is another thing I hadn't considered, and I have no problem saying this non-backwards-compatible-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.
> One good solution to this is a "guard point" around your property functions.
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.
> > It's a feature. It's why Enum classes can have members named 'value' and
> > 'name'.
> Can you explain further? What's special about value and name?
I'm also very curious about this. I've read the Enum code a few times but it hasn't clicked yet.
> > Perhaps a better approach is to prevent descriptors from leaking
> > AttributeError in the first place? Change the protocol so that if
> > descriptor.__get__ raises AttributeError, it is caught and re-raised as
> > RuntimeError, similar to StopIteration and generators.
> This can't be done globally, because that's how a descriptor can be
> made conditional (it raises AttributeError to say "this attribute does
> not, in fact, exist"). But it's easy enough - and safe enough - to do
> it just for your own module, where you know in advance that any
> AttributeError is a leak.
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.