Python: Чи погано формувати винятки протягом __init__?


128

Чи вважається поганою формою збільшення винятків у межах __init__? Якщо так, то який прийнятий метод видавання помилки, коли певні змінні класу ініціалізуються як Noneабо неправильного типу?


У C погано формувати виняток у деструкторі, але конструктор повинен бути добре.
Calyth

6
@Calyth, ти маєш на увазі C ++ - у C. немає конструкторів чи деструкторів.
Алекс Мартеллі

4
... або винятки з цього питання.
Метт Вілдінг

@Calyth: хай, будь ласка, видаліть це безглузде твердження, яке зробив невинний друкарський
помилок

4
@lpapp так. C ++. FML. І я не зміг змінити цей коментар. Тож Інтернет задокументує мою ідіотизм;)
Calyth

Відповіді:


159

Збільшення винятків всередині __init__()абсолютно чудово. Немає іншого хорошого способу вказати стан помилки в конструкторі, і в стандартній бібліотеці є багато сотень прикладів, коли побудова об'єкта може створити виняток.

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


14
Найбільш використовувані винятки в конструкторі - ValueErrorі TypeError.
Денис Откідач

9
Тільки вказуючи, що __init__це не конструктор, а його ініціалізатор. Ви можете відредагувати рядок. Немає іншого способу вказувати на стан помилки в конструкторі, ..
Haris

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 ++ дуже поширена ідіома - розподіляти ресурси в конструкторі та розміщувати їх у деструкторі . Ви припускаєте, що об’єкт-член знищує себе (розбираючи ресурси в його деструкторі) - тоді те саме виникає при написанні класу-члена.
Seppo Enarvi

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'

Також я не бачу жодної причини, чому це слід вважати поганою формою.


3

Я думаю, що це ідеальний випадок для вбудованого ValueErrorвинятку.


2

Я погоджуюся з усім вищесказаним.

Дійсно немає іншого способу сигналізувати, що щось ішло не так при ініціалізації об'єкта, окрім підняття винятку.

У більшості класів програм, де стан класу повністю залежить від входів до цього класу, ми можемо очікувати, що буде підвищено якесь значення ValueError або TypeError.

Класи з побічними ефектами (наприклад, з мережевими або графічними) можуть викликати помилку в init, якщо (наприклад) мережевий пристрій недоступний або об'єкт полотна не може бути записаний на нього. Для мене це звучить розумно, тому що часто вам хочеться якомога швидше дізнатися про умови відмови.


2

Збільшення помилок від init уникнути неминуче, але робити занадто багато роботи в init - це поганий стиль. Вам слід розглянути можливість створення фабрики або псевдо-фабрики - простого методу класів, який повертає створений об'єкт.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.