在现代Python中声明自定义异常类的正确方法是什么?我的主要目标是遵循其他异常类所具有的任何标准,以便(例如)由捕获异常的任何工具打印出异常中包含的任何额外字符串。
所谓"现代Python",我指的是在Python 2.5中运行但在Python 2.6和Python 3中"正确"的东西。做事的方法。我所说的"自定义"是指一个异常对象,它可以包含关于错误原因的额外数据:一个字符串,也可能是一些与异常相关的其他任意对象。
我在Python 2.6.2中遇到了下面的弃用警告:
>>> class MyError(Exception):
... def __init__(self, message):
... self.message = message
...
>>> MyError("foo")
_sandbox.py:3: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6
BaseException对于名为message的属性具有特殊的含义,这似乎有些疯狂。我从PEP-352中收集到这个属性在2.5中确实有一个特殊的含义,所以我猜这个名字(而且只是这个)现在是禁止的了?啊。
我也模糊地意识到Exception有一些神奇的参数args,但是我从来不知道如何使用它。我也不确定这是向前发展的正确方式;我在网上找到的很多讨论都表明,他们正试图在python3中取消args。
更新:有两个答案建议重写__init__和__str__/__unicode__/__repr__。好像打了很多字,有必要吗?
也许我错过了这个问题,但为什么不呢:
class MyException(Exception):
pass
编辑:要覆盖某些东西(或传递额外的参数),请这样做:
class ValidationError(Exception):
def __init__(self, message, errors):
# Call the base class constructor with the parameters it needs
super(ValidationError, self).__init__(message)
# Now for your custom code...
self.errors = errors
这样,您就可以将dict的错误消息传递给第二个参数,然后使用e.errors进行处理
Python 3更新:在python3 +中,您可以使用稍微紧凑一点的super():
class ValidationError(Exception):
def __init__(self, message, errors):
# Call the base class constructor with the parameters it needs
super().__init__(message)
# Now for your custom code...
self.errors = errors
但是,这样定义的异常是不可选的;点击这里查看讨论:stackoverflow.com/questions/16244923/…
@jiakai意味着"可pickle"。:-)
对于现代Python异常,您不需要滥用.message或覆盖.__str__()或.__repr__()或任何其他异常。如果您想要的只是在抛出异常时得到一条有用的消息,请这样做:
class MyException(Exception):
pass
raise MyException("My hovercraft is full of eels")
这将给出一个以MyException: My hovercraft is full of eels结尾的回溯。
如果你想从异常中获得更多的灵活性,你可以传递一个字典作为参数:
raise MyException({"message":"My hovercraft is full of animals","animal":"eels
- "但这将在未来被弃用"——这仍然是为了弃用吗?Python 3.7似乎仍然乐于接受Exception(foo, bar, qux)。
- 自从上次尝试由于转换的痛苦而失败后,它还没有看到任何最近的工作来破坏它,但是这种用法仍然不受欢迎。我将更新我的答案来反映这一点。
- @frnknstn,为什么不鼓励这样做?对我来说,这是个不错的习语。
- 首先,使用元组存储异常信息与使用字典存储异常信息相比没有什么好处。如果您对异常更改背后的原因感兴趣,请查看PEP352
"Proper way to declare custom exceptions in modern Python?"
这很好,除非你的异常是一种更特殊的异常:
[cc lang="python"]class MyException(Exception):pass
或者更好(也许是完美的),而不是pass给出一个docstring:
class MyException(Exception):
"""Raise for my specific kind of exception"""
<
子类化异常子类/ hh2 >
从文档
Exception
All built-in, non-system-exiting exceptions are derived from this class.
All user-defined exceptions should also be derived from this
class.
这意味着,如果您的异常是一种更具体的异常类型,则子类化该异常而不是通用的Exception(结果将是您仍然按照文档的建议从Exception派生)。此外,您至少可以提供一个docstring(而不必强制使用pass关键字):
class MyAppValueError(ValueError):
'''Raise when my specific value is wrong'''
设置使用自定义__init__创建的属性。避免将dict作为位置参数传递,代码的未来用户将会感谢您。如果您使用了deprecated message属性,那么您自己分配它将避免使用DeprecationWarning:
class MyAppValueError(ValueError):
'''Raise when a specific subset of values in context of app is wrong'''
def __init__(self, message, foo, *args):
self.message = message # without this you may get DeprecationWarning
# Special attribute you desire with your Error,
# perhaps the value that caused the error?:
self.foo = foo
# allow users initialize misc. arguments as any other builtin Error
super(MyAppValueError, self).__init__(message, foo, *args)
真的没有必要编写自己的__str__或__repr__。内置组件非常好,您的合作继承确保您使用它。顶部答案的评论
Maybe I missed the question, but why not:
class MyException(Exception):
pass
同样,上面的问题是,为了捕获它,您要么必须指定它的名称(如果在其他地方创建,则需要导入它),要么捕获异常(但是您可能不准备处理所有类型的异常,您应该只捕获准备处理的异常)。类似的批评如下,但除此之外,这不是通过super初始化的方法,如果你访问message属性,你会得到一个DeprecationWarning:
Edit: to override something (or pass extra args), do this:
class ValidationError(Exception):
def __init__(self, message, errors):
# Call the base class constructor with the parameters it needs
super(ValidationError, self).__init__(message)
# Now for your custom code...
self.errors = errors
That way you could pass dict of error messages to the second param, and get to it later with e.errors
它还需要传入两个参数(除了self)。不多不少。这是一个有趣的约束,未来的用户可能不会喜欢。
直接地说,它违反了Liskov可替换性。
我将演示这两个错误:
>>> ValidationError('foo', 'bar', 'baz').message
Traceback (most recent call last):
File"", line 1, in
ValidationError('foo', 'bar', 'baz').message
TypeError: __init__() takes exactly 3 arguments (4 given)
>>> ValidationError('foo', 'bar').message
__main__:1: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6
'foo'
相比:
>>> MyAppValueError('foo', 'FOO', 'bar').message
'foo'
你好从2018年!BaseException.message在Python 3中没有了,所以这个评论只适用于旧版本,对吗?
关于Liskov可替代性的批评仍然有效。第一个参数作为"消息"的语义也存在争议,但我不认为我要讨论这一点。当我有更多的空闲时间时,我会多看看这个。
FWIW,对于python3(至少对于3.6+),我们将重新定义MyAppValueError的__str__方法,而不是依赖于message属性
为什么要避免将dict作为位置参数传递?它保留了所有的原始语义,包括(__repr__/__str__),用户可以通过.args[0]根据frnknstn的答案解析dict吗?(您在文档字符串中注意到了这一点,不是吗?)
@AaronHall,你能详细说明一下子类化ValueError而不是Exception的好处吗?您声明这就是文档的含义,但是直接阅读并不支持这种解释,在Python教程中,在用户定义异常下,它明确地让用户选择:"异常通常应该直接或间接地从Exception类派生。"因此,我很想知道你的观点是否合理。
@ostergaard现在不能完全回答,但是简而言之,用户可以获得额外的选项来捕获ValueError。如果它在值错误的类别中,这是有意义的。如果它不属于值错误的范畴,我将在语义上反对它。对于程序员来说,还有一些细微差别和推理的空间,但如果适用,我更喜欢具体化。我很快会更新我的答案,以便更好地解决这个问题。
@AaronHall谢谢,这很有道理,我同意。尽管我认为把这一意义归于文档是一种延伸。
see how exceptions work by default if one vs more attributes are used (tracebacks omitted):
>>> raise Exception('bad thing happened')
Exception: bad thing happened
>>> raise Exception('bad thing happened', 'code is broken')
Exception: ('bad thing happened', 'code is broken')
所以你可能想要一种"异常模板",作为一个异常本身,以兼容的方式工作:
>>> nastyerr = NastyError('bad thing happened')
>>> raise nastyerr
NastyError: bad thing happened
>>> raise nastyerr()
NastyError: bad thing happened
>>> raise nastyerr('code is broken')
NastyError: ('bad thing happened', 'code is broken')
使用这个子类可以很容易地做到这一点
class ExceptionTemplate(Exception):
def __call__(self, *args):
return self.__class__(*(self.args + args))
# ...
class NastyError(ExceptionTemplate): pass
如果您不喜欢默认的类元表示,只需将__str__方法添加到ExceptionTemplate类中,比如:
# ...
def __str__(self):
return ': '.join(self.args)
和你会有
>>> raise nastyerr('code is broken')
NastyError: bad thing happened: code is broken
您应该覆盖__repr__或__unicode__方法,而不是使用message,构造异常时提供的args将位于异常对象的args属性中。
不,"消息"不是禁止的。它只是弃用。您的应用程序可以很好地使用消息。当然,您可能希望消除弃用错误。
当您为您的应用程序创建自定义异常类时,它们中的许多并不仅仅来自Exception,而是来自其他异常,如ValueError或类似的异常。然后你必须适应他们对变量的使用。
如果您的应用程序中有很多异常,通常最好为所有异常都提供一个通用的自定义基类,这样模块的用户就可以这样做
try:
...
except NelsonsExceptions:
...
在这种情况下,您可以执行所需的__init__ and __str__,因此不必对每个异常都重复它。但是简单地调用message变量而不是message就可以了。
在任何情况下,您只需要在执行与Exception本身不同的操作时使用__init__ or __str__。因为如果不赞成,你需要两者,否则你会得到一个错误。每个类需要的额外代码并不多。,)
从Python 3.8 (2018, https://docs.python.org/dev/whatsnew/3.8.html)开始,推荐的方法仍然是:
class CustomExceptionName(Exception):
"""Exception raised when very uncommon things happen"""
pass
请不要忘记记录,为什么需要自定义异常!
如果你需要,这是处理异常的方法与更多的数据:
class CustomExceptionName(Exception):
"""Still an exception raised when uncommon things happen"""
def __init__(self, message, payload=None):
self.message = message
self.payload = payload # you could add more args
def __str__(self):
return str(self.message) # __str__() obviously expects a string to be returned, so make sure not to send any other data types
然后像这样取:
try:
raise CustomExceptionName("Very bad mistake.","Forgot upgrading from Python 1")
except CustomExceptionName as error:
print(str(error)) # Very bad mistake
print("Detail: {}".format(self.payload)) # Detail: Forgot upgrading from Python 1
payload=None使其可酸洗是很重要的。在丢弃它之前,您必须调用error.__reduce()__。加载将按预期工作。
如果需要将大量数据传输到某个外部结构,您可能应该研究如何使用pythons return语句找到解决方案。这对我来说似乎更清楚/更像蟒蛇。高级异常在Java中大量使用,当使用框架并必须捕获所有可能的错误时,这有时会很烦人。
嗯,Python 3。8中有什么变化?出于好奇,我浏览了你发布的链接,但是没有提到任何与这个主题相关的内容……
如果什么都没有改变,就不需要一个新的答案;对公认的答案做一个简单的评论就足够了。
至少,当前的文档表明这是一种方法(至少没有使用__str__),而不是使用super().__init__(...)的其他答案。只是遗憾的是,仅仅为了更好的"默认"序列化,覆盖__str__和__repr__可能是必要的。
试试这个例子
class InvalidInputError(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return repr(self.msg)
inp = int(input("Enter a number between 1 to 10:"))
try:
if type(inp) != int or inp not in list(range(1,11)):
raise InvalidInputError
except InvalidInputError:
print("Invalid input entered")