Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 553 – 내장 breakpoint()

Author:
Barry Warsaw <barry at python.org>
Status:
Final
Type:
Standards Track
Created:
05-Sep-2017
Python-Version:
3.7
Post-History:
05-Sep-2017, 07-Sep-2017, 13-Sep-2017
Resolution:
Python-Dev message

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

이 PEP에서는 호출 지점에서 Python 디버거로 진입하는 breakpoint()라는 새 내장 함수를 추가할 것을 제안합니다. 또한 어떤 디버거로 진입할지 선택할 수 있도록 sys 모듈에 두 개의 새 이름을 추가합니다.

근거

Python에는 오랫동안 표준 라이브러리에 pdb라는 훌륭한 디버거가 있었습니다. 중단점을 설정하는 코드는 일반적으로 다음과 같이 작성합니다.:

foo()
import pdb; pdb.set_trace()
bar()

따라서 foo()를 실행한 후 bar()를 실행하기 전에 Python은 디버거로 진입합니다. 그러나 이 관용구에는 몇 가지 단점이 있습니다.

  • 입력해야 할 내용이 많습니다(27자).
  • 오타를 내기 쉽습니다. PEP 작성자는 세미콜론을 빠뜨리거나 밑줄 대신 점을 입력하는 등 이 줄을 자주 잘못 입력합니다.
  • 이 방식은 디버깅을 pdb 선택과 직접 연결합니다. IDE나 다른 개발 환경을 사용하는 경우처럼 다른 디버깅 옵션이 있을 수도 있습니다.
  • Python 린터(예: flake8 [linters])는 이 줄에 두 개의 문이 포함되어 있기 때문에 이를 문제로 지적합니다. 이 관용구를 두 줄로 나누면 정리할 때 실수할 가능성이 커져 사용이 복잡해집니다. 즉, 코드를 더 이상 디버깅할 필요가 없을 때 그중 한 줄을 삭제하는 것을 잊을 수도 있습니다.

Python 개발자는 선택할 수 있는 다른 디버거도 많이 가지고 있지만, 이를 호출하는 방법을 기억하기가 어려울 수 있습니다. 예를 들어 IDE에 중단점을 설정하는 사용자 인터페이스가 있더라도 코드를 직접 편집하는 편이 더 편리할 수 있습니다. 프로그래밍 방식으로 디버거에 진입하는 API는 일관되지 않으므로 정확히 무엇을 입력해야 하는지 기억하기 어려울 수 있습니다.

이 PEP에서 제안하는 것처럼 디버거에 진입하는 범용 API를 제공하면 이러한 모든 문제를 해결할 수 있습니다.

제안

JavaScript 언어는 문이 나타나는 지점에서 디버거로 진입하는 debugger[js-debugger]을 제공합니다.

이 PEP에서는 호출 위치에서 Python 디버거로 진입하는 breakpoint()라는 새 내장 함수를 제안합니다. 따라서 위의 예제는 다음과 같이 작성할 수 있습니다.:

foo()
breakpoint()
bar()

또한 이 PEP에서는 sys 모듈에 sys.breakpointhook()sys.__breakpointhook__이라는 두 개의 새 이름 바인딩을 제안합니다. 기본적으로 sys.breakpointhook()은 실제로 pdb.set_trace()를 가져오고 해당 위치로 진입하는 작업을 구현하며, 다른 함수로 설정하여 breakpoint()이 진입하는 디버거를 변경할 수 있습니다.

sys.__breakpointhook__sys.breakpointhook()와 동일한 함수로 초기화되므로, 언제든지 sys.breakpointhook()을 기본값으로 쉽게 재설정할 수 있습니다(예: sys.breakpointhook = sys.__breakpointhook__를 실행). 이는 기존의 sys.displayhook() / sys.__displayhook__sys.excepthook() / sys.__excepthook__이 작동하는 방식 [hooks]과 정확히 같습니다.

내장 함수의 시그니처는 breakpoint(*args, **kws)입니다. 위치 인자와 키워드 인자는 sys.breakpointhook()에 그대로 전달되며, 시그니처가 일치해야 합니다. 그렇지 않으면 TypeError가 발생합니다. sys.breakpointhook()의 반환값은 상위 호출로 전달되고 breakpoint()에서 반환됩니다.

이에 대한 근거는 기반 디버거가 추가적인 선택적 인자를 허용할 수 있다는 관찰에 기반합니다. 예를 들어, IPython에서는 중단점에 진입할 때 출력되는 문자열을 지정할 수 있습니다 [ipython-embed]. Python 3.7부터 pdb 모듈은 선택적인 header 인자도 지원합니다 [pdb-header].

환경 변수

sys.breakpointhook()의 기본 구현은 PYTHONBREAKPOINT라는 새로운 환경 변수를 참조합니다. 이 환경 변수에는 다양한 값이 지정될 수 있습니다:

  • PYTHONBREAKPOINT=0은 디버깅을 비활성화합니다. 구체적으로 이 값이 지정되면 sys.breakpointhook()은 즉시 None을 반환합니다.
  • PYTHONBREAKPOINT= (즉, 빈 문자열)입니다. 이는 환경 변수를 전혀 설정하지 않은 것과 같으며, 이 경우 pdb.set_trace()가 평소와 같이 실행됩니다.
  • PYTHONBREAKPOINT=some.importable.callable입니다. 이 경우 sys.breakpointhook()some.importable 모듈을 가져오고, 그 결과로 생성된 모듈에서 callable 객체를 가져온 다음 이를 호출합니다. 값에는 점이 없는 문자열을 사용할 수도 있으며, 이 경우 내장 호출 가능 객체의 이름을 나타냅니다. 예를 들어 PYTHONBREAKPOINT=int입니다. (Guido는 setuptools 스타일의 엔트리 포인트 구문 [syntax]이 아니라 일반적인 Python 점 경로를 선호한다고 밝혔습니다.)

이 환경 변수를 사용하면 외부 프로세스가 중단점을 처리하는 방식을 제어할 수 있습니다. 몇 가지 사용 사례는 다음과 같습니다:

  • 프로덕션으로 배포된 모든 우발적인 breakpoint() 호출을 완전히 비활성화합니다. 실행 환경에서 PYTHONBREAKPOINT=0으로 설정하면 이를 구현할 수 있습니다. PEP 검토자들이 제안한 또 다른 방법은 이 경우 PYTHONBREAKPOINT=sys.exit으로 설정하는 것이었습니다.
  • 임베디드 실행을 위한 특수 디버거와 IDE를 통합합니다. IDE는 PYTHONBREAKPOINT을 내부 디버깅 훅으로 설정한 상태에서 디버깅 환경에서 프로그램을 실행합니다.

PYTHONBREAKPOINTsys.breakpointhook()에 도달할 때마다 다시 해석됩니다. 이를 통해 프로세스는 프로그램 실행 중에 값을 변경할 수 있으며, breakpoint()이 해당 변경에 응답하도록 할 수 있습니다. 디버거에 진입하면 정의상 실행이 중단되므로 이는 성능에 중요한 구역으로 간주되지 않습니다. 따라서 프로그램은 다음과 같이 할 수 있습니다::

os.environ['PYTHONBREAKPOINT'] = 'foo.bar.baz'
breakpoint()    # Imports foo.bar and calls foo.bar.baz()

sys.breakpointhook을 재정의하면 PYTHONBREAKPOINT에 대한 기본 조회가 무효화됩니다. 원하는 경우 PYTHONBREAKPOINT을 조회하는 것은 재정의 코드의 책임입니다.

PYTHONBREAKPOINT에 지정된 호출 가능 객체에 어떤 방식으로든 액세스하지 못하면(예: 가져오기에 실패하거나 결과 모듈에 호출 가능 객체가 없는 경우) RuntimeWarning이 발생하며 중단점 함수는 호출되지 않습니다.

다른 모든 PYTHON* 환경 변수와 마찬가지로, -E가 지정된 상태로 인터프리터가 시작되면 PYTHONBREAKPOINT은 무시된다는 점에 유의하십시오. 이는 기본 동작이 발생한다는 의미입니다(즉, pdb.set_trace()가 실행됩니다). -E가 적용된 경우 PYTHONBREAKPOINT=0을 다르게 처리하는 방안에 대한 논의도 있었지만 의견이 일치하지 않았으므로, 이를 특별한 예외로 취급할 만큼 충분히 특수하지 않다고 결정했습니다.

구현

제안된 구현에 대한 풀 리퀘스트가 있습니다 [impl].

실제 구현은 C로 되어 있지만, 이 기능의 Python 의사 코드는 대략 다음과 같습니다.:

# In builtins.
def breakpoint(*args, **kws):
    import sys
    missing = object()
    hook = getattr(sys, 'breakpointhook', missing)
    if hook is missing:
        raise RuntimeError('lost sys.breakpointhook')
    return hook(*args, **kws)

# In sys.
def breakpointhook(*args, **kws):
    import importlib, os, warnings
    hookname = os.getenv('PYTHONBREAKPOINT')
    if hookname is None or len(hookname) == 0:
        hookname = 'pdb.set_trace'
    elif hookname == '0':
        return None
    modname, dot, funcname = hookname.rpartition('.')
    if dot == '':
        modname = 'builtins'
    try:
        module = importlib.import_module(modname)
        hook = getattr(module, funcname)
    except:
        warnings.warn(
            'Ignoring unimportable $PYTHONBREAKPOINT: {}'.format(
                hookname),
            RuntimeWarning)
        return None
    return hook(*args, **kws)

__breakpointhook__ = breakpointhook

거부된 대안

새 키워드

저자는 원래 새 키워드 또는 break here와 같은 기존 키워드의 확장을 고려했습니다. 이는 여러 측면에서 거부됩니다.

  • 완전히 새로운 키워드를 활성화하려면 __future__가 필요합니다. 거의 모든 새로운 키워드가 기존 코드와 충돌할 수 있기 때문입니다. 이는 디버거에 진입하는 편리함을 없앱니다.
  • break here와 같은 확장된 키워드는 더 읽기 쉽고 __future__를 요구하지 않지만, 키워드 확장을 이 새로운 기능에 종속시키므로 PEP 548에서 제안된 것과 같은 더 유용한 확장을 막습니다.
  • 새 키워드를 추가하려면 수정된 문법과 아마도 새로운 바이트코드가 필요합니다. 이 각각은 구현을 더 복잡하게 만듭니다. 새로운 내장 기능은 기존 코드를 전혀 손상시키지 않으며(기존 모듈 전역 이름은 내장 기능을 가리기만 하므로) 구현하기도 매우 쉽습니다.

sys.breakpoint()

sys.breakpoint()가 아닙니까? 디버거를 호출하기 위해 임포트를 요구하는 것은 sys가 모든 모듈에서 임포트되지는 않는다는 이유로 명시적으로 거부됩니다. 이는 단지 타이핑을 더 요구할 뿐이며 다음과 같은 결과로 이어질 것입니다.:

import sys; sys.breakpoint()

이는 본 PEP가 해결하고자 하는 여러 문제를 그대로 물려받습니다.

버전 이력

  • 2019-10-13
    • 의사 코드의 except 절에 누락된 return None을 추가했습니다.
  • 2017-09-13
    • PYTHONBREAKPOINT 환경 변수가 일급 기능이 되었습니다.
  • 2017-09-07
    • debug()breakpoint()로 이름이 변경되었습니다.
    • 시그니처가 sys.breakpointhook()으로 그대로 전달되는 breakpoint(*args, **kws)로 변경되었습니다.

참고 문헌