PEP553: 새로운 빌트인 breakpoint() 함수

2023년 05월 10일

pep

# 파이썬# PEP553# pdb# breakpoint

PEP 553: 새로운 빌트인 breakpoint() 함수

초록

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

근거

파이썬은 pdb라 불리는 훌륭한 디버거를 표준 라이브러리에 오래전부터 가지고 있었습니다. 브레이크 포인트는 흔히 다음과 같이 작성했습니다.

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

foo() 함수가 실행된 후 그리고 bar() 함수가 실행되기 전에, 파이썬은 디버거로 진입합니다. 이러한 관용적 사용은 몇가지 단점들이 있습니다.

  • 많은 타이핑이 필요합니다. (27개의 문자) -> import pdb; pdb.set_trace()
  • 잘못치는 경우가 종종 있습니다. (예를 들어, 세미콜론을 뺴먹는 경우 혹은 언더스코어 대신에 점을 찍는 경우)
  • 디버거를 선택할 수 없습니다. (pdb가 아닌 다른 디버거를 사용하고 싶은 경우)
  • 파이썬 린터 (예: flake8)은 이 줄에 두 개의 statements가 있기때문에 이 줄에 대해 경고를 합니다. 이 관용구를 두 줄로 나누면 정리할 때 더 많은 실수가 발생할 수 있습니다. 필요가 없어졌을 때 이 두 줄 중 하나를 삭제하는 것을 잊을 수 있습니다.

파이썬 개발자들은 많은 다른 디버거들을 선택할 수 있습니다. 하지만, 이들을 어떻게 호출하는지 기억하는 것은 문제가 될 수 있습니다. 예를 들어, IDE가 브레이크 포인트를 설정하는 사용자 인터페이스를 가지고 있더라도, 코드를 편집하는 것이 더 편리할 수 있습니다. 디버거로 진입하는 API들은 일관성이 없기 때문에, 정확히 무엇을 입력해야 하는지 기억하기 어려울 수 있습니다.

이 PEP의 제안대로, 디버거로 진입하기 위한 universal API를 제공함으로써 이러한 모든 문제를 해결할 수 있습니다.

제안

자바스크립트 언어는 statement가 등장하는 지점에서 디버거로 진입할 수 있는 디버거 statement를 제공합니다.

이 PEP는 새로운 내장 함수인 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__가 작동하는 방식과 정확히 동일합니다.

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

이를 위한 근거는 기본 디버거가 추가적인 선택적 인자를 받을 수 있다는 관찰에 기반합니다. 예를 들어, IPython은 브레이크 포인트가 진입될 때 출력되는 문자열을 지정할 수 있습니다. [ipython-embed] Python 3.7부터 pdb 모듈은 선택적 헤더 인자를 지원합니다. [pdb-header]

환경 변수

The default implementation of sys.breakpointhook() consults a new environment variable called PYTHONBREAKPOINT. This environment variable can have various values:

PYTHONBREAKPOINT=0 disables debugging. Specifically, with this value sys.breakpointhook() returns None immediately. PYTHONBREAKPOINT= (i.e. the empty string). This is the same as not setting the environment variable at all, in which case pdb.set_trace() is run as usual. PYTHONBREAKPOINT=some.importable.callable. In this case, sys.breakpointhook() imports the some.importable module and gets the callable object from the resulting module, which it then calls. The value may be a string with no dots, in which case it names a built-in callable, e.g. PYTHONBREAKPOINT=int. (Guido has expressed the preference for normal Python dotted-paths, not setuptools-style entry point syntax [syntax].) This environment variable allows external processes to control how breakpoints are handled. Some uses cases include:

Completely disabling all accidental breakpoint() calls pushed to production. This could be accomplished by setting PYTHONBREAKPOINT=0 in the execution environment. Another suggestion by reviewers of the PEP was to set PYTHONBREAKPOINT=sys.exit in this case. IDE integration with specialized debuggers for embedded execution. The IDE would run the program in its debugging environment with PYTHONBREAKPOINT set to their internal debugging hook. PYTHONBREAKPOINT is re-interpreted every time sys.breakpointhook() is reached. This allows processes to change its value during the execution of a program and have breakpoint() respond to those changes. It is not considered a performance critical section since entering a debugger by definition stops execution. Thus, programs can do the following:

os.environ[‘PYTHONBREAKPOINT’] = ‘foo.bar.baz’ breakpoint() # Imports foo.bar and calls foo.bar.baz() Overriding sys.breakpointhook defeats the default consultation of PYTHONBREAKPOINT. It is up to the overriding code to consult PYTHONBREAKPOINT if they want.

If access to the PYTHONBREAKPOINT callable fails in any way (e.g. the import fails, or the resulting module does not contain the callable), a RuntimeWarning is issued, and no breakpoint function is called.

Note that as with all other PYTHON* environment variables, PYTHONBREAKPOINT is ignored when the interpreter is started with -E. This means the default behavior will occur (i.e. pdb.set_trace() will run). There was some discussion about alternatively treating PYTHONBREAKPOINT=0 when -E as in effect, but the opinions were inconclusive, so it was decided that this wasn’t special enough for a special case.

구현

pull request exists with the proposed implementation.

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

# 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
© 2025, 미나리와 함께 만들었음