Piszę własny kontener, który musi dać dostęp do słownika wewnątrz poprzez wywołania atrybutów. Typowe zastosowanie kontenera wyglądałoby następująco:
dict_container = DictContainer()
dict_container['foo'] = bar
...
print dict_container.foo
Wiem, że pisanie czegoś takiego może być głupie, ale taką funkcjonalność muszę zapewnić. Myślałem o wdrożeniu tego w następujący sposób:
def __getattribute__(self, item):
try:
return object.__getattribute__(item)
except AttributeError:
try:
return self.dict[item]
except KeyError:
print "The object doesn't have such attribute"
Nie jestem pewien, czy zagnieżdżone bloki try / except są dobrą praktyką, więc innym sposobem byłoby użycie hasattr()
i has_key()
:
def __getattribute__(self, item):
if hasattr(self, item):
return object.__getattribute__(item)
else:
if self.dict.has_key(item):
return self.dict[item]
else:
raise AttributeError("some customised error")
Lub użyj jednego z nich i jednego spróbuj złapać blok w ten sposób:
def __getattribute__(self, item):
if hasattr(self, item):
return object.__getattribute__(item)
else:
try:
return self.dict[item]
except KeyError:
raise AttributeError("some customised error")
Która opcja jest najbardziej pytoniczna i elegancka?
if 'foo' in dict_container:
. Amen.Odpowiedzi:
Twój pierwszy przykład jest w porządku. Nawet oficjalna dokumentacja Pythona zaleca ten styl znany jako EAFP .
Osobiście wolę unikać zagnieżdżania, gdy nie jest to konieczne:
def __getattribute__(self, item): try: return object.__getattribute__(item) except AttributeError: pass # fallback to dict try: return self.dict[item] except KeyError: raise AttributeError("The object doesn't have such attribute") from None
PS.
has_key()
przez długi czas był przestarzały w Pythonie 2. Użyjitem in self.dict
zamiast tego.źródło
return object.__getattribute__(item)
jest niepoprawny i zwróci,TypeError
ponieważ przekazywana jest zła liczba argumentów. Zamiast tego powinno byćreturn object.__getattribute__(self, item)
.from None
oznacza w ostatnim wierszu?Podczas gdy w Javie rzeczywiście zła praktyka jest używanie wyjątków do kontroli przepływu (głównie dlatego, że wyjątki zmuszają jvm do gromadzenia zasobów ( więcej tutaj )), w Pythonie obowiązują 2 ważne zasady: Duck Typing i EAFP . Zasadniczo oznacza to, że zachęcamy do próbowania używania obiektu w sposób, w jaki myślisz, że będzie działał, i radzenia sobie, gdy rzeczy nie są takie.
Podsumowując, jedynym problemem byłoby zbyt duże wcięcie kodu. Jeśli masz na to ochotę, spróbuj uprościć niektóre zagnieżdżenia, takie jak lqc sugerowane w sugerowanej powyżej odpowiedzi .
źródło
Tylko bądź ostrożny - w tym przypadku najpierw
finally
jest dotykany, ALE też pomijany.def a(z): try: 100/z except ZeroDivisionError: try: print('x') finally: return 42 finally: return 1 In [1]: a(0) x Out[1]: 1
źródło
finally
bloki są wykonywanea(0)
, alefinally-return
zwracany jest tylko rodzic .W twoim konkretnym przykładzie nie musisz ich zagnieżdżać. Jeśli wyrażenie w
try
bloku powiedzie się, funkcja zwróci, więc kod po całym bloku try / except zostanie uruchomiony tylko wtedy, gdy pierwsza próba się nie powiedzie. Możesz więc po prostu:def __getattribute__(self, item): try: return object.__getattribute__(item) except AttributeError: pass # execution only reaches here when try block raised AttributeError try: return self.dict[item] except KeyError: print "The object doesn't have such attribute"
Zagnieżdżanie ich nie jest złe, ale czuję, że pozostawienie go płaskiego sprawia, że struktura jest bardziej przejrzysta: po kolei próbujesz serii rzeczy i zwracasz pierwszą, która działa.
Nawiasem mówiąc, możesz pomyśleć o tym, czy naprawdę chcesz użyć
__getattribute__
zamiast__getattr__
tutaj. Używanie__getattr__
uprości sprawę, ponieważ będziesz wiedział, że normalny proces wyszukiwania atrybutów już się nie powiódł.źródło
Moim zdaniem byłby to najbardziej Pythonowy sposób, aby sobie z tym poradzić, chociaż i ponieważ sprawia, że twoje pytanie jest dyskusyjne. Zauważ, że
__getattr__()
zamiast tego definiuje,__getattribute__()
ponieważ oznacza to, że musi zajmować się tylko „specjalnymi” atrybutami przechowywanymi w wewnętrznym słowniku.def __getattr__(self, name): """only called when an attribute lookup in the usual places has failed""" try: return self.my_dict[name] except KeyError: raise AttributeError("some customized error message")
źródło
except
bloku może dać mylące dane wyjściowe w Pythonie 3. Dzieje się tak, ponieważ (zgodnie z PEP 3134) Python 3 śledzi pierwszy wyjątek (theKeyError
) jako „kontekst” drugiego wyjątku (theAttributeError
) i jeśli dociera do najwyższy poziom, wydrukuje śledzenie, które zawiera oba wyjątki. Może to być pomocne, gdy nie oczekiwano drugiego wyjątku, ale jeśli celowo zgłaszasz drugi wyjątek, jest to niepożądane. W przypadku Pythona 3.3 PEP 415 dodał możliwość pomijania kontekstu przy użyciuraise AttributeError("whatever") from None
.KeyError
w anAttributeError
i pokazanie, że to, co wydarzyło się w śledzeniu, byłoby przydatne i odpowiednie.__getattr__
zgłasza wyjątek, błąd jest prawdopodobnie błędem w dostępie do atrybutu, a nie błędem implementacji w kodzie bieżącej klasy. Pokazanie wcześniejszego wyjątku jako kontekstu może to zmylić. A nawet jeśli wyłączysz kontekst za pomocąraise Whatever from None
, nadal możesz w razie potrzeby uzyskać dostęp do poprzedniego wyjątku za pośrednictwemex.__context__
.__getattribute__()
.W Pythonie łatwiej jest prosić o przebaczenie niż o pozwolenie . Nie przejmuj się obsługą zagnieżdżonych wyjątków.
(Poza tym i tak
has*
prawie zawsze używa wyjątków pod przykryciem).źródło
Zgodnie z dokumentacją lepiej jest obsługiwać wiele wyjątków za pomocą krotek lub w ten sposób:
import sys try: f = open('myfile.txt') s = f.readline() i = int(s.strip()) except IOError as e: print "I/O error({0}): {1}".format(e.errno, e.strerror) except ValueError: print "Could not convert data to an integer." except: print "Unexpected error:", sys.exc_info()[0] raise
źródło
Dobrym i prostym przykładem zagnieżdżonej try / except może być:
import numpy as np def divide(x, y): try: out = x/y except: try: out = np.inf * x / abs(x) except: out = np.nan finally: return out
Teraz wypróbuj różne kombinacje, a otrzymasz poprawny wynik:
divide(15, 3) # 5.0 divide(15, 0) # inf divide(-15, 0) # -inf divide(0, 0) # nan
[oczywiście mamy numpy, więc nie musimy tworzyć tej funkcji]
źródło
Jedną z rzeczy, których lubię unikać, jest zgłaszanie nowego wyjątku podczas obsługi starego. Powoduje to, że odczytywanie komunikatów o błędach jest mylące.
Na przykład w moim kodzie pierwotnie napisałem
try: return tuple.__getitem__(self, i)(key) except IndexError: raise KeyError(key)
I dostałem tę wiadomość.
>>> During handling of above exception, another exception occurred.
To, czego chciałem, to:
try: return tuple.__getitem__(self, i)(key) except IndexError: pass raise KeyError(key)
Nie ma to wpływu na sposób obsługi wyjątków. W każdym bloku kodu zostałby przechwycony KeyError. To tylko kwestia zdobywania punktów za styl.
źródło
from None
, aby uzyskać jeszcze więcej punktów za styl. :)Jeśli try-oprócz-final jest zagnieżdżony w bloku last, wynik z „dziecka” zostaje ostatecznie zachowany. Nie znalazłem jeszcze oficjalnego wyjaśnienia, ale poniższy fragment kodu pokazuje to zachowanie w Pythonie 3.6.
def f2(): try: a = 4 raise SyntaxError except SyntaxError as se: print('log SE') raise se from None finally: try: raise ValueError except ValueError as ve: a = 5 print('log VE') raise ve from None finally: return 6 return a In [1]: f2() log SE log VE Out[2]: 6
źródło
Nie sądzę, żeby to była kwestia bycia pytonicznym czy eleganckim. Chodzi o to, aby jak najbardziej zapobiegać wyjątkom. Wyjątki mają na celu obsługę błędów, które mogą wystąpić w kodzie lub zdarzeniach, nad którymi nie masz kontroli. W takim przypadku masz pełną kontrolę podczas sprawdzania, czy element jest atrybutem czy w słowniku, więc unikaj zagnieżdżonych wyjątków i trzymaj się drugiej próby.
źródło