Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
9dc577a
Watch out for NULL return.
warsaw Aug 30, 2017
bc4634d
Add a blurb entry for the issue.
warsaw Sep 4, 2017
f7e3c40
Typo in blurb entry.
warsaw Sep 4, 2017
6696c23
Merge branch 'master' into compile-null-ptr
warsaw Sep 4, 2017
4600bcf
Set an appropriate exception.
warsaw Sep 4, 2017
0f46af7
Add a debug() builtin.
warsaw Sep 4, 2017
a141f63
Document the new stuff.
warsaw Sep 4, 2017
79d7ac8
Add tests for the debug() builtin.
warsaw Sep 4, 2017
1666925
Undo a branch leak.
warsaw Sep 4, 2017
8437033
Add versionadded tags and cleanup.
warsaw Sep 5, 2017
6534542
Merge branch 'master' into debughook
warsaw Sep 5, 2017
2d75d7a
Merge branch 'master' into debughook
warsaw Sep 5, 2017
ed649fa
Add a blurb for PEP 553.
warsaw Sep 5, 2017
0bea882
Merge branch 'master' into debughook
warsaw Sep 6, 2017
eee151d
Merge branch 'master' into debughook
warsaw Sep 6, 2017
05b7cae
debug() -> breakpoint()
warsaw Sep 6, 2017
6b9a59c
Add a test for breakpointhook reset.
warsaw Sep 6, 2017
5e0b93a
Merge branch 'master' into debughook
warsaw Sep 7, 2017
c57d0dc
Signature changed to breakpoint(*args, **kws).
warsaw Sep 7, 2017
eb17d0f
Merge branch 'master' into debughook
warsaw Sep 7, 2017
244bff5
Update documentation.
warsaw Sep 7, 2017
c4bed66
Merge branch 'master' into debughook
warsaw Sep 7, 2017
6eb3470
Make patchheck happy.
warsaw Sep 7, 2017
af81484
Merge branch 'master' into debughook
warsaw Sep 8, 2017
f3200c7
Support $PYTHONBREAKPOINT
warsaw Sep 8, 2017
1c60e11
Merge branch 'master' into debughook
warsaw Sep 10, 2017
ed41006
$PYTHONBREAKPOINT processing moved to sys.breakpointhook()
warsaw Sep 12, 2017
b84a018
Accept *args, **kws
warsaw Sep 12, 2017
f6db9e3
Merge branch 'master' into debughook
warsaw Sep 12, 2017
cf9e7ac
Add a test and fix some behavior.
warsaw Sep 12, 2017
e46c59d
Merge branch 'master' into debughook
warsaw Sep 13, 2017
9bc36b8
Merge branch 'debughook' of github.com:warsaw/cpython into debughook
warsaw Sep 13, 2017
b5c2393
$PYTHONBREAKPOINT docs.
warsaw Sep 13, 2017
2846d3b
Merge branch 'master' into debughook
Oct 2, 2017
50c3e40
Add a What's New.
Oct 3, 2017
6b23d5b
Merge branch 'master' into debughook
Oct 3, 2017
0d2fae5
Several fixes addressing review comments.
Oct 4, 2017
1cf3e06
Respond to reviewer comments
Oct 5, 2017
6217e90
Use METH_FAST and _PyObject_FastCallKeywords()
Oct 5, 2017
28bd8e4
Merge branch 'master' into debughook
Oct 5, 2017
8808367
Use a MagicMock.
Oct 5, 2017
bd68b76
Merge branch 'master' into debughook
Oct 5, 2017
fea5d3d
Merge branch 'master' into debughook
Oct 5, 2017
599108d
Some tests must be skipped when -E is given
Oct 5, 2017
a74f23e
Add a missing versionadded
Oct 5, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 31 additions & 17 deletions Doc/library/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,24 @@ Built-in Functions
The Python interpreter has a number of functions and types built into it that
are always available. They are listed here in alphabetical order.

=================== ================= ================== ================ ====================
.. .. Built-in Functions .. ..
=================== ================= ================== ================ ====================
:func:`abs` |func-dict|_ :func:`help` :func:`min` :func:`setattr`
:func:`all` :func:`dir` :func:`hex` :func:`next` :func:`slice`
:func:`any` :func:`divmod` :func:`id` :func:`object` :func:`sorted`
:func:`ascii` :func:`enumerate` :func:`input` :func:`oct` :func:`staticmethod`
:func:`bin` :func:`eval` :func:`int` :func:`open` |func-str|_
:func:`bool` :func:`exec` :func:`isinstance` :func:`ord` :func:`sum`
|func-bytearray|_ :func:`filter` :func:`issubclass` :func:`pow` :func:`super`
|func-bytes|_ :func:`float` :func:`iter` :func:`print` |func-tuple|_
:func:`callable` :func:`format` :func:`len` :func:`property` :func:`type`
:func:`chr` |func-frozenset|_ |func-list|_ |func-range|_ :func:`vars`
:func:`classmethod` :func:`getattr` :func:`locals` :func:`repr` :func:`zip`
:func:`compile` :func:`globals` :func:`map` :func:`reversed` :func:`__import__`
=================== ================= ================== ================== ====================
.. .. Built-in Functions .. ..
=================== ================= ================== ================== ====================
:func:`abs` :func:`delattr` :func:`hash` |func-memoryview|_ |func-set|_
:func:`all` |func-dict|_ :func:`help` :func:`min` :func:`setattr`
:func:`any` :func:`dir` :func:`hex` :func:`next` :func:`slice`
:func:`ascii` :func:`divmod` :func:`id` :func:`object` :func:`sorted`
:func:`bin` :func:`enumerate` :func:`input` :func:`oct` :func:`staticmethod`
:func:`bool` :func:`eval` :func:`int` :func:`open` |func-str|_
:func:`breakpoint` :func:`exec` :func:`isinstance` :func:`ord` :func:`sum`
|func-bytearray|_ :func:`filter` :func:`issubclass` :func:`pow` :func:`super`
|func-bytes|_ :func:`float` :func:`iter` :func:`print` |func-tuple|_
:func:`callable` :func:`format` :func:`len` :func:`property` :func:`type`
:func:`chr` |func-frozenset|_ |func-list|_ |func-range|_ :func:`vars`
:func:`classmethod` :func:`getattr` :func:`locals` :func:`repr` :func:`zip`
:func:`compile` :func:`globals` :func:`map` :func:`reversed` :func:`__import__`
:func:`complex` :func:`hasattr` :func:`max` :func:`round`
:func:`delattr` :func:`hash` |func-memoryview|_ |func-set|_
=================== ================= ================== ================ ====================
=================== ================= ================== ================== ====================

.. using :func:`dict` would create a link to another page, so local targets are
used, with replacement texts to make the output in the table consistent
Expand Down Expand Up @@ -113,6 +113,20 @@ are always available. They are listed here in alphabetical order.
.. index:: pair: Boolean; type


.. function:: breakpoint(*args, **kws)

This function drops you into the debugger at the call site. Specifically,
it calls :func:`sys.breakpointhook`, passing ``args`` and ``kws`` straight
through. By default, ``sys.breakpointhook()`` calls
:func:`pdb.set_trace()` expecting no arguments. In this case, it is
purely a convenience function so you don't have to explicitly import
:mod:`pdb` or type as much code to enter the debugger. However,
:func:`sys.breakpointhook` can be set to some other function and
:func:`breakpoint` will automatically call that, allowing you to drop into
the debugger of choice.

.. versionadded:: 3.7

.. _func-bytearray:
.. class:: bytearray([source[, encoding[, errors]]])
:noindex:
Expand Down
28 changes: 24 additions & 4 deletions Doc/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,21 @@ always available.
This function should be used for internal and specialized purposes only.


.. function:: breakpointhook()

This hook function is called by built-in :func:`breakpoint`. By default,
it drops you into the :mod:`pdb` debugger, but it can be set to any other
function so that you can choose which debugger gets used.

The signature of this function is dependent on what it calls. For example,
the default binding (e.g. ``pdb.set_trace()``) expects no arguments, but
you might bind it to a function that expects additional arguments
(positional and/or keyword). The built-in ``breakpoint()`` function passes
its ``*args`` and ``**kws`` straight through. Whatever
``breakpointhooks()`` returns is returned from ``breakpoint()``.

.. versionadded:: 3.7

.. function:: _debugmallocstats()

Print low-level information to stderr about the state of CPython's memory
Expand Down Expand Up @@ -187,14 +202,19 @@ always available.
customized by assigning another three-argument function to ``sys.excepthook``.


.. data:: __displayhook__
.. data:: __breakpointhook__
__displayhook__
__excepthook__

These objects contain the original values of ``displayhook`` and ``excepthook``
at the start of the program. They are saved so that ``displayhook`` and
``excepthook`` can be restored in case they happen to get replaced with broken
These objects contain the original values of ``breakpointhook``,
``displayhook``, and ``excepthook`` at the start of the program. They are
saved so that ``breakpointhook``, ``displayhook`` and ``excepthook`` can be
restored in case they happen to get replaced with broken or alternative
objects.

.. versionadded:: 3.7
__breakpointhook__


.. function:: exc_info()

Expand Down
54 changes: 54 additions & 0 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from operator import neg
from test.support import TESTFN, unlink, check_warnings
from test.support.script_helper import assert_python_ok
from unittest.mock import patch
try:
import pty, signal
except ImportError:
Expand Down Expand Up @@ -352,6 +353,59 @@ def f(): """doc"""
rv = ns['f']()
self.assertEqual(rv, (debugval, docstring))

def test_breakpoint(self):
with patch('pdb.set_trace') as mock:
breakpoint()
mock.assert_called_once()

def test_breakpoint_with_breakpointhook_set(self):
call_status = 'Not called'
def my_breakpointhook():
nonlocal call_status
call_status = 'Called'
try:
sys.breakpointhook = my_breakpointhook
breakpoint()
finally:
sys.breakpointhook = sys.__breakpointhook__
self.assertEqual(call_status, 'Called')

def test_breakpoint_with_breakpointhook_reset(self):
call_status = 'Not called'
def my_breakpointhook():
nonlocal call_status
call_status = 'Called'
try:
sys.breakpointhook = my_breakpointhook
breakpoint()
finally:
sys.breakpointhook = sys.__breakpointhook__
self.assertEqual(call_status, 'Called')
with patch('pdb.set_trace') as mock:
breakpoint()
mock.assert_called_once()

def test_breakpoint_with_args_and_keywords(self):
call_args = None
def my_breakpointhook(*args, **kws):
nonlocal call_args
call_args = args, kws
try:
sys.breakpointhook = my_breakpointhook
breakpoint(1, 2, 3, four=4, five=5)
finally:
sys.breakpointhook = sys.__breakpointhook__
self.assertEqual(call_args, ((1, 2, 3), dict(four=4, five=5)))

def test_breakpoint_with_passthru_error(self):
def my_breakpointhook():
pass
try:
sys.breakpointhook = my_breakpointhook
self.assertRaises(TypeError, breakpoint, 1, 2, 3, four=4, five=5)
finally:
sys.breakpointhook = sys.__breakpointhook__

def test_delattr(self):
sys.spam = 1
delattr(sys, 'spam')
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -3523,7 +3523,8 @@ def test_builtins_have_signatures(self):
needs_semantic_update = {"round"}
no_signature |= needs_semantic_update
# These need *args support in Argument Clinic
needs_varargs = {"min", "max", "print", "__build_class__"}
needs_varargs = {"breakpoint", "min", "max", "print",
"__build_class__"}
no_signature |= needs_varargs
# These simply weren't covered in the initial AC conversion
# for builtin callables
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
PEP 553 - Add a new built-in called ``breakpoint()`` which calls

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PEP 553 can be written:

:pep:`553`

to get a direct link to the PEP ;-)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn’t docutils auto-linkify text that looks like RFC 1234 or PEP 123?

``sys.breakpointhook()``. By default this imports ``pdb`` and calls
``pdb.set_trace()``, but users may override ``sys.breakpointhook()`` to call
whatever debugger they want. The original value of the hook is saved in
``sys.__breakpointhook__``.
25 changes: 25 additions & 0 deletions Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,30 @@ builtin_callable(PyObject *module, PyObject *obj)
return PyBool_FromLong((long)PyCallable_Check(obj));
}

static PyObject *
builtin_breakpoint(PyObject *self, PyObject *args, PyObject *keywords)
{
PyObject *hook = PySys_GetObject("breakpointhook");
PyObject *retval = NULL;

if (hook == NULL) {
PyErr_SetString(PyExc_RuntimeError, "lost sys.breakpointhook");
return NULL;
}
Py_INCREF(hook);
retval = PyObject_Call(hook, args, keywords);
Py_DECREF(hook);
return retval;
}

PyDoc_STRVAR(breakpoint_doc,
"breakpoint(*args, **kws)\n\
\n\
Call the sys.breakpointhook() function, passing *args and **kws\n\
through unchanged. sys.breakpointhook() must accept the arguments\n\

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not simply "Call sys.breakpointhook(*args, **kwds)."? It's just a suggestion. I don't know what is the best.

passed.\n\
\n\
By default, this drops you into the pdb debugger.");

typedef struct {
PyObject_HEAD
Expand Down Expand Up @@ -2620,6 +2644,7 @@ static PyMethodDef builtin_methods[] = {
BUILTIN_ANY_METHODDEF
BUILTIN_ASCII_METHODDEF
BUILTIN_BIN_METHODDEF
{"breakpoint", (PyCFunction)builtin_breakpoint, METH_VARARGS | METH_KEYWORDS, breakpoint_doc},
BUILTIN_CALLABLE_METHODDEF
BUILTIN_CHR_METHODDEF
BUILTIN_COMPILE_METHODDEF
Expand Down
30 changes: 30 additions & 0 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,32 @@ PySys_SetObject(const char *name, PyObject *v)
return PyDict_SetItemString(sd, name, v);
}

static PyObject *
sys_breakpointhook(PyObject *self, PyObject *noargs)
{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add "assert(!PyErr_Occurred());" at the entry point since this function is called when something goes wrong, and the implementation don't expect that an exception is currently set.

... Maybe even raise an exception at runtime if it's called with an exception set? See _Py_CheckFunctionResult() which implements a similar check at exit point.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that true, i.e. that it's possible to call this with an exception set? I don't mind adding an assertion for that, but I'm not sure I see how it's possible. builtin_breakpoint() won't call it with an exception set AFAICT. What scenario are you thinking about?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that true, i.e. that it's possible to call this with an exception set?

In my experience, it's very common that a function returning a result and set an exception is not catched immediately, but somewhere "later". I had such issues when I ran my pyfailmalloc tool which injects MemoryError anywhere.

But I made many changes in CPython internals to add assert(!PyErr_Occurred()) at many places. The main change is that all functions calling functions should now check that no exception is raised if the called function returned a result. This is _Py_CheckFunctionResult().

To come back to your breakpoint() implementation, "assert(!PyErr_Occurred());" should be enough.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be clear, you're suggesting assert(!PyErr_Occurred()); at the beginning of sys_breakpointhook() right? I'm cool with that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

PyObject *pdb = PyImport_ImportModule("pdb");
PyObject *set_trace = NULL;
PyObject *retval = NULL;

if (pdb == NULL) {
return NULL;
}
set_trace = PyObject_GetAttrString(pdb, "set_trace");
Py_DECREF(pdb);
if (set_trace == NULL) {
return NULL;
}
retval = PyObject_CallObject(set_trace, NULL);
Py_DECREF(set_trace);
return retval;
}

PyDoc_STRVAR(breakpointhook_doc,
"breakpointhook()\n"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unclear to me if the prototype is breakpointhook() or breakpointhook(*args, **kw)?

"\n"
"Called when the built-in breakpoint() function is called.\n"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to repeat the first sentence of its doc:

This hook function is called by built-in breakpoint().

);

/* Write repr(o) to sys.stdout using sys.stdout.encoding and 'backslashreplace'
error handler. If sys.stdout has a buffer attribute, use
sys.stdout.buffer.write(encoded), otherwise redecode the string and use
Expand Down Expand Up @@ -1360,6 +1386,7 @@ sys_getandroidapilevel(PyObject *self)

static PyMethodDef sys_methods[] = {
/* Might as well keep this in alphabetic order */
{"breakpointhook", sys_breakpointhook, METH_NOARGS, breakpointhook_doc},
{"callstats", (PyCFunction)sys_callstats, METH_NOARGS,
callstats_doc},
{"_clear_type_cache", sys_clear_type_cache, METH_NOARGS,
Expand Down Expand Up @@ -1951,6 +1978,9 @@ _PySys_BeginInit(void)
PyDict_GetItemString(sysdict, "displayhook"));
SET_SYS_FROM_STRING_BORROW("__excepthook__",
PyDict_GetItemString(sysdict, "excepthook"));
SET_SYS_FROM_STRING_BORROW(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your indentation here is different from what's already present here. While I personally prefer how you've done it, local inconsistency is ugly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, but unfortunately that's the only way to not break 80 columns (or maybe "the least horrible way").

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the indentation of the other entries should not be discarded as an option if that allows PEP 7 conformance.

Not ideal, perhaps, but allows local consistency.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I want to touch lines outside those related to this PEP.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the interest of local consistency, that could be done after PEP code is landed (assuming acceptance, which sounds likely), as a separate commit. Otherwise, some way to conform would be valuable.

"__breakpointhook__",
PyDict_GetItemString(sysdict, "breakpointhook"));
SET_SYS_FROM_STRING("version",
PyUnicode_FromString(Py_GetVersion()));
SET_SYS_FROM_STRING("hexversion",
Expand Down