PEP 553 – Built-in 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
Abstract
This PEP proposes adding a new built-in function calledbreakpoint()
which
enters a Python debugger at the point of the call. Additionally, two new
names are added to thesys
module to make the choice of which debugger is
entered configurable.
Rationale
Python has long had a great debugger in its standard library calledpdb
.
Setting a break point is commonly written like this:
foo()
importpdb;pdb.set_trace()
bar()
Thus after executingfoo()
and before executingbar()
,Python will
enter the debugger. However this idiom has several disadvantages.
- It’s a lot to type (27 characters).
- It’s easy to typo. The PEP author often mistypes this line, e.g. omitting the semicolon, or typing a dot instead of an underscore.
- It ties debugging directly to the choice of pdb. There might be other debugging options, say if you’re using an IDE or some other development environment.
- Python linters (e.g. flake8[linters]) complain about this line because it contains two statements. Breaking the idiom up into two lines complicates its use because there are more opportunities for mistakes at clean up time. I.e. you might forget to delete one of those lines when you no longer need to debug the code.
Python developers also have many other debuggers to choose from, but remembering how to invoke them can be problematic. For example, even when IDEs have user interface for setting breakpoints, it may still be more convenient to just edit the code. The APIs for entering the debugger programmatically are inconsistent, so it can be difficult to remember exactly what to type.
We can solve all these problems by providing a universal API for entering the debugger, as proposed in this PEP.
Proposal
The JavaScript language provides adebugger
statement[js-debugger]which enters
the debugger at the point where the statement appears.
This PEP proposes a new built-in function calledbreakpoint()
which enters a Python debugger at the call site. Thus the example
above would be written like so:
foo()
breakpoint()
bar()
Further, this PEP proposes two new name bindings for thesys
module, calledsys.breakpointhook()
and
sys.__breakpointhook__
.By default,sys.breakpointhook()
implements the actual importing and entry intopdb.set_trace()
,
and it can be set to a different function to change the debugger that
breakpoint()
enters.
sys.__breakpointhook__
is initialized to the same function as
sys.breakpointhook()
so that you can always easily reset
sys.breakpointhook()
to the default value (e.g. by doing
sys.breakpointhook=sys.__breakpointhook__
). This is exactly the same as
how the existingsys.displayhook()
/sys.__displayhook__
and
sys.excepthook()
/sys.__excepthook__
work[hooks].
The signature of the built-in isbreakpoint(*args,**kws)
.The positional
and keyword arguments are passed straight through tosys.breakpointhook()
and the signatures must match or aTypeError
will be raised. The return
fromsys.breakpointhook()
is passed back up to, and returned from
breakpoint()
.
The rationale for this is based on the observation that the underlying
debuggers may accept additional optional arguments. For example, IPython
allows you to specify a string that gets printed when the break point is
entered[i Python -embed].As of Python 3.7, the pdb module also supports an
optionalheader
argument[pdb-header].
Environment variable
The default implementation ofsys.breakpointhook()
consults a new
environment variable calledPYTHONBREAKPOINT
.This environment variable
can have various values:
PYTHONBREAKPOINT=0
disables debugging. Specifically, with this valuesys.breakpointhook()
returnsNone
immediately.PYTHONBREAKPOINT=
(i.e. the empty string). This is the same as not setting the environment variable at all, in which casepdb.set_trace()
is run as usual.PYTHONBREAKPOINT=some.importable.callable
.In this case,sys.breakpointhook()
imports thesome.importable
module and gets thecallable
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 settingPYTHONBREAKPOINT=0
in the execution environment. Another suggestion by reviewers of the PEP was to setPYTHONBREAKPOINT=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 timesys.breakpointhook()
is
reached. This allows processes to change its value during the execution of a
program and havebreakpoint()
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()
Overridingsys.breakpointhook
defeats the default consultation of
PYTHONBREAKPOINT
.It is up to the overriding code to consult
PYTHONBREAKPOINT
if they want.
If access to thePYTHONBREAKPOINT
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 otherPYTHON*
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 treatingPYTHONBREAKPOINT=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.
Implementation
A pull request exists with the proposed implementation[impl].
While the actual implementation is in C, the Python pseudo-code for this feature looks roughly like the following:
# In builtins.
defbreakpoint(*args,**kws):
importsys
missing=object()
hook=getattr(sys,'breakpointhook',missing)
ifhookismissing:
raiseRuntimeError('lost sys.breakpointhook')
returnhook(*args,**kws)
# In sys.
defbreakpointhook(*args,**kws):
importimportlib,os,warnings
hookname=os.getenv('PYTHONBREAKPOINT')
ifhooknameisNoneorlen(hookname)==0:
hookname='pdb.set_trace'
elifhookname=='0':
returnNone
modname,dot,funcname=hookname.rpartition('.')
ifdot=='':
modname='builtins'
try:
module=importlib.import_module(modname)
hook=getattr(module,funcname)
except:
warnings.warn(
'Ignoring unimportable $PYTHONBREAKPOINT:{}'.format(
hookname),
RuntimeWarning)
returnNone
returnhook(*args,**kws)
__breakpointhook__=breakpointhook
Rejected alternatives
A new keyword
Originally, the author considered a new keyword, or an extension to an
existing keyword such asbreakhere
.This is rejected on several fronts.
- A brand new keyword would require a
__future__
to enable it since almost any new keyword could conflict with existing code. This negates the ease with which you can enter the debugger. - An extended keyword such as
breakhere
,while more readable and not requiring a__future__
would tie the keyword extension to this new feature, preventing more useful extensions such as those proposed in PEP 548. - A new keyword would require a modified grammar and likely a new bytecode. Each of these makes the implementation more complex. A new built-in breaks no existing code (since any existing module global would just shadow the built-in) and is quite easy to implement.
sys.breakpoint()
Why notsys.breakpoint()
?Requiring an import to invoke the debugger is
explicitly rejected becausesys
is not imported in every module. That
just requires more typing and would lead to:
importsys;sys.breakpoint()
which inherits several of the problems this PEP aims to solve.
Version History
- 2019-10-13
- Add missing
returnNone
inexcept
clause to pseudo-code.
- Add missing
- 2017-09-13
- The
PYTHONBREAKPOINT
environment variable is made a first class feature.
- The
- 2017-09-07
debug()
renamed tobreakpoint()
- Signature changed to
breakpoint(*args,**kws)
which is passed straight through tosys.breakpointhook()
.
References
Copyright
This document has been placed in the public domain.
Source:https://github / Python /peps/blob/main/peps/pep-0553.rst
Last modified:2023-09-09 17:39:29 GMT