From 3378e99acfb3599077c6e8b7c4a824b36aca29fa Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 25 Aug 2018 13:36:59 +0300 Subject: [PATCH 1/2] bpo-23325: Turn signal.SIG_DFL and signal.SIG_IGN into functions. These singletons are no longer integers. They are now copyable and pickleable and have docstrings. --- Lib/signal.py | 26 ------------------ Lib/test/test_signal.py | 26 +++++++++++++++--- .../2018-08-25-13-35-37.bpo-23325.xoZDv0.rst | 2 ++ Modules/signalmodule.c | 27 ++++++++++++++----- 4 files changed, 45 insertions(+), 36 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2018-08-25-13-35-37.bpo-23325.xoZDv0.rst diff --git a/Lib/signal.py b/Lib/signal.py index 826b62cf596ccf..c5a61d58dd091e 100644 --- a/Lib/signal.py +++ b/Lib/signal.py @@ -12,10 +12,6 @@ and (name.startswith('SIG') and not name.startswith('SIG_')) or name.startswith('CTRL_')) -_IntEnum._convert( - 'Handlers', __name__, - lambda name: name in ('SIG_DFL', 'SIG_IGN')) - if 'pthread_sigmask' in _globals: _IntEnum._convert( 'Sigmasks', __name__, @@ -32,28 +28,6 @@ def _int_to_enum(value, enum_klass): return value -def _enum_to_int(value): - """Convert an IntEnum member to a numeric value. - If it's not an IntEnum member return the value itself. - """ - try: - return int(value) - except (ValueError, TypeError): - return value - - -@_wraps(_signal.signal) -def signal(signalnum, handler): - handler = _signal.signal(_enum_to_int(signalnum), _enum_to_int(handler)) - return _int_to_enum(handler, Handlers) - - -@_wraps(_signal.getsignal) -def getsignal(signalnum): - handler = _signal.getsignal(signalnum) - return _int_to_enum(handler, Handlers) - - if 'pthread_sigmask' in _globals: @_wraps(_signal.pthread_sigmask) def pthread_sigmask(how, mask): diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index d30a2d61290534..127d205269d66d 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -1,4 +1,6 @@ +import copy import os +import pickle import random import signal import socket @@ -21,9 +23,7 @@ class GenericTests(unittest.TestCase): def test_enums(self): for name in dir(signal): sig = getattr(signal, name) - if name in {'SIG_DFL', 'SIG_IGN'}: - self.assertIsInstance(sig, signal.Handlers) - elif name in {'SIG_BLOCK', 'SIG_UNBLOCK', 'SIG_SETMASK'}: + if name in {'SIG_BLOCK', 'SIG_UNBLOCK', 'SIG_SETMASK'}: self.assertIsInstance(sig, signal.Sigmasks) elif name.startswith('SIG') and not name.startswith('SIG_'): self.assertIsInstance(sig, signal.Signals) @@ -31,6 +31,25 @@ def test_enums(self): self.assertIsInstance(sig, signal.Signals) self.assertEqual(sys.platform, "win32") + def test_standard_handlers(self): + for name in 'SIG_DFL', 'SIG_IGN': + with self.subTest(name): + handler = getattr(signal, name) + self.assertIn(name, repr(handler)) + self.assertNotEqual(handler.__doc__, type(handler).__doc__) + + self.assertIs(copy.copy(handler), handler) + self.assertIs(copy.deepcopy(handler), handler) + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + p = pickle.dumps(handler, proto) + self.assertIs(pickle.loads(p), handler) + + old_handler = signal.signal(signal.SIGINT, handler) + try: + self.assertIs(signal.getsignal(signal.SIGINT), handler) + finally: + signal.signal(signal.SIGINT, old_handler) + @unittest.skipIf(sys.platform == "win32", "Not valid on Windows") class PosixTests(unittest.TestCase): @@ -51,7 +70,6 @@ def test_setting_signal_handler_to_none_raises_error(self): def test_getsignal(self): hup = signal.signal(signal.SIGHUP, self.trivial_signal_handler) - self.assertIsInstance(hup, signal.Handlers) self.assertEqual(signal.getsignal(signal.SIGHUP), self.trivial_signal_handler) signal.signal(signal.SIGHUP, hup) diff --git a/Misc/NEWS.d/next/Library/2018-08-25-13-35-37.bpo-23325.xoZDv0.rst b/Misc/NEWS.d/next/Library/2018-08-25-13-35-37.bpo-23325.xoZDv0.rst new file mode 100644 index 00000000000000..0eae5d7dfe4ff6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-08-25-13-35-37.bpo-23325.xoZDv0.rst @@ -0,0 +1,2 @@ +Singletons :data:`~signal.SIG_DFL` and :data:`~signal.SIG_IGN` are no longer +integers. They are now copyable and pickleable and have docstrings. diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c index d1209485827331..31d165dc5c9bd1 100644 --- a/Modules/signalmodule.c +++ b/Modules/signalmodule.c @@ -1197,6 +1197,19 @@ signal_pthread_kill_impl(PyObject *module, unsigned long thread_id, #endif /* #if defined(HAVE_PTHREAD_KILL) */ +static PyObject * +signal_sig_const(PyObject *self, PyObject *args) +{ + PyErr_SetString(PyExc_TypeError, + "standard handler can't be called directly"); + return NULL; +} + +PyDoc_STRVAR(signal_SIG_DFL_doc, +"Standard signal handler used to refer to the system default handler."); + +PyDoc_STRVAR(signal_SIG_IGN_doc, +"Standard signal handler used to ignore the given signal."); /* List of functions defined in the module -- some of the methoddefs are defined to nothing if the corresponding C function is not available. */ @@ -1220,6 +1233,8 @@ static PyMethodDef signal_methods[] = { #if defined(HAVE_SIGFILLSET) || defined(MS_WINDOWS) SIGNAL_VALID_SIGNALS_METHODDEF #endif + {"SIG_DFL", signal_sig_const, METH_VARARGS, signal_SIG_DFL_doc}, + {"SIG_IGN", signal_sig_const, METH_VARARGS, signal_SIG_IGN_doc}, {NULL, NULL} /* sentinel */ }; @@ -1239,8 +1254,6 @@ pause() -- wait until a signal arrives [Unix only]\n\ default_int_handler() -- default SIGINT handler\n\ \n\ signal constants:\n\ -SIG_DFL -- used to refer to the system default handler\n\ -SIG_IGN -- used to ignore the signal\n\ NSIG -- number of defined signals\n\ SIGINT, SIGTERM, etc. -- signal numbers\n\ \n\ @@ -1299,13 +1312,15 @@ PyInit__signal(void) /* Add some symbolic constants to the module */ d = PyModule_GetDict(m); - x = DefaultHandler = PyLong_FromVoidPtr((void *)SIG_DFL); - if (!x || PyDict_SetItemString(d, "SIG_DFL", x) < 0) + x = DefaultHandler = PyDict_GetItemString(d, "SIG_DFL"); + if (!x) goto finally; + Py_INCREF(DefaultHandler); - x = IgnoreHandler = PyLong_FromVoidPtr((void *)SIG_IGN); - if (!x || PyDict_SetItemString(d, "SIG_IGN", x) < 0) + x = IgnoreHandler = PyDict_GetItemString(d, "SIG_IGN"); + if (!x) goto finally; + Py_INCREF(IgnoreHandler); x = PyLong_FromLong((long)NSIG); if (!x || PyDict_SetItemString(d, "NSIG", x) < 0) From 390c79a9afecd16d2c46fc05e80755002076f3f4 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 28 Jun 2020 14:22:57 +0300 Subject: [PATCH 2/2] Fix unittest. --- Lib/unittest/signals.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/Lib/unittest/signals.py b/Lib/unittest/signals.py index e6a5fc52439712..29180dfd936133 100644 --- a/Lib/unittest/signals.py +++ b/Lib/unittest/signals.py @@ -10,19 +10,9 @@ class _InterruptHandler(object): def __init__(self, default_handler): self.called = False self.original_handler = default_handler - if isinstance(default_handler, int): - if default_handler == signal.SIG_DFL: - # Pretend it's signal.default_int_handler instead. - default_handler = signal.default_int_handler - elif default_handler == signal.SIG_IGN: - # Not quite the same thing as SIG_IGN, but the closest we - # can make it: do nothing. - def default_handler(unused_signum, unused_frame): - pass - else: - raise TypeError("expected SIGINT signal handler to be " - "signal.SIG_IGN, signal.SIG_DFL, or a " - "callable object") + if default_handler == signal.SIG_DFL: + # Pretend it's signal.default_int_handler instead. + default_handler = signal.default_int_handler self.default_handler = default_handler def __call__(self, signum, frame):