Какую ошибку повышать, если состояние класса недействительно?
В классе Python, какую ошибку я должен поднять из метода экземпляра, когда некоторые другие атрибуты класса должны быть изменены до запуска этого метода?
Я иду из фона С#, где я бы использовал InvalidOperationException
, "исключение, которое бросается, когда вызов метода недействителен для текущего состояния объекта", но я не смог найти эквивалент встроенное исключение в Python.
Я поднимал ValueError
( "при поднятии, когда встроенная операция или функция получает аргумент, который имеет правильный тип, но несоответствующее значение" ), когда проблема связана с параметрами функции. Я полагаю, что это технически недопустимое значение для параметра self
; это правильный способ лечения? Например, это идиоматика: raise ValueError("self.foo must be set before running self.bar()")
?
Ответы
Ответ 1
ValueError
- лучшая вещь, которую нужно повысить в этом случае. Для python вам следует использовать встроенные типы исключений для создания собственного. Вы должны создавать только новые типы исключений, когда вы ожидаете, что вам нужно будет их поймать и вести себя по-другому, чем вы себя вели, когда будете ловить встроенные типы. В этом случае ситуация не должна возникать - вы не ожидаете этого, потому что это указывает на ошибку при использовании рассматриваемого класса. Для этого не стоит создавать новый тип, чтобы он имел другое имя - это то, что строка сообщения, которую вы передаете в ValueError()
, предназначена для.
Возможно ли реструктурировать ваш класс, чтобы такое недопустимое состояние невозможно?
Ответ 2
Я нахожу RuntimeError
наиболее подходящее из всех встроенных исключений для сигнала недопустимого состояния.
См. этот пример того, как он используется в CPython:
Python 2.7.10 (default, Jul 13 2015, 12:05:58)
[GCC 4.2.1 Compatible Apple LLVM 6.1.0 (clang-602.0.53)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from threading import Thread
>>> Thread().join()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python/2.7.10_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 938, in join
raise RuntimeError("cannot join thread before it is started")
RuntimeError: cannot join thread before it is started
Важно заметить, что даже сама реализация CPython не согласуется с использованием конкретных типов исключений между библиотеками. Иногда используется ValueError
, однако, на мой взгляд, его описание из документации Python показывает, что его использование зарезервировано для других ситуаций. RuntimeError
является более общим исключением и его следует использовать, когда кусок кода не может корректно вести событие, если ему был присвоен правильный ввод, что несколько похоже на ситуацию, когда объект находится в недопустимом состоянии.
Ответ 3
Я думаю, что путинский путь не оставляет объект в таком состоянии, когда вызов метода не будет сбой, несмотря на то, что он находится в ошибочном состоянии. Это самые трудные ошибки для поиска, поскольку точка, в которой программа, наконец, перевернулась, не там, где произошла ошибка.
например.
class PseudoTuple(object):
"""
The sum method of PseudoTuple will raise an AttributeError if either x or y have
not been set
"""
def setX(self, x):
self.x = x
def setY(self, y):
self.y = y
def sum(self):
"""
In the documentation it should be made clear that x and y need to have been set
for sum to work properly
"""
return self.x + self.y
class AnotherPseudoTuple(PseudoTuple):
"""
For AnotherPseudoTuple sum will now raise a TypeError if x and y have not been
properly set
"""
def __init__(self, x=None, y=None):
self.x = x
self.y = y
Нельзя делать что-то вроде
class BadPseudoTuple(PseudoTuple):
"""
In BadPseudoTuple -1 is used to indicate an invalid state
"""
def __init__(self, x=-1, y=-1):
self.x = x
self.y = y
def sum(self):
if self.x == -1 or self.y == -1:
raise SomeException("BadPseudoTuple in invalid state")
else:
return self.x + self.y
Я думаю, что это подпадает под pythonic девиз:
Проще просить прощения, чем получить разрешение
Если исключительное состояние - это то, что можно ожидать во время обычного хода выполнения, а не как ошибка пользователя, злоупотребляя классом, тогда кажется разумным, что вы должны создать свое собственное исключение. StopIteration и итераторы являются примером этого.
Ответ 4
class InvalidOperationException(Exception):
pass
SYS_STATE = 1
def something_being_run():
if SYS_STATE < 2:
raise InvalidOperationException
Что-то вроде этого? Я не вижу причин, по которым вы не должны исключать sub-class, чтобы создавать свои собственные типы исключений, но это может быть просто старый Oracle PL/SQL Dev во мне, выходящий...
Ответ 5
ValueError
подходит мне, но я думаю, что AssertionError
более уместен. В принципе, это нарушает утверждение, сделанное дизайнером API.
Ответ 6
Я думаю, вы должны поднять ValueError
, когда проблема связана с параметрами функции, как вы это делаете, и AttributeError
, когда проблема связана с атрибутом, который должен быть установлен.
Кроме того, вы можете подклассифицировать AttributeError
, чтобы сделать более конкретное исключение, но я не вижу в этом необходимости. Исключение AttributeError
Исключение с сообщением об ошибке достаточно ясно.