2012-06-20 8 views
13

Mam problemy z __getattr__. Mam złożoną rekurencyjną bazę kodów, w której ważne jest, aby propagować wyjątki.Python: Dlaczego __getattr__ przechwytuje AttributeErrors?

class A(object): 
    @property 
    def a(self): 
     raise AttributeError('lala') 

    def __getattr__(self, name):  
     print('attr: ', name) 
     return 1  

print(A().a) 

Wyniki w:

('attr: ', 'a') 
1 

Dlaczego to zachowanie? Dlaczego nie zgłoszono wyjątku? To zachowanie nie jest udokumentowane (__getattr__ documentation). getattr() może po prostu użyć A.__dict__. jakieś pomysły?

+2

Powiedziałbym, że niepowodzenie dokumentacji polega na tym, że "AttributeError" może zostać podniesiony w '__get__' (co skutecznie tutaj robisz), ale nie wyjaśnia, jaki efekt ma. Prawdopodobnie powinno się powiedzieć, że 'AttributeError' sygnalizuje, że powinien zachowywać się tak, jakby nie znaleziono tego atrybutu (aby jak zwykle próbowano wykonać kopię" __getattr__ ") - jeśli to nie jest pożądane zachowanie, to powinieneś podnieść coś innego. – James

Odpowiedz

9

Właśnie zmienił kod do

class A(object): 
    @property 
    def a(self): 
     print "trying property..." 
     raise AttributeError('lala') 
    def __getattr__(self, name):  
     print('attr: ', name) 
     return 1  

print(A().a) 

i, jak widzimy, jest nieruchomość rzeczywiście próbował jako pierwszy. Ale jak twierdzi, że tam nie ma (podnosząc AttributeError), __getattr__() jest nazywany "ostatecznością".

Nie jest to dokumentowane w sposób jednoznaczny, ale może być liczone w "Wywoływane, gdy wyszukiwanie atrybutów nie odnalazło atrybutu w zwykłych miejscach".

+0

i jaki jest zalecany sposób, aby to zrobić zamiast tego? Nie używasz właściwości lub używasz "__getattribute__" zamiast "__getattr__"? –

+2

Powiedziałabym, że nie podnosiłam 'AttributeError', ani nie zajmowałam się tym przypadkiem w' __getattr__', lub używałam '__getattribute__' i wołam' super (...) .__ getattribute__', przechwytując 'AttributeError'. – glglgl

+1

Nie mogę się domyślić, dlaczego chciałbyś kiedyś jawnie zgłosić atrybut "AttributeError" w usłudze. Lub nigdzie indziej niż metoda "__ (get | set) attr [ibute] __", naprawdę. –

4

__getattr__ jest wywoływana, gdy dostęp do atrybutu kończy się niepowodzeniem z atrybutem AttributeError. Być może dlatego myślisz, że "łapie" błędy. Jednak nie, to funkcja dostępu do atrybutów Pythona, która je przechwytuje, a następnie wywołuje __getattr__.

Sam w sobie nie powoduje żadnych błędów. Jeśli podniesiesz AttributeError w __getattr__ otrzymasz nieskończoną rekursję.

6

__getattribute__ documentation mówi:

Jeśli klasa definiuje również __getattr__(), ten ostatni nie zostanie wywołana chyba __getattribute__() albo nazywa go wprost lub podnosi AttributeError.

czytam to (przez inclusio unius est exclusio alterius) mówiąc, że dostęp do atrybutu będzie rozmowę __getattr__ jeśli object.__getattribute__ (który jest "nazywa bezwarunkowo wdrożyć atrybut uzyskuje dostęp") dzieje się podnieść AttributeError - czy bezpośrednio lub wewnątrz deskryptora __get__ (np. właściwość fget); zauważ, że __get__ powinien "zwrócić (obliczyć) wartość atrybutu lub podnieść AttributeError wyjątek".

Analogicznie, specjalne metody operatora mogą podnieść NotImplementedError, po czym zostaną wypróbowane inne metody operatora (na przykład __radd__ dla __add__).

6

Korzystanie z właściwości w tej samej klasie jest niebezpieczne, ponieważ może prowadzić do błędów, które są bardzo trudne do debugowania.

Jeśli gracz pobierający nieruchomość rzuca AttributeError, wówczas AttributeError zostaje po cichu schwytany i zostaje wywołana __getattr__. Zwykle powoduje to, że __getattr__ kończy się niepowodzeniem z wyjątkiem, ale jeśli masz wyjątkowo pecha, to nie, a nawet nie będziesz w stanie łatwo prześledzić problemu z powrotem do __getattr__.

Dopóki twoje mienie własnościowe nie jest trywialne, nigdy nie możesz być 100% pewny, że nie wyrzuci AttributeError. Wyjątek może być wyrzucony na kilka poziomów.

Oto co można zrobić:

  1. Unikaj używania właściwości i __getattr__ w tej samej klasie.
  2. Dodaj try ... except blok do wszystkich pobierające własności, które nie są trywialne
  3. Przechowywać pobierające własności proste, więc wiesz, że nie rzuci AttributeError
  4. Napisz swoją własną wersję @property dekoratora, który łapie AttributeError i ponownie wyrzuca go jako RuntimeError.

Zobacz także http://blog.devork.be/2011/06/using-getattr-and-property_17.html

EDIT: W przypadku gdy ktoś rozważa rozwiązanie 4 (czego nie polecam), można to zrobić tak:

def property_(f): 
    def getter(*args, **kwargs): 
     try: 
      return f(*args, **kwargs) 
     except AttributeError as e: 
      raise RuntimeError, "Wrapped AttributeError: " + str(e), sys.exc_info()[2] 

    return property(getter) 

Następnie użyj @property_ zamiast @property w klasach, które zastępują __getattr__.

0

jesteś skazany jakikolwiek podczas łączenia @property z __getattr__:

class Paradise: 
    pass 

class Earth: 
    @property 
    def life(self): 
     print('Checking for paradise (just for fun)') 
     return Paradise.breasts 
    def __getattr__(self, item): 
     print("sorry! {} does not exist in Earth".format(item)) 

earth = Earth() 
try: 
    print('Life in earth: ' + str(earth.life)) 
except AttributeError as e: 
    print('Exception found!: ' + str(e)) 

daje następujący wynik:

Checking for paradise (just for fun) 
sorry! life does not exist in Earth 
Life in earth: None 

Gdy prawdziwy problem był z wywołaniem Paradise.breasts.

__getattr__ jest zawsze wywoływana, gdy wzrasta AtributeError. Treść wyjątku jest ignorowana.

Smutne jest to, że nie ma rozwiązania tego problemu danej hasattr(earth, 'life') powróci True (tylko dlatego __getattr__ jest zdefiniowany), ale nadal będzie osiągnięty przez atrybut „życia”, ponieważ nie istnieje, podczas gdy prawdziwy bazowy problem jest z Paradise.breasts.

Moje częściowe rozwiązanie wymaga użycia bloków try-except z wyjątkiem @property, o których wiadomo, że uderzają w wyjątki od AttributeError.

0

regularnie napotykam na ten problem, ponieważ implementuję __getattr__ i mam wiele metod @property. Oto dekorator wymyśliłem, aby uzyskać bardziej użyteczne komunikat o błędzie:

def replace_attribute_error_with_runtime_error(f): 
    @functools.wraps(f) 
    def wrapped(*args, **kwargs): 
     try: 
      return f(*args, **kwargs) 
     except AttributeError as e: 
      # logging.exception(e) 
      raise RuntimeError(
       '{} failed with an AttributeError: {}'.format(f.__name__, e) 
      ) 
    return wrapped 

i używać go tak:

class C(object): 

    def __getattr__(self, name): 
     ... 

    @property 
    @replace_attribute_error_with_runtime_error 
    def complicated_property(self): 
     ... 

    ... 

Komunikat o błędzie bazowego wyjątku będzie zawierać nazwę klasy, której instancja podniósł bazową AttributeError. Możesz również zalogować się, jeśli chcesz.

+0

W rzeczywistości zrobiłem coś podobnego do obejścia tego i nazwałem to 'safe_property'. Robi to samo, tylko z jednym dekoratorem. –

Powiązane problemy