Python: Czy zgłaszanie wyjątków w __init__ jest złą formą?


128

Czy zgłaszanie wyjątków w ramach programu jest złą formą __init__? Jeśli tak, to jaka jest akceptowana metoda zgłaszania błędu, gdy pewne zmienne klasy są inicjowane jako Nonelub niepoprawnego typu?


W C zgłoszenie wyjątku w destruktorze jest złą formą, ale konstruktor powinien być w porządku.
Calyth

6
@Calyth, masz na myśli C ++ - w C. nie ma konstruktorów ani destruktorów
Alex Martelli

4
... lub wyjątki, jeśli o to chodzi.
Matt Wilding,

@Calyth: lol, proszę usuń to bezsensowne stwierdzenie złożone przez niewinną literówkę ;-)
lpapp

4
@lpapp yes. C ++. FML. I nie mogłem edytować tego komentarza. Internet powinien więc udokumentować moje idiotyzm;)
Calyth,

Odpowiedzi:


159

Zgłaszanie wyjątków w ramach __init__() jest absolutnie w porządku. Nie ma innego dobrego sposobu na wskazanie warunku błędu w konstruktorze, aw bibliotece standardowej istnieje wiele setek przykładów, w których zbudowanie obiektu może zgłosić wyjątek.

Klasa błędu, którą należy podnieść, zależy oczywiście od Ciebie. ValueErrornajlepiej, jeśli do konstruktora przekazano nieprawidłowy parametr.


14
Najczęściej używanymi wyjątkami w konstruktorze są ValueErrori TypeError.
Denis Otkidach

9
__init__Zwracam tylko uwagę, że nie jest konstruktorem, tylko inicjatorem. Możesz chcieć edytować linię Nie ma innego dobrego sposobu na wskazanie stanu błędu w konstruktorze, ...
Haris

26

Prawdą jest, że jedynym właściwym sposobem wskazania błędu w konstruktorze jest zgłoszenie wyjątku. Dlatego w C ++ i innych językach obiektowych, które zostały zaprojektowane z myślą o bezpieczeństwie wyjątków, destruktor nie jest wywoływany, jeśli w konstruktorze obiektu zostanie zgłoszony wyjątek (co oznacza, że ​​inicjalizacja obiektu jest niekompletna). Często tak nie jest w językach skryptowych, takich jak Python. Na przykład poniższy kod zgłasza błąd AttributeError, jeśli funkcja socket.connect () nie powiedzie się:

class NetworkInterface:
    def __init__(self, address)
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.connect(address)
        self.stream = self.socket.makefile()

    def __del__(self)
        self.stream.close()
        self.socket.close()

Przyczyną jest to, że destruktor niekompletnego obiektu jest wywoływany po nieudanej próbie połączenia, przed zainicjowaniem atrybutu strumienia. Nie należy unikać wyrzucania wyjątków z konstruktorów, mówię tylko, że trudno jest napisać w Pythonie kod w pełni bezpieczny dla wyjątków. Niektórzy programiści Pythona całkowicie unikają używania destruktorów, ale to już kwestia innej debaty.


Ta odpowiedź jest naprawdę przydatna. Nie chodzi tylko o „zielone światło”, ale o przykład z „destruktorem”.
lpapp,

Twój kod Pythona zawodzi, __del__ponieważ jest źle napisany. Miałbyś dokładnie ten sam problem, gdybyś zrobił this->p_socket->close()w destruktorze w C ++. W C ++ nie zrobiłbyś tego - pozwoliłbyś obiektowi składowemu sam się zniszczyć. Zrób to samo w Pythonie.
Eric

1
@Eric Nie miałbym problemu w C ++, ponieważ C ++ nie niszczy obiektów, które nie zostały poprawnie zainicjowane . W rzeczywistości bardzo powszechnym idiomem w C ++ jest przydzielanie zasobów w konstruktorze i zwalnianie ich w destruktorze . Sugerujesz, że obiekt członkowski sam się niszczy (zwalnia zasoby w jego destruktorze) - wtedy ten sam problem pojawia się podczas pisania klasy składowej.
Seppo Enarvi

11

Nie widzę powodu, dla którego miałaby to być zła forma.

Wręcz przeciwnie, jednym z wyjątków, które są znane z tego, że radzą sobie dobrze, w przeciwieństwie do zwracania kodów błędów, jest to, że konstruktorzy zwykle nie mogą zwracać kodów błędów . Tak więc przynajmniej w językach takich jak C ++ zgłaszanie wyjątków jest jedynym sposobem sygnalizowania błędów.


6

Biblioteka standardowa mówi:

>>> f = file("notexisting.txt")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory: 'notexisting.txt'

Poza tym nie widzę żadnego powodu, dla którego miałoby to być uważane za złą formę.


3

Myślę, że jest to idealny przypadek dla wbudowanego ValueErrorwyjątku.


2

Zgadzam się z powyższym.

Tak naprawdę nie ma innego sposobu, aby zasygnalizować, że coś poszło nie tak podczas inicjalizacji obiektu, niż zgłoszenie wyjątku.

W większości klas programów, w których stan klasy jest całkowicie zależny od danych wejściowych tej klasy, możemy spodziewać się podniesienia pewnego rodzaju wartości ValueError lub TypeError.

Klasy z efektami ubocznymi (np. Takie, które obsługują sieć lub grafikę) mogą powodować błąd w init, jeśli (na przykład) urządzenie sieciowe jest niedostępne lub nie można zapisać do obiektu kanwy. Wydaje mi się to rozsądne, ponieważ często chcesz wiedzieć jak najszybciej o warunkach awarii.


2

W niektórych przypadkach nie da się uniknąć błędów z init, ale zbyt dużo pracy w init to zły styl. Powinieneś rozważyć stworzenie fabryki lub pseudo-fabryki - prostej metody klasowej, która zwraca skonfigurowany obiekt.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.