From bede39a536b5afe42fdc3bec63b6c83fa9db3957 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Mon, 2 Oct 2017 13:39:55 -0400 Subject: [PATCH] Respond to Guido's comments. --- pep-0553.rst | 144 ++++++++++++++++++++++++++------------------------- 1 file changed, 74 insertions(+), 70 deletions(-) diff --git a/pep-0553.rst b/pep-0553.rst index 3793cd567..5884936ad 100644 --- a/pep-0553.rst +++ b/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/