[python] 현대 파이썬에서 사용자 정의 예외를 선언하는 올바른 방법은 무엇입니까?

현대 파이썬에서 사용자 정의 예외 클래스를 선언하는 올바른 방법은 무엇입니까? 나의 주요 목표는 다른 예외 클래스가 가지고있는 표준을 준수하는 것입니다. 예를 들어 예외에 포함 된 추가 문자열은 예외를 잡은 도구에 의해 인쇄됩니다.

“현대 파이썬”이란 파이썬 2.5에서 실행되지만 파이썬 2.6 및 파이썬 3에 대해 ‘올바른’것을 의미합니다. 그리고 “custom”은 오류의 원인에 대한 추가 데이터를 포함 할 수있는 Exception 객체를 의미합니다. 문자열 일 수도 있고 예외와 관련된 다른 임의의 객체 일 수도 있습니다.

파이썬 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있지만 그것을 사용하는 방법을 몰랐습니다. 앞으로 나아가는 것이 올바른 방법이라고 확신하지는 않습니다. 온라인에서 찾은 많은 토론은 Python 3에서 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

그렇게하면 오류 메시지를 두 번째 매개 변수에 전달하고 나중에 e.errors


Python 3 업데이트 : Python 3 이상에서는 다음과 같이 약간 더 간결하게 사용할 수 있습니다 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


답변

현대의 파이썬 예외를 사용하면 남용 .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"})

그러나 except블록 에서 이러한 세부 정보를 얻는 것은 조금 더 복잡합니다. 세부 사항은 args속성 인 목록에 저장됩니다 . 다음과 같이해야합니다.

try:
    raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})
except MyException as e:
    details = e.args[0]
    print(details["animal"])

여러 항목을 예외에 전달하고 튜플 인덱스를 통해 액세스 할 수는 있지만 권장 하지는 않습니다 (그리고 더 이상 사용되지 않을 예정). 하나 이상의 정보가 필요하고 위의 방법으로 충분하지 않은 Exception경우 학습서에 설명 된대로 서브 클래스를 작성해야합니다 .

class MyError(Exception):
    def __init__(self, message, animal):
        self.message = message
        self.animal = animal
    def __str__(self):
        return self.message


답변

“현대 파이썬에서 커스텀 예외를 선언하는 올바른 방법은?”

예외가 실제로 더 구체적인 예외 유형이 아닌 한 이것은 좋습니다.

class MyException(Exception):
    pass

또는 passdocstring 을 주는 대신 더 좋을 수도 있습니다 (완벽 할 수도 있습니다) .

class MyException(Exception):
    """Raise for my specific kind of exception"""

서브 클래 싱 예외 서브 클래스

로부터 문서

Exception

시스템에 존재하지 않는 모든 내장 예외는이 클래스에서 파생됩니다. 모든 사용자 정의 예외도이 클래스에서 파생되어야합니다.

것을 의미 하는 경우 귀하의 예외는보다 구체적인 예외의 종류, 대신 일반의 예외 있다는 서브 클래스입니다 Exception(당신은 여전히에서 파생 있고 결과가 될 것입니다 Exception워드 프로세서 권장으로는). 또한 최소한 docstring을 제공 할 수 있습니다 ( pass키워드 를 사용하지 않아야 함 ).

class MyAppValueError(ValueError):
    '''Raise when my specific value is wrong'''

자신이 만든 속성을 custom으로 설정하십시오 __init__. dict을 위치 인수로 전달하지 마십시오. 코드의 미래 사용자는 감사 할 것입니다. 더 이상 사용되지 않는 메시지 속성을 사용하는 경우 직접 할당하면 다음을 피할 수 있습니다 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__. 내장 된 것들이 매우 좋으며, 당신의 협동 상속 은 그것을 사용하도록 보장합니다.

최고의 답변 비판

어쩌면 나는 질문을 놓쳤지만 왜 그렇지 않습니까?

class MyException(Exception):
    pass

다시 말하지만, 위의 문제는 그것을 잡으려면 구체적으로 이름을 지정하거나 (다른 곳에서 만든 경우 가져 오기) 예외를 잡아야한다는 것입니다 (그러나 아마도 모든 유형의 예외를 처리 할 준비가되어 있지는 않습니다. 처리 할 수있는 예외 만 잡아야합니다.) 아래의 유사 비판하지만, 추가의하지 않는 방법을 통해 초기화하는 것을 super, 당신은 얻을 것이다 DeprecationWarning당신이 메시지 속성에 액세스하는 경우 :

편집 : 무언가를 무시하거나 추가 인수를 전달하려면 다음과 같이하십시오.

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

그렇게하면 오류 메시지를 두 번째 매개 변수에 전달하고 나중에 e.errors로 가져올 수 있습니다.

또한 (.를 제외하고) 정확히 두 개의 인수를 전달해야합니다 self. 그것은 미래의 사용자가 이해하지 못하는 흥미로운 제약입니다.

직접적으로는 Liskov의 대체 가능성을 위반 합니다.

두 가지 오류를 모두 설명하겠습니다.

>>> ValidationError('foo', 'bar', 'baz').message

Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    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'


답변

한 경우 예외가 기본적으로 작동 방법을 볼 이상의 속성이 (역 추적 생략) 사용됩니다

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


답변

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(error.payload)) # Detail: Forgot upgrading from Python 1

payload=None피클 가능하게 만드는 것이 중요합니다. 덤프하기 전에을 호출해야합니다 error.__reduce__(). 로드가 예상대로 작동합니다.

return외부 구조로 전송하기 위해 많은 데이터가 필요한 경우 pythons 문을 사용하여 솔루션을 찾을 때 조사 해야 할 수도 있습니다. 이것은 나에게 더 분명하고 더 많은 것으로 보입니다. Java에서는 고급 예외가 많이 사용되며 프레임 워크를 사용하고 가능한 모든 오류를 잡아야 할 때 성가신 경우가 있습니다.


답변

메시지를 사용하는 대신 __repr__또는 __unicode__메소드를 대체해야 합니다. 예외를 구성 할 때 제공하는 인수 args는 예외 오브젝트 의 속성에 있습니다.


답변

아니요, “메시지”는 금지되지 않습니다. 더 이상 사용되지 않습니다. 메시지를 사용하면 응용 프로그램이 제대로 작동합니다. 그러나 사용 중단 오류를 제거하고 싶을 수도 있습니다.

응용 프로그램에 대한 사용자 지정 예외 클래스를 만들면 많은 클래스가 예외에서 하위 클래스가 아니라 ValueError 또는 이와 유사한 클래스에서 하위 클래스로 분류됩니다. 그런 다음 변수 사용에 적응해야합니다.

또한 응용 프로그램에 많은 예외가있는 경우 일반적으로 모듈 사용자가 할 수 있도록 모든 사용자 정의 공통 클래스를 갖는 것이 좋습니다.

try:
    ...
except NelsonsExceptions:
    ...

이 경우 __init__ and __str__필요한 작업을 수행 할 수 있으므로 모든 예외에 대해 반복 할 필요가 없습니다. 그러나 단순히 메시지 이외의 메시지 변수를 호출하면 트릭이 수행됩니다.

어쨌든 __init__ or __str__예외 자체와 다른 일을 하는 경우 에만 필요합니다 . 더 이상 사용되지 않는 경우 둘 다 필요하거나 오류가 발생하기 때문입니다. 그것은 클래스마다 필요한 많은 추가 코드가 아닙니다. 😉