Python: это плохой тон - вызывать исключения в __init__?

128

Считается ли дурным тоном создавать исключения внутри __init__? Если да, то каков принятый метод выдачи ошибки, когда определенные переменные класса инициализируются как Noneнеправильного типа или имеют неправильный тип?

krizajb
источник
В языке C вызывать исключение в деструкторе - плохой тон, но с конструктором все должно быть хорошо.
Calyth 02
6
@Calyth, вы имеете в виду C ++ - в C. нет конструкторов или деструкторов
Alex Martelli
4
... или исключения, если на то пошло.
Мэтт Уилдинг
@Calyth: lol, пожалуйста, удалите это бессмысленное заявление, сделанное из-за невинной опечатки ;-)
lpapp 08
4
@lpapp да. C ++. FML. И я не мог редактировать этот комментарий. Так что Интернет должен задокументировать мой идиотизм;)
Calyth

Ответы:

159

Вызов исключений внутри __init__() - это нормально. Нет другого хорошего способа указать состояние ошибки в конструкторе, и в стандартной библиотеке есть сотни примеров, когда построение объекта может вызвать исключение.

Класс ошибки, который нужно поднять, конечно, зависит от вас. ValueErrorлучше всего, если конструктору был передан недопустимый параметр.

Джон Милликин
источник
14
Наиболее часто используемые исключения в конструкторе - это ValueErrorи TypeError.
Денис Откидач 02
9
Просто указываю, что __init__это не конструктор, а инициализатор. Возможно, вы захотите отредактировать строку. Нет другого хорошего способа указать состояние ошибки в конструкторе ..
Харис
26

Верно, что единственный правильный способ указать ошибку в конструкторе - это вызвать исключение. Вот почему в C ++ и других объектно-ориентированных языках, которые были разработаны с учетом безопасности исключений, деструктор не вызывается, если в конструкторе объекта возникает исключение (что означает, что инициализация объекта не завершена). В языках сценариев, таких как Python, этого часто не происходит. Например, следующий код вызывает ошибку AttributeError в случае сбоя socket.connect ():

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()

Причина в том, что деструктор неполного объекта вызывается после неудачной попытки подключения, до того, как атрибут потока был инициализирован. Не следует избегать генерации исключений из конструкторов, я просто говорю, что на Python сложно написать полностью безопасный код исключений. Некоторые разработчики Python вообще избегают использования деструкторов, но это уже другой спор.

Сеппо Энарви
источник
Этот ответ действительно полезен. Речь идет не просто о «зеленом свете», а упоминается пример с «деструктором».
lpapp
Ваш код Python не работает, __del__потому что он плохо написан. У вас была бы точно такая же проблема, если бы вы делали this->p_socket->close()это в деструкторе на C ++. В C ++ вы бы этого не сделали - вы позволили бы объекту-члену уничтожить себя. Сделайте то же самое в питоне.
Эрик
1
@Eric У меня не было бы проблем с C ++, потому что C ++ не разрушает объекты, которые не были должным образом инициализированы . На самом деле это очень распространенная идиома в C ++ - выделять ресурсы в конструкторе и освобождать их в деструкторе . Вы предполагаете, что объект-член уничтожает себя (освобождая ресурсы в его деструкторе) - тогда та же проблема возникает при написании класса-члена.
Сеппо Энарви
11

Я не вижу причин, по которым это должно быть плохим тоном.

Напротив, одна из вещей, которые, как известно, работают хорошо, в отличие от возврата кодов ошибок, заключается в том, что коды ошибок обычно не могут быть возвращены конструкторами. Так что, по крайней мере, в таких языках, как C ++, создание исключений - единственный способ сигнализировать об ошибках.

Эдан Маор
источник
6

Стандартная библиотека говорит:

>>> 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'

Также я не вижу причин, по которым это следует считать плохим тоном.

STH
источник
3

Думаю, это идеальный случай для встроенного ValueErrorисключения.

Тедди
источник
2

Я согласен со всем вышесказанным.

На самом деле нет другого способа сигнализировать о том, что при инициализации объекта что-то пошло не так, кроме как вызвать исключение.

В большинстве программных классов, где состояние класса полностью зависит от входных данных для этого класса, мы можем ожидать возникновения какой-либо ValueError или TypeError.

Классы с побочными эффектами (например, те, которые работают с сетью или графикой) могут вызвать ошибку в init, если (например) сетевое устройство недоступно или объект холста не может быть записан. Мне это кажется разумным, потому что часто вы хотите узнать об условиях отказа как можно скорее.

Салим Фадли
источник
2

В некоторых случаях появление ошибок из init неизбежно, но слишком много работы в init - плохой стиль. Вам следует подумать о создании фабрики или псевдо-фабрики - простого метода классов, который возвращает настроенный объект.

Ctrl-C,
источник