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


1289

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

"현대 파이썬"이란 파이썬 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__. 타이핑이 많이 필요한 것 같아요?

답변:


1322

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

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

35
그러나 이와 같이 정의 된 예외는 선택할 수 없습니다. 여기 토론을 참조하십시오 stackoverflow.com/questions/16244923/…
jiakai

86
@jiakai는 "피킹 가능"을 의미합니다. :-)
Robino

1
사용자 정의 예외에 대한 python 문서에 따라 __init__ 함수에 언급 된 이름이 올바르지 않습니다. (자기, 메시지, 오류) 대신 (자기, 표현, 메시지)입니다. 속성 표현식은 오류가 발생한 입력 표현식이며 메시지는 오류에 대한 설명입니다.
ddleon

2
@ddleon은 오해입니다. 언급 한 문서의 예는 특정 사용 사례입니다. 서브 클래스의 생성자 인수 이름 (또는 숫자)에는 의미가 없습니다.
asthasr

498

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

2
"하지만 앞으로는 더 이상 사용되지 않을 것입니다"-여전히 사용 중단 될 예정입니까? 파이썬 3.7은 여전히 ​​행복하게 받아들이는 것 같습니다 Exception(foo, bar, qux).
mtraceur

전환의 고통으로 인해 마지막 시도가 실패한 이후로 최근에이를 폐기하려는 최근의 작업은 보지 못했지만 그 사용은 여전히 ​​권장되지 않습니다. 이를 반영하여 답변을 업데이트하겠습니다.
frnknstn

@frnknstn, 왜 낙담합니까? 저에게 좋은 관용구처럼 보입니다.
17시

2
@ 튜플을 사용하여 예외 정보를 저장하는 것은 사전을 사용하여 동일한 작업을 수행하는 것보다 이점이 없습니다. 예외 변경에 대한 추론에 관심이 있으시면 PEP352
frnknstn at

PEP352의 관련 섹션은 "철회 아이디어" 입니다.
liberforce

196

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

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

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'

2
2018 년부터 안녕하세요! BaseException.messagePython 3에서는 사라 졌으므로 비판은 이전 버전에만 적용됩니다.
Kos

5
@ Kos Liskov Substitutability에 대한 비판은 여전히 ​​유효합니다. "메시지"로서의 첫 번째 주장의 의미론도 의심의 여지가 있지만, 나는 그 요점을 논쟁하지는 않을 것이라고 생각합니다. 더 많은 자유 시간이있을 때 좀 더 살펴 보겠습니다.
Aaron Hall

1
FWIW, Python 3 (적어도 3.6+)의 경우 속성 에 의존하는 대신 __str__방법을 재정의 합니다MyAppValueErrormessage
Jacquot

1
@AaronHall 예외가 아닌 하위 클래스 ValueError의 이점을 확대 할 수 있습니까? 이것은 문서에서 의미하는 것이지만 직접 읽기는 해당 해석을 지원하지 않으며 사용자 정의 예외 아래의 Python 자습서에서는 사용자가 다음을 명확하게 선택할 수있게합니다. "예외는 일반적으로 Exception 클래스에서 파생되어야합니다. 직접 또는 간접적으로. " 따라서 귀하의 견해가 정당한지 이해하고 싶습니다.
ostergaard

1
@ostergaard 지금은 전체 답변을 할 수 없지만 간단히 말해 사용자는을 잡는 추가 옵션을 얻습니다 ValueError. 값 오류 범주에 있으면 이치에 맞습니다. 값 오류 범주에 속하지 않으면 의미론에서 반대합니다. 프로그래머에게는 약간의 뉘앙스와 추론의 여지가 있지만 적용 가능한 경우에는 특이성을 선호합니다. 곧 주제를 다루기 위해 답변을 업데이트하겠습니다.
Aaron Hall

50

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

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

32

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에서는 고급 예외가 많이 사용되며 프레임 워크를 사용하고 가능한 모든 오류를 잡아야 할 때 성가신 경우가 있습니다.


1
적어도에서, 현재 문서는 이 (적어도없이 할 수있는 방법입니다 표시 __str__하지 않고 사용하는 것이 다른 답변보다) super().__init__(...)..에 대한 오버라이드 (override) 그냥 수치 __str__와는 __repr__단지 더 나은 "기본"을 위해 아마 필요 직렬화.
kevlarr

2
정직한 질문 : 예외가 피클 가능해야하는 이유는 무엇입니까? 예외 덤프 및로드의 사용 사례는 무엇입니까?
Roel Schroeven

1
@RoelSchroeven : 코드를 한 번 병렬화해야했습니다. 훌륭한 단일 프로세스를 실행했지만 일부 클래스의 측면은 직렬화 할 수 없었습니다 (람다 함수가 객체로 전달됨). 알아 내고 고치는 데 시간이 걸렸습니다. 나중에 누군가가 코드를 직렬화하고, 수행 할 수 없으며, 이유를 파헤쳐 야 할 수도 있다는 것을 의미합니다 ... 내 문제는 피할 수없는 오류는 아니지만 비슷한 문제를 일으키는 것을 볼 수 있습니다.
logicOnAbstractions 2018 년

17

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


7

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

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

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

try:
    ...
except NelsonsExceptions:
    ...

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

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


Django 예외가 공통 기반에서 상속되지 않는다는 것이 흥미 롭습니다. docs.djangoproject.com/en/2.2/_modules/django/core/exceptions 특정 응용 프로그램에서 모든 예외를 잡아야하는 좋은 예가 있습니까? (일부 특정 유형의 응용 프로그램에만 유용 할 수 있습니다).
Yaroslav Nikitenko 2016 년

이 주제에 대한 좋은 기사 julien.danjou.info/python-exceptions-guide를 찾았습니다 . 예외는 주로 응용 프로그램 기반이 아닌 도메인 기반의 하위 클래스로 분류해야한다고 생각합니다. 앱이 HTTP 프로토콜에 관한 것이라면 HTTPError에서 파생됩니다. 앱의 일부가 TCP 인 경우 TCPError에서 해당 부분의 예외를 파생시킵니다. 그러나 앱이 많은 도메인 (파일, 권한 등)에 걸쳐 있으면 MyBaseException이 발생하는 이유가 줄어 듭니다. 아니면 '계층 위반'으로부터 보호해야합니까?
Yaroslav Nikitenko

6

아주 좋은 기사 " Python 예외에 대한 결정적인 가이드 "를 참조하십시오 . 기본 원칙은 다음과 같습니다.

  • 항상 (적어도) 예외에서 상속하십시오.
  • 항상 BaseException.__init__하나의 인수 만 사용하여 호출하십시오 .
  • 라이브러리를 빌드 할 때 Exception에서 상속되는 기본 클래스를 정의하십시오.
  • 오류에 대한 세부 사항을 제공하십시오.
  • 의미가있는 경우 내장 예외 유형에서 상속합니다.

또한 모듈을 구성하고 예외를 래핑하는 것에 대한 정보가 있습니다. 가이드를 읽는 것이 좋습니다.


1
이것은 왜 SO에서 일반적으로 가장 많이 답한 답을 확인하지만 가장 최근의 답을 확인하는 좋은 예입니다. 유용한 추가 기능, 감사합니다.
logicOnAbstractions

1
Always call BaseException.__init__ with only one argument.실제로 여러 인수를 허용하기 때문에 불필요한 제약 조건처럼 보입니다 .
유진 야마 쉬

@EugeneYarmash 동의합니다. 이제는 이해할 수 없습니다. 어쨌든 사용하지 않습니다. 기사를 다시 읽고 답을 확장해야 할 수도 있습니다.
Yaroslav Nikitenko

@EugeneYarmash 기사를 다시 읽었습니다. 여러 인수가있는 경우 C 구현은 "return PyObject_Str (self-> args);"를 호출합니다. 그것은 하나의 문자열이 여러 문자열보다 더 잘 작동한다는 것을 의미합니다. 확인 했습니까?
Yaroslav Nikitenko

3

이 예제를 사용해보십시오

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

1

자신의 예외를 올바르게 정의하려면 따라야 할 몇 가지 모범 사례가 있습니다.

  • 에서 상속되는 기본 클래스를 정의하십시오 Exception. 이를 통해 프로젝트와 관련된 예외를 포착 할 수 있습니다 (보다 구체적인 예외는 프로젝트에서 상속해야 함).

    class MyProjectError(Exception):
        """A base class for MyProject exceptions."""

    이러한 예외 클래스를 별도의 모듈 (예 :)로 구성하는 exceptions.py것이 일반적으로 좋습니다.

  • 추가 인수를 예외에 전달하려면 __init__()가변 개수의 인수 로 사용자 정의 메소드를 정의하십시오 . 기본 클래스의 문의 __init__()그것을 어떤 위치 인수를 전달 (즉, 기억 BaseException/Exception 임의의 수의 예상 위치 인수를 )

    class CustomError(MyProjectError):
        def __init__(self, *args, **kwargs):
            super(CustomError, self).__init__(*args)
            self.foo = kwargs.get('foo')

    추가 인수로 이러한 예외를 발생 시키려면 다음을 사용할 수 있습니다.

    raise CustomError('Something bad happened', foo='foo')

    이 디자인 은 기본 예외 클래스의 인스턴스를 파생 예외 클래스의 인스턴스로 대체 할 수 있기 때문에 Liskov 대체 원칙실제로 준수 합니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.