동적으로 예외를 잡는 것에 대해 논의할 때 가장 마음에 드는 점은 숨겨진 버그와 재미를 찾을 수 있다는 점입니다...
문제가 있는 코드
다음 코드는 제품의 좋은 추상 코드에서 나온 것입니다. 약간(!) 이것은 일부 통계 데이터를 호출한 다음 이를 처리하는 함수입니다. 첫 번째는 소켓 연결을 사용하여 값을 얻는 것입니다. 소켓 오류가 발생할 수 있습니다. 통계는 시스템에서 중요하지 않으므로 오류만 기록하고 계속 진행하겠습니다.
(이 기사를 doctest를 사용하여 테스트했다는 점에 유의하세요. 이는 코드가 작동한다는 의미입니다!)
>>> def get_stats():
... 통과
...
>>> def do_something_with_stats(stats):
... 통과
...
>>> 시도:
... stats = get_stats()
.. 소켓 제외 .error:
...logging.warning("통계를 얻을 수 없습니다")
... else:
... do_something_with_stats(stats )
찾기
테스트할 때는 아무 문제도 발견하지 못했지만 실제로 정적 분석 보고서에 문제가 있는 것을 발견했습니다.
$ flake8 filename.py
filename.py:351:1: F821 정의되지 않은 이름 '소켓'
filename.py:352:1: F821 정의되지 않은 이름 'logging'
분명히 우리는 그것을 테스트하지 않았습니다. 문제는 우리가 코드에서 소켓과 로깅 모듈을 참조하지 않았다는 것입니다. 제가 놀랐던 점은 이것이 미리 NameError를 발생시키지 않았다는 것입니다. 예외문. 명사, 이러한 예외를 잡아야 한다면 무엇을 알아야 할까요!
사실이 아닌 것으로 밝혀졌는데, 예외문 조회가 느리게 완료되고 예외는 다음과 같습니다. 평가할 때만 발생합니다. 예외의 명시적인 선언을 '인수'로 맞춤 설정할 수 있습니다.
이것은 좋은 것일 수도 있고 나쁜 것일 수도 있습니다.
좋은 점(이전 단락에서 언급)
예외 매개변수는 어떤 형식으로든 숫자로 전달될 수 있습니다. 이를 통해 예외의 동적 매개변수를 캡처할 수 있습니다.
>> > def do_something():
... blob
...
>>> def 시도(action,ignore_spec):
.. . 시도:
... action()
...ignore_spec 제외:
... 통과
...
>>> try(do_something,ignore_spec=(NameError, TypeError))
>>> try(do_something,ignore_spec=TypeError)
추적(가장 최근 호출 마지막):
...
NameError: 전역 이름 ' blob'이 정의되지 않았습니다.
나쁜 점(이전 단락에서 언급)
명백한 단점은 오류가 발생한다는 것입니다. 예외 매개변수는 일반적으로 예외가 발생한 후에만 알 수 있지만, 예외를 사용하여 비정상적인 이벤트(예: 쓰기용 파일 열기 실패)를 포착하는 경우 특정 테스트 사례를 만들지 않는 한 예외(또는 모든 예외)가 트리거될 때 발생합니다. 그런 다음 이를 기록하고 일치하는 예외가 있는지 확인하고 자체 오류 예외를 발생시킵니다. 이는 일반적으로 NameError가 수행하는 작업입니다. def do_something():
... 1, 2 반환
...
>>> 시도:
... a, b = do_something()
... ValuError: # 죄송합니다 - 누군가가 입력할 수 없습니다
... print("죄송합니다")
... else:
... print("OK!") # do_something이 트리플을 반환할 때까지 '괜찮습니다'...
OK!
짜증난다(이전에서 언급함) 단락)
>>> 시도:
... TypeError = ZeroDivisionError # 이제 왜 이런 짓을 하겠습니까...?!
... 1 / 0
.. TypeError:
... print("잡았습니다!")
... else:
... print(" ok")
...
잡았습니다!
예외 매개변수는 이름으로 조회될 뿐만 아니라 다른 표현식도 다음과 같이 작동합니다.
>>> 시도:
... 1 / 0
... eval(''.join('Zero Division Error'.split())) 제외:
... print("잡혔습니다!")
... else:
... print("확인")
...
잡았습니다!
예외 매개변수는 런타임에 결정될 수 있을 뿐만 아니라 수명 주기 동안 예외 정보를 사용할 수도 있습니다. 다음은 발생한 예외를 포착하는 더 복잡한 방법입니다.
>>> import sys
>>> def current_exc_type():
... return sys.exc_info()[0]
.. .
>>> 시도:
... blob
... 제외 current_exc_type():
... 인쇄(" 알겠습니다. !")
...
알겠습니다!
분명히 이것이 우리가 정말로 찾고 있는 것입니다. 예외 핸들러를 작성할 때 가장 먼저 해야 할 일은 무엇입니까? 이것이
(바이트) 코드
예외 처리에서 어떻게 작동하는지 확인하기 위해 예외 예제에서 dis.dis()를 실행했습니다(여기서 분해는 Python 2.7에서 수행되었습니다. Python 3.3에서는 다른 바이트코드가 생성되지만 기본적으로 유사합니다.):
>>> import dis
> >> def x():
... try :
... 통과
... Blobbity 제외:
... 인쇄("나쁨")
... else:
... print("좋음")
...
>>> dis.dis(x) # doctest: +NORMALIZE_WHITESPACE
2 0 SETUP_EXCEPT 4(~7)
3 3 POP_BLOCK
4 JUMP_FORWARD 22(29까지)
4 >> 7 DUP_TOP
8 LOAD_GLOBAL 0(Blobbity)
11 COMP ARE_OP 10(예외 일치)
14 POP_JUMP_IF_FALSE 28
17 POP_TOP
18 POP_TOP
19 POP_TOP
5 20 LOAD_CONST 1 ('나쁨')
23 PRINT_ITEM
24 PRINT_NEWLINE
25 JUMP_FORWARD 6(34까지)
>> 28 END_FINALLY
7 >> 29 LOAD_CONST 2 ('좋음')
32 PRINT_ITEM
33 PRINT_NEWLINE
>> 34 LOAD_CONST 0 (없음)
37 RETURN_VALUE
这显示了我来预期的问题(문제). 异常处理"看起来"完全是按光Python内起机运行. 这一步完全没有必要知道关于后续的异常“捕获”语句, 并且如果没有异常抛take它们将被完全忽略了.SETUP_EXCEPT并不关心发生了什么, 仅仅是如果发生了异常, 第一个处理程序应该被评估,然后第二个,以此类推.
每个处理程序都有两part分组成: 获得一个异常的规则, 정말. 一切道是延迟的, 一切看起来正如对你的逐行的释器的角島来考虑. 没有任务聪明的事情发生了,只是突然使得它看起来不常聪明.
总结
虽然这种动态的异常参당신은 내가 큰 것을 가지고 있고, 但是这当중앙에 더 많은 것을 가지고 있습니다. 当然去实现它们当中的许多或许是个馊主意,呵呵
有时并不能总是凭直觉来确认多少Python性支持 -例如 는 表达式 와 声 의 类被显式接受 의 例如 에서 , (而不是函数, 방법, 전체局작용域),但是并不是所有阯如此灵活的. 虽然(我认为)那将是十分美好的, 表达式被禁止应用于装饰器 - 以下是Python语법错误:
@(lambda fn: fn)
데프 x() :
통과
这个是尝试动态异常参数通过给定类型传递给第一个异常的例子, 静静的忍受复的异常:
>>> class Pushover(객체):
... ex_spec = set()
...
... def 시도(자체, 동작):
... 시도해 보세요:
... return action()
... tuple(self.exc_spec) 제외:
... 통과
... BaseException을 e:
... self.exc_spec.add(e.__class__)
... 인상
...
>>> pushover = Pushover()
>>>
>>> for _ in range(4):
... 시도해 보세요.
... pushover.attempt(lambda: 1 / 0)
... 제외:
... 인쇄("Boo")
... else:
... 인쇄("야!")
부야
야!
야!
야!