Respond to Guido's comments.
This commit is contained in:
parent
db997d4a17
commit
bede39a536
144
pep-0553.rst
144
pep-0553.rst
|
@ -14,7 +14,8 @@ Abstract
|
|||
|
||||
This PEP proposes adding a new built-in function called ``breakpoint()`` which
|
||||
enters a Python debugger at the point of the call. Additionally, two new
|
||||
names are added to the ``sys`` module to make the debugger pluggable.
|
||||
names are added to the ``sys`` module to make the choice of which debugger is
|
||||
entered configurable.
|
||||
|
||||
|
||||
Rationale
|
||||
|
@ -40,11 +41,20 @@ enter the debugger. However this idiom has several disadvantages.
|
|||
environment.
|
||||
|
||||
* Python linters (e.g. flake8 [linters]_) complain about this line because it
|
||||
contains two statements. Breaking the idiom up into two lines further
|
||||
complicates the use of the debugger,
|
||||
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.
|
||||
|
||||
These problems can be solved by modeling a solution based on prior art in
|
||||
other languages, and utilizing a convention that already exists in Python.
|
||||
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
|
||||
|
@ -66,18 +76,26 @@ 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
|
||||
``breakpoint()`` enters. ``sys.__breakpointhook__`` then stashes the
|
||||
default value of ``sys.breakpointhook()`` to make it easy to reset.
|
||||
This exactly models the existing ``sys.displayhook()`` /
|
||||
``sys.__displayhook__`` and ``sys.excepthook()`` /
|
||||
``sys.__excepthook__`` [hooks]_.
|
||||
``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]_.
|
||||
|
||||
The signature of the built-in is ``breakpoint(*args, **kws)``. The positional
|
||||
and keyword arguments are passed straight through to ``sys.breakpointhook()``
|
||||
and the signatures must match or a ``TypeError`` will be raised. The return
|
||||
from ``sys.breakpointhook()`` is passed back up to, and returned from
|
||||
``breakpoint()``. Since ``sys.breakpointhook()`` by default calls
|
||||
``pdb.set_trace()`` by default it accepts no arguments.
|
||||
``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]_.
|
||||
|
||||
|
||||
Environment variable
|
||||
|
@ -118,8 +136,10 @@ are handled. Some uses cases include:
|
|||
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. (Of note, the implementation fast-tracks the
|
||||
``PYTHONBREAKPOINT=0`` case.)
|
||||
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
|
||||
|
@ -130,62 +150,6 @@ import fails, or the resulting module does not contain the callable), a
|
|||
``RuntimeWarning`` is issued, and no breakpoint function is called.
|
||||
|
||||
|
||||
Open issues
|
||||
===========
|
||||
|
||||
Confirmation from other debugger vendors
|
||||
----------------------------------------
|
||||
|
||||
We want to get confirmation from at least one alternative debugger
|
||||
implementation (e.g. PyCharm) that the hooks provided in this PEP will
|
||||
be useful to them.
|
||||
|
||||
|
||||
Evaluation of $PYTHONBREAKPOINT
|
||||
-------------------------------
|
||||
|
||||
There has been some mailing list discussion around this topic. The basic
|
||||
behavior as described above does not appear to be controversial. Guido has
|
||||
expressed a preference for ``$PYTHONBREAKPOINT`` consultation happening in
|
||||
the default implementation of ``sys.breakpointhook`` [envar]_.
|
||||
|
||||
The one point of discussion relates to whether the value of
|
||||
``$PYTHONBREAKPOINT`` should be loaded on interpreter start, and whether its
|
||||
value should be cached the first time it's accessed.
|
||||
|
||||
It is the PEP author's opinion that the environment variable need only be
|
||||
looked up at the time of use. It is also the author's opinion that the value
|
||||
of the environment variable can be accessed each time ``sys.breakpointhook``
|
||||
is run, to allow for maximum functional flexibility. Because this feature
|
||||
enters the debugger, any performance improvements for caching will be
|
||||
negligible and do not outweigh the flexibility. Further, because the special
|
||||
case of ``PYTHONBREAKPOINT=0`` is fast-tracked, the no-op code path is quite
|
||||
fast, and should be in the noise given the function calls of ``breakpoint()``
|
||||
-> ``sys.breakpointhook()``.
|
||||
|
||||
|
||||
Breakpoint bytecode
|
||||
-------------------
|
||||
|
||||
Related, there has been an idea to add a bytecode that calls
|
||||
``sys.breakpointhook()``. Whether built-in ``breakpoint()`` emits
|
||||
this bytecode (or gets peephole optimized to the bytecode) is an open
|
||||
issue. The bytecode is useful for debuggers that actively modify
|
||||
bytecode streams to trampoline into their own debugger. Having a
|
||||
"breakpoint" bytecode might allow them to avoid bytecode modification
|
||||
in order to invoke this trampoline. *NOTE*: It probably makes sense to split
|
||||
this idea into a separate PEP.
|
||||
|
||||
|
||||
Call a fancier object by default
|
||||
--------------------------------
|
||||
|
||||
Some folks want to be able to use other ``pdb`` interfaces such as
|
||||
``pdb.pm()``. Although this is a less commonly used API, it could be
|
||||
supported by binding ``sys.breakpointhook`` to an object that implements
|
||||
``__call__()``. Calling this object would call ``pdb.set_trace()``, but the
|
||||
object could expose other methods, such as ``pdb.pm()``, making invocation of
|
||||
it as handy as ``breakpoint.pm()``.
|
||||
|
||||
|
||||
Implementation
|
||||
|
@ -193,6 +157,40 @@ 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.
|
||||
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
|
||||
|
||||
|
||||
Rejected alternatives
|
||||
=====================
|
||||
|
@ -248,6 +246,12 @@ Version History
|
|||
References
|
||||
==========
|
||||
|
||||
.. [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
|
||||
|
||||
.. [linters]
|
||||
http://flake8.readthedocs.io/en/latest/
|
||||
|
||||
|
|
Loading…
Reference in New Issue