2017-09-05 16:42:34 -04:00
|
|
|
|
PEP: 553
|
2017-09-06 12:50:56 -04:00
|
|
|
|
Title: Built-in breakpoint()
|
2017-09-05 16:42:34 -04:00
|
|
|
|
Author: Barry Warsaw <barry@python.org>
|
|
|
|
|
Status: Draft
|
|
|
|
|
Type: Standards Track
|
|
|
|
|
Content-Type: text/x-rst
|
|
|
|
|
Created: 2017-09-05
|
|
|
|
|
Python-Version: 3.7
|
2017-09-13 21:55:54 -04:00
|
|
|
|
Post-History: 2017-09-05, 2017-09-07, 2017-09-13
|
2017-09-05 16:42:34 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Abstract
|
|
|
|
|
========
|
|
|
|
|
|
2017-09-06 12:50:56 -04:00
|
|
|
|
This PEP proposes adding a new built-in function called ``breakpoint()`` which
|
2017-09-05 16:42:34 -04:00
|
|
|
|
enters a Python debugger at the point of the call. Additionally, two new
|
2017-10-02 13:39:55 -04:00
|
|
|
|
names are added to the ``sys`` module to make the choice of which debugger is
|
|
|
|
|
entered configurable.
|
2017-09-05 16:42:34 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rationale
|
|
|
|
|
=========
|
|
|
|
|
|
|
|
|
|
Python has long had a great debugger in its standard library called ``pdb``.
|
|
|
|
|
Setting a break point is commonly written like this::
|
|
|
|
|
|
|
|
|
|
foo()
|
|
|
|
|
import pdb; pdb.set_trace()
|
|
|
|
|
bar()
|
|
|
|
|
|
|
|
|
|
Thus after executing ``foo()`` and before executing ``bar()``, 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.
|
|
|
|
|
|
2017-09-13 19:54:22 -04:00
|
|
|
|
* Python linters (e.g. flake8 [linters]_) complain about this line because it
|
2017-10-02 13:39:55 -04:00
|
|
|
|
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.
|
2017-09-05 16:42:34 -04:00
|
|
|
|
|
2017-10-02 13:39:55 -04:00
|
|
|
|
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.
|
2017-09-05 16:42:34 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Proposal
|
|
|
|
|
========
|
|
|
|
|
|
2017-09-13 19:54:22 -04:00
|
|
|
|
The JavaScript language provides a ``debugger`` statement [java]_ which enters
|
2017-09-05 16:42:34 -04:00
|
|
|
|
the debugger at the point where the statement appears.
|
|
|
|
|
|
2017-09-06 12:50:56 -04:00
|
|
|
|
This PEP proposes a new built-in function called ``breakpoint()``
|
|
|
|
|
which enters a Python debugger at the call site. Thus the example
|
|
|
|
|
above would be written like so::
|
2017-09-05 16:42:34 -04:00
|
|
|
|
|
|
|
|
|
foo()
|
2017-09-06 12:50:56 -04:00
|
|
|
|
breakpoint()
|
2017-09-05 16:42:34 -04:00
|
|
|
|
bar()
|
|
|
|
|
|
2017-09-06 12:50:56 -04:00
|
|
|
|
Further, this PEP proposes two new name bindings for the ``sys``
|
|
|
|
|
module, called ``sys.breakpointhook()`` and
|
|
|
|
|
``sys.__breakpointhook__``. By default, ``sys.breakpointhook()``
|
|
|
|
|
implements the actual importing and entry into ``pdb.set_trace()``,
|
|
|
|
|
and it can be set to a different function to change the debugger that
|
2017-10-02 13:39:55 -04:00
|
|
|
|
``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 the existing ``sys.displayhook()`` / ``sys.__displayhook__`` and
|
|
|
|
|
``sys.excepthook()`` / ``sys.__excepthook__`` work [hooks]_.
|
2017-09-05 16:42:34 -04:00
|
|
|
|
|
2017-09-07 14:27:55 -04:00
|
|
|
|
The signature of the built-in is ``breakpoint(*args, **kws)``. The positional
|
|
|
|
|
and keyword arguments are passed straight through to ``sys.breakpointhook()``
|
2017-09-07 14:40:12 -04:00
|
|
|
|
and the signatures must match or a ``TypeError`` will be raised. The return
|
2017-09-07 14:27:55 -04:00
|
|
|
|
from ``sys.breakpointhook()`` is passed back up to, and returned from
|
2017-10-02 13:39:55 -04:00
|
|
|
|
``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 [ipython-embed]_. As of Python 3.7, the pdb module also supports an
|
|
|
|
|
optional ``header`` argument [pdb-header]_.
|
2017-09-05 16:42:34 -04:00
|
|
|
|
|
|
|
|
|
|
2017-09-13 11:02:10 -04:00
|
|
|
|
Environment variable
|
|
|
|
|
====================
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
2017-09-17 13:43:29 -04:00
|
|
|
|
* ``PYTHONBREAKPOINT=`` (i.e. the empty string). This is the same as not
|
2017-09-13 11:02:10 -04:00
|
|
|
|
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
|
2017-09-13 19:54:22 -04:00
|
|
|
|
syntax [syntax]_.)
|
2017-09-13 11:02:10 -04:00
|
|
|
|
|
|
|
|
|
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
|
2017-10-02 13:39:55 -04:00
|
|
|
|
definition stops execution. Thus, programs can do the following::
|
|
|
|
|
|
|
|
|
|
os.environ['PYTHONBREAKPOINT'] = 'foo.bar.baz'
|
|
|
|
|
breakpoint() # Imports foo.bar and calls foo.bar.baz()
|
2017-09-13 11:02:10 -04:00
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
2017-09-06 20:16:32 -04:00
|
|
|
|
|
2017-09-05 16:42:34 -04:00
|
|
|
|
|
|
|
|
|
Implementation
|
|
|
|
|
==============
|
|
|
|
|
|
2017-09-13 19:54:22 -04:00
|
|
|
|
A pull request exists with the proposed implementation [impl]_.
|
2017-09-05 16:42:34 -04:00
|
|
|
|
|
2017-10-02 13:39:55 -04:00
|
|
|
|
While the actual implementation is in C, the Python pseudo-code for this
|
|
|
|
|
feature looks roughly like the following::
|
|
|
|
|
|
|
|
|
|
# In builtins.
|
|
|
|
|
def breakpoint(*args, **kws):
|
|
|
|
|
import sys
|
|
|
|
|
hook = getattr(sys, 'breakpointhook', None)
|
|
|
|
|
if hook is None:
|
|
|
|
|
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 hook(*args, **kws)
|
|
|
|
|
|
|
|
|
|
__breakpointhook__ = breakpointhook
|
|
|
|
|
|
2017-09-05 16:42:34 -04:00
|
|
|
|
|
|
|
|
|
Rejected alternatives
|
|
|
|
|
=====================
|
|
|
|
|
|
|
|
|
|
A new keyword
|
|
|
|
|
-------------
|
|
|
|
|
|
|
|
|
|
Originally, the author considered a new keyword, or an extension to an
|
|
|
|
|
existing keyword such as ``break here``. 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 ``break here``, 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.
|
|
|
|
|
|
2017-09-05 17:12:57 -04:00
|
|
|
|
|
2017-09-06 12:50:56 -04:00
|
|
|
|
sys.breakpoint()
|
|
|
|
|
----------------
|
|
|
|
|
|
|
|
|
|
Why not ``sys.breakpoint()``? Requiring an import to invoke the debugger is
|
2017-09-05 17:12:57 -04:00
|
|
|
|
explicitly rejected because ``sys`` is not imported in every module. That
|
|
|
|
|
just requires more typing and would lead to::
|
|
|
|
|
|
2017-09-06 12:50:56 -04:00
|
|
|
|
import sys; sys.breakpoint()
|
2017-09-05 17:12:57 -04:00
|
|
|
|
|
|
|
|
|
which inherits several of the problems this PEP aims to solve.
|
|
|
|
|
|
2017-09-05 16:42:34 -04:00
|
|
|
|
|
2017-09-07 14:27:55 -04:00
|
|
|
|
Version History
|
|
|
|
|
===============
|
|
|
|
|
|
2017-09-13 22:12:12 -04:00
|
|
|
|
* 2017-09-13
|
|
|
|
|
|
|
|
|
|
* The ``PYTHONBREAKPOINT`` environment variable is made a first class
|
|
|
|
|
feature.
|
|
|
|
|
|
2017-09-07 14:27:55 -04:00
|
|
|
|
* 2017-09-07
|
|
|
|
|
|
|
|
|
|
* ``debug()`` renamed to ``breakpoint()``
|
|
|
|
|
* Signature changed to ``breakpoint(*args, **kws)`` which is passed straight
|
|
|
|
|
through to ``sys.breakpointhook()``.
|
|
|
|
|
|
|
|
|
|
|
2017-09-05 16:42:34 -04:00
|
|
|
|
References
|
|
|
|
|
==========
|
|
|
|
|
|
2017-10-02 13:39:55 -04:00
|
|
|
|
.. [ipython-embed]
|
|
|
|
|
http://ipython.readthedocs.io/en/stable/api/generated/IPython.terminal.embed.html
|
|
|
|
|
|
|
|
|
|
.. [pdb-header]
|
|
|
|
|
https://docs.python.org/3.7/library/pdb.html#pdb.set_trace
|
|
|
|
|
|
2017-09-13 19:54:22 -04:00
|
|
|
|
.. [linters]
|
|
|
|
|
http://flake8.readthedocs.io/en/latest/
|
2017-09-05 16:42:34 -04:00
|
|
|
|
|
2017-09-13 19:54:22 -04:00
|
|
|
|
.. [java]
|
|
|
|
|
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger
|
2017-09-05 16:42:34 -04:00
|
|
|
|
|
2017-09-13 19:54:22 -04:00
|
|
|
|
.. [hooks]
|
|
|
|
|
https://docs.python.org/3/library/sys.html#sys.displayhook
|
2017-09-05 16:42:34 -04:00
|
|
|
|
|
2017-09-13 19:54:22 -04:00
|
|
|
|
.. [syntax]
|
|
|
|
|
http://setuptools.readthedocs.io/en/latest/setuptools.html?highlight=console#automatic-script-creation
|
2017-09-13 11:02:10 -04:00
|
|
|
|
|
2017-09-13 19:54:22 -04:00
|
|
|
|
.. [impl]
|
|
|
|
|
https://github.com/python/cpython/pull/3355
|
|
|
|
|
|
|
|
|
|
.. [envar]
|
|
|
|
|
https://mail.python.org/pipermail/python-dev/2017-September/149447.html
|
2017-09-05 16:42:34 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Copyright
|
|
|
|
|
=========
|
|
|
|
|
|
|
|
|
|
This document has been placed in the public domain.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
..
|
|
|
|
|
Local Variables:
|
|
|
|
|
mode: indented-text
|
|
|
|
|
indent-tabs-mode: nil
|
|
|
|
|
sentence-end-double-space: t
|
|
|
|
|
fill-column: 70
|
|
|
|
|
coding: utf-8
|
|
|
|
|
End:
|