2016-04-12 10 views
2

Ich habe ein Problem mit AttributeErrors in einem @property in Kombination mit __getattr__() in Python angehoben:AttributeErrors: unerwünschte Wechselwirkung zwischen @property und __getattr__

Beispielcode:

>>> def deeply_nested_factory_fn(): 
...  a = 2 
...  return a.invalid_attr 
... 
>>> class Test(object): 
...  def __getattr__(self, name): 
...   if name == 'abc': 
...    return 'abc' 
...   raise AttributeError("'Test' object has no attribute '%s'" % name) 
...  @property 
...  def my_prop(self): 
...   return deeply_nested_factory_fn() 
... 
>>> test = Test() 
>>> test.my_prop 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 5, in __getattr__ 
AttributeError: 'Test' object has no attribute 'my_prop' 

In meinem Fall ist dies ein sehr irreführende Fehlermeldung, weil es die Tatsache versteckt, dass deeply_nested_factory_fn() einen Fehler hat.


Basierend auf der Idee in Tadhg McDonald-Jensen Antwort, meine zur Zeit beste Lösung ist die folgende. Irgendwelche Hinweise darauf, wie das __main__. Präfix zu AttributeError und der Verweis auf attributeErrorCatcher im Traceback loszuwerden ist, würden sehr geschätzt werden.

>>> def catchAttributeErrors(func): 
...  AttributeError_org = AttributeError 
...  def attributeErrorCatcher(*args, **kwargs): 
...   try: 
...    return func(*args, **kwargs) 
...   except AttributeError_org as e: 
...    import sys 
...    class AttributeError(Exception): 
...     pass 
...    etype, value, tb = sys.exc_info() 
...    raise AttributeError(e).with_traceback(tb.tb_next) from None 
...  return attributeErrorCatcher 
... 
>>> def deeply_nested_factory_fn(): 
...  a = 2 
...  return a.invalid_attr 
... 
>>> class Test(object): 
...  def __getattr__(self, name): 
...   if name == 'abc': 
...    # computing come other attributes 
...    return 'abc' 
...   raise AttributeError("'Test' object has no attribute '%s'" % name) 
...  @property 
...  @catchAttributeErrors 
...  def my_prop(self): 
...   return deeply_nested_factory_fn() 
... 
>>> class Test1(object): 
...  def __init__(self): 
...   test = Test() 
...   test.my_prop 
... 
>>> test1 = Test1() 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "<stdin>", line 4, in __init__ 
    File "<stdin>", line 11, in attributeErrorCatcher 
    File "<stdin>", line 10, in my_prop 
    File "<stdin>", line 3, in deeply_nested_factory_fn 
__main__.AttributeError: 'int' object has no attribute 'invalid_attr' 
+0

Sie müssten '__qualname__ =" AttributeError "' in der Klassendefinition tun, um den '__main__' Teil zu entfernen, aber vertrauen Sie mir ** Sie wollen ** den Fehler nicht einfach nur AttributeError sagen, denn dann riskieren Sie, verwirrt zu werden warum das heck 'except AttributeError' kein AttributError gefunden hat. –

Antwort

1

Sie können eine benutzerdefinierte Ausnahme erstellen, die ein AttributeError zu sein scheint, wird aber __getattr__ nicht auslösen, da es nicht wirklich ein AttributeError ist.

AKTUALISIERT: die Zurückverfolgungs Nachricht wird durch die Neuzuweisung der .__traceback__ Attribut vor der erneuten Anhebung der Fehler stark verbessert:

class AttributeError_alt(Exception): 
    @classmethod 
    def wrapper(err_type, f): 
     """wraps a function to reraise an AttributeError as the alternate type""" 
     @functools.wraps(f) 
     def alt_AttrError_wrapper(*args,**kw): 
      try: 
       return f(*args,**kw) 
      except AttributeError as e: 
       new_err = err_type(e) 
       new_err.__traceback__ = e.__traceback__.tb_next 
       raise new_err from None 
     return alt_AttrError_wrapper 

Dann, wenn Sie Ihre Immobilie als definieren:

@property 
@AttributeError_alt.wrapper 
def my_prop(self): 
    return deeply_nested_factory_fn() 

und die Fehlermeldung Sie werden sehen wie folgt aus:

Traceback (most recent call last): 
    File ".../test.py", line 34, in <module> 
    test.my_prop 
    File ".../test.py", line 14, in alt_AttrError_wrapper 
    raise new_err from None 
    File ".../test.py", line 30, in my_prop 
    return deeply_nested_factory_fn() 
    File ".../test.py", line 20, in deeply_nested_factory_fn 
    return a.invalid_attr 
AttributeError_alt: 'int' object has no attribute 'invalid_attr' 

beachten Sie die Re ist eine Zeile für raise new_err from None aber es ist oberhalb der Linien von innerhalb der Eigenschaft Anruf. Es würde auch eine Zeile für return f(*args,**kw) sein, aber das wird mit .tb_next weggelassen.


Ich bin ziemlich sicher, dass die beste Lösung für Ihr Problem already been suggested hat und Sie können die previous revision meiner Antwort, warum ich glaube, es ist die beste Option.Obwohl ehrlich, wenn ein Fehler ist, der dann eine blutige RuntimeError auf die eine verkettete erhöhen falsch unterdrückt wird, die sonst verborgen bleiben würden:

def assert_no_AttributeError(f): 
    @functools.wraps(f) 
    def assert_no_AttrError_wrapper(*args,**kw): 
     try: 
      return f(*args,**kw) 
     except AttributeError as e: 
      e.__traceback__ = e.__traceback__.tb_next 
      raise RuntimeError("AttributeError was incorrectly raised") from e 
    return assert_no_AttrError_wrapper 

dann, wenn Sie Ihre Immobilie mit diesem dekorieren erhalten Sie einen Fehler wie diese:

Traceback (most recent call last): 
    File ".../test.py", line 27, in my_prop 
    return deeply_nested_factory_fn() 
    File ".../test.py", line 17, in deeply_nested_factory_fn 
    return a.invalid_attr 
AttributeError: 'int' object has no attribute 'invalid_attr' 

The above exception was the direct cause of the following exception: 

Traceback (most recent call last): 
    File ".../test.py", line 32, in <module> 
    x.my_prop 
    File ".../test.py", line 11, in assert_no_AttrError_wrapper 
    raise RuntimeError("AttributeError was incorrectly raised") from e 
RuntimeError: AttributeError was incorrectly raised 

Obwohl, wenn Sie mehr als nur eine Sache erwarten ein Attribut erhöhen dann mögen Sie vielleicht, um eine Überlastung nur __getattribute__ für jeden eigenartigen Fehler für alle Lookups zu überprüfen:

def __getattribute__(self,attr): 
    try: 
     return object.__getattribute__(self,attr) 
    except AttributeError as e: 
     if str(e) == "{0.__class__.__name__!r} object has no attribute {1!r}".format(self,attr): 
      raise #normal case of "attribute not found" 
     else: #if the error message was anything else then it *causes* a RuntimeError 
      raise RuntimeError("Unexpected AttributeError") from e 

Auf diese Weise, wenn etwas schief geht, das Sie nicht erwarten, werden Sie es sofort wissen!

+0

Das macht die Sache nicht wirklich viel besser: Der Stack-Trace ist noch weit weg! Beachten Sie, dass der Fehler in der Funktion "deep_nested_factory_fn" nicht in der Funktion "inner" liegt, wie dies durch die von Ihrer Lösung erzeugte Stapelverfolgung angezeigt wird. - Das heißt, ich würde Ihre Idee wirklich mögen, die 'sys.exit (1)' vermeidet, wenn das Stacktrace-Problem behoben werden könnte. – ARF

+0

Nach einigem Kopfkratzen kam ich zu einer Lösung, über die ich nicht allzu unglücklich bin. Wenn Sie noch interessiert sind, schauen Sie sich die modifizierte Frage an. – ARF

+0

Das wird Leute davon abhalten, 'außer AttributeError' zu machen, um diese Ausnahme zu erfassen. – user2357112

2

Wenn Sie bereit sind ausschließlich Klassen neuen Stil zu verwenden, könnten Sie __getattribute__ Überlastung statt __getattr__:

class Test(object): 
    def __getattribute__(self, name): 
     if name == 'abc': 
      return 'abc' 
     else: 
      return object.__getattribute__(self, name) 
    @property 
    def my_prop(self): 
     return deeply_nested_factory_fn() 

nun Ihre Stack-Trace richtig deeply_nested_factory_fn erwähnen wird.

Traceback (most recent call last): 
    File "C:\python\myprogram.py", line 16, in <module> 
    test.my_prop 
    File "C:\python\myprogram.py", line 10, in __getattribute__ 
    return object.__getattribute__(self, name) 
    File "C:\python\myprogram.py", line 13, in my_prop 
    return deeply_nested_factory_fn() 
    File "C:\python\myprogram.py", line 3, in deeply_nested_factory_fn 
    return a.invalid_attr 
AttributeError: 'int' object has no attribute 'invalid_attr' 
+0

Danke, mein Problem mit dieser Lösung ist der Leistungseinbruch für andere Attribute. (Auf einige davon kann in Schleifen zugegriffen werden.) – ARF

+0

@ARF, wenn Leistung ein Problem ist, dann erhalten Sie lokale Verweise auf die häufig zugegriffenen Attribute. Ich empfehle das sogar _ohne _ die leichte Verlangsamung durch Überladen von '__getattribute__'. –

+0

Sie meinen mit 'a = test.a' und dann mit' a' statt 'test.a'? – ARF

Verwandte Themen