[Python-checkins] bpo-36542: Allow to overwrite the signature for Python functions. (GH-12705)

Serhiy Storchaka webhook-mailer at python.org
Mon May 6 15:40:31 EDT 2019


https://github.com/python/cpython/commit/d53cf99dca4605ace4b81b1e585616b3e1b74fa6
commit: d53cf99dca4605ace4b81b1e585616b3e1b74fa6
branch: master
author: Serhiy Storchaka <storchaka at gmail.com>
committer: GitHub <noreply at github.com>
date: 2019-05-06T22:40:27+03:00
summary:

bpo-36542: Allow to overwrite the signature for Python functions. (GH-12705)

files:
A Misc/NEWS.d/next/Library/2019-04-06-12-36-09.bpo-36542.Q0qyYV.rst
M Lib/bdb.py
M Lib/cProfile.py
M Lib/collections/__init__.py
M Lib/concurrent/futures/_base.py
M Lib/concurrent/futures/process.py
M Lib/concurrent/futures/thread.py
M Lib/contextlib.py
M Lib/curses/__init__.py
M Lib/functools.py
M Lib/inspect.py
M Lib/multiprocessing/managers.py
M Lib/profile.py
M Lib/test/test_inspect.py
M Lib/trace.py
M Lib/unittest/case.py
M Lib/weakref.py

diff --git a/Lib/bdb.py b/Lib/bdb.py
index 54aa98437450..69174364c46a 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -649,6 +649,7 @@ def runcall(*args, **kwds):
             self.quitting = True
             sys.settrace(None)
         return res
+    runcall.__text_signature__ = '($self, func, /, *args, **kwds)'
 
 
 def set_trace():
diff --git a/Lib/cProfile.py b/Lib/cProfile.py
index 2e449cc576ce..369d02e22e24 100755
--- a/Lib/cProfile.py
+++ b/Lib/cProfile.py
@@ -124,6 +124,7 @@ def runcall(*args, **kw):
             return func(*args, **kw)
         finally:
             self.disable()
+    runcall.__text_signature__ = '($self, func, /, *args, **kw)'
 
     def __enter__(self):
         self.enable()
diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py
index 9657c1cf83bc..e6cafb320fab 100644
--- a/Lib/collections/__init__.py
+++ b/Lib/collections/__init__.py
@@ -1018,6 +1018,8 @@ def __init__(*args, **kwargs):
             self.update(dict)
         if kwargs:
             self.update(kwargs)
+    __init__.__text_signature__ = '($self, dict=None, /, **kwargs)'
+
     def __len__(self): return len(self.data)
     def __getitem__(self, key):
         if key in self.data:
diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py
index ea16eef841c5..8f155f0ea82b 100644
--- a/Lib/concurrent/futures/_base.py
+++ b/Lib/concurrent/futures/_base.py
@@ -567,6 +567,7 @@ def submit(*args, **kwargs):
                             'got %d' % (len(args)-1))
 
         raise NotImplementedError()
+    submit.__text_signature__ = '($self, fn, /, *args, **kwargs)'
 
     def map(self, fn, *iterables, timeout=None, chunksize=1):
         """Returns an iterator equivalent to map(fn, iter).
diff --git a/Lib/concurrent/futures/process.py b/Lib/concurrent/futures/process.py
index e6ce278b5d44..21bf4a447f08 100644
--- a/Lib/concurrent/futures/process.py
+++ b/Lib/concurrent/futures/process.py
@@ -630,6 +630,7 @@ def submit(*args, **kwargs):
 
             self._start_queue_management_thread()
             return f
+    submit.__text_signature__ = _base.Executor.submit.__text_signature__
     submit.__doc__ = _base.Executor.submit.__doc__
 
     def map(self, fn, *iterables, timeout=None, chunksize=1):
diff --git a/Lib/concurrent/futures/thread.py b/Lib/concurrent/futures/thread.py
index 0a61e3a9ac1b..2af31a106dd9 100644
--- a/Lib/concurrent/futures/thread.py
+++ b/Lib/concurrent/futures/thread.py
@@ -174,6 +174,7 @@ def submit(*args, **kwargs):
             self._work_queue.put(w)
             self._adjust_thread_count()
             return f
+    submit.__text_signature__ = _base.Executor.submit.__text_signature__
     submit.__doc__ = _base.Executor.submit.__doc__
 
     def _adjust_thread_count(self):
diff --git a/Lib/contextlib.py b/Lib/contextlib.py
index ae498a2b6ef5..de989a001c6d 100644
--- a/Lib/contextlib.py
+++ b/Lib/contextlib.py
@@ -454,6 +454,7 @@ def callback(*args, **kwds):
         _exit_wrapper.__wrapped__ = callback
         self._push_exit_callback(_exit_wrapper)
         return callback  # Allow use as a decorator
+    callback.__text_signature__ = '($self, callback, /, *args, **kwds)'
 
     def _push_cm_exit(self, cm, cm_exit):
         """Helper to correctly register callbacks to __exit__ methods."""
@@ -615,6 +616,7 @@ def push_async_callback(*args, **kwds):
         _exit_wrapper.__wrapped__ = callback
         self._push_exit_callback(_exit_wrapper, False)
         return callback  # Allow use as a decorator
+    push_async_callback.__text_signature__ = '($self, callback, /, *args, **kwds)'
 
     async def aclose(self):
         """Immediately unwind the context stack."""
diff --git a/Lib/curses/__init__.py b/Lib/curses/__init__.py
index 44a198428820..24ff3ca93a89 100644
--- a/Lib/curses/__init__.py
+++ b/Lib/curses/__init__.py
@@ -110,3 +110,4 @@ def wrapper(*args, **kwds):
             echo()
             nocbreak()
             endwin()
+wrapper.__text_signature__ = '(func, /, *args, **kwds)'
diff --git a/Lib/functools.py b/Lib/functools.py
index 1f1874db9b4c..28d9f6f75fdb 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -388,6 +388,7 @@ def __init__(*args, **keywords):
             self.func = func
             self.args = args
             self.keywords = keywords
+    __init__.__text_signature__ = '($self, func, /, *args, **keywords)'
 
     def __repr__(self):
         args = ", ".join(map(repr, self.args))
diff --git a/Lib/inspect.py b/Lib/inspect.py
index c460309bb5a1..6c3027987b30 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -2121,7 +2121,7 @@ def _signature_from_builtin(cls, func, skip_bound_arg=True):
     return _signature_fromstr(cls, func, s, skip_bound_arg)
 
 
-def _signature_from_function(cls, func):
+def _signature_from_function(cls, func, skip_bound_arg=True):
     """Private helper: constructs Signature for the given python function."""
 
     is_duck_function = False
@@ -2133,6 +2133,10 @@ def _signature_from_function(cls, func):
             # of pure function:
             raise TypeError('{!r} is not a Python function'.format(func))
 
+    s = getattr(func, "__text_signature__", None)
+    if s:
+        return _signature_fromstr(cls, func, s, skip_bound_arg)
+
     Parameter = cls._parameter_cls
 
     # Parameter information.
@@ -2301,7 +2305,8 @@ def _signature_from_callable(obj, *,
     if isfunction(obj) or _signature_is_functionlike(obj):
         # If it's a pure Python function, or an object that is duck type
         # of a Python function (Cython functions, for instance), then:
-        return _signature_from_function(sigcls, obj)
+        return _signature_from_function(sigcls, obj,
+                                        skip_bound_arg=skip_bound_arg)
 
     if _signature_is_builtin(obj):
         return _signature_from_builtin(sigcls, obj,
diff --git a/Lib/multiprocessing/managers.py b/Lib/multiprocessing/managers.py
index 80c3ddb9154a..22abd47fb1f2 100644
--- a/Lib/multiprocessing/managers.py
+++ b/Lib/multiprocessing/managers.py
@@ -419,6 +419,7 @@ def create(*args, **kwds):
 
         self.incref(c, ident)
         return ident, tuple(exposed)
+    create.__text_signature__ = '($self, c, typeid, /, *args, **kwds)'
 
     def get_methods(self, c, token):
         '''
@@ -1309,6 +1310,7 @@ def create(*args, **kwargs):
             if hasattr(self.registry[typeid][-1], "_shared_memory_proxy"):
                 kwargs['shared_memory_context'] = self.shared_memory_context
             return Server.create(*args, **kwargs)
+        create.__text_signature__ = '($self, c, typeid, /, *args, **kwargs)'
 
         def shutdown(self, c):
             "Call unlink() on all tracked shared memory, terminate the Server."
diff --git a/Lib/profile.py b/Lib/profile.py
index 9a865d3f6f6e..1346297c04a5 100755
--- a/Lib/profile.py
+++ b/Lib/profile.py
@@ -447,6 +447,7 @@ def runcall(*args, **kw):
             return func(*args, **kw)
         finally:
             sys.setprofile(None)
+    runcall.__text_signature__ = '($self, func, /, *args, **kw)'
 
 
     #******************************************************************
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index 3c825b00e5e4..c54cdb23c242 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -3782,6 +3782,17 @@ def test_builtins_have_signatures(self):
             with self.subTest(builtin=name):
                 self.assertIsNone(obj.__text_signature__)
 
+    def test_python_function_override_signature(self):
+        def func(*args, **kwargs):
+            pass
+        func.__text_signature__ = '($self, a, b=1, *args, c, d=2, **kwargs)'
+        sig = inspect.signature(func)
+        self.assertIsNotNone(sig)
+        self.assertEqual(str(sig), '(self, /, a, b=1, *args, c, d=2, **kwargs)')
+        func.__text_signature__ = '($self, a, b=1, /, *args, c, d=2, **kwargs)'
+        sig = inspect.signature(func)
+        self.assertEqual(str(sig), '(self, a, b=1, /, *args, c, d=2, **kwargs)')
+
 
 class NTimesUnwrappable:
     def __init__(self, n):
diff --git a/Lib/trace.py b/Lib/trace.py
index fd40fbae8505..63008a134a8a 100755
--- a/Lib/trace.py
+++ b/Lib/trace.py
@@ -476,6 +476,7 @@ def runfunc(*args, **kw):
             if not self.donothing:
                 sys.settrace(None)
         return result
+    runfunc.__text_signature__ = '($self, func, /, *args, **kw)'
 
     def file_module_function_of(self, frame):
         code = frame.f_code
diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
index 8ff2546fc207..8e01c3dc7bbd 100644
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -102,6 +102,7 @@ def addModuleCleanup(*args, **kwargs):
     args = tuple(args)
 
     _module_cleanups.append((function, args, kwargs))
+addModuleCleanup.__text_signature__ = '(function, /, *args, **kwargs)'
 
 
 def doModuleCleanups():
@@ -498,8 +499,8 @@ def addCleanup(*args, **kwargs):
         args = tuple(args)
 
         self._cleanups.append((function, args, kwargs))
+    addCleanup.__text_signature__ = '($self, function, /, *args, **kwargs)'
 
-    @classmethod
     def addClassCleanup(*args, **kwargs):
         """Same as addCleanup, except the cleanup items are called even if
         setUpClass fails (unlike tearDownClass)."""
@@ -514,6 +515,8 @@ def addClassCleanup(*args, **kwargs):
         args = tuple(args)
 
         cls._class_cleanups.append((function, args, kwargs))
+    addClassCleanup.__text_signature__ = '($cls, function, /, *args, **kwargs)'
+    addClassCleanup = classmethod(addClassCleanup)
 
     def setUp(self):
         "Hook method for setting up the test fixture before exercising it."
diff --git a/Lib/weakref.py b/Lib/weakref.py
index 285c70792e0b..1eeb7b0a0b44 100644
--- a/Lib/weakref.py
+++ b/Lib/weakref.py
@@ -569,6 +569,7 @@ def __init__(*args, **kwargs):
         info.index = next(self._index_iter)
         self._registry[self] = info
         finalize._dirty = True
+    __init__.__text_signature__ = '($self, obj, func, /, *args, **kwargs)'
 
     def __call__(self, _=None):
         """If alive then mark as dead and return func(*args, **kwargs);
diff --git a/Misc/NEWS.d/next/Library/2019-04-06-12-36-09.bpo-36542.Q0qyYV.rst b/Misc/NEWS.d/next/Library/2019-04-06-12-36-09.bpo-36542.Q0qyYV.rst
new file mode 100644
index 000000000000..8374776e61eb
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-04-06-12-36-09.bpo-36542.Q0qyYV.rst
@@ -0,0 +1,2 @@
+The signature of Python functions can now be overridden by specifying the
+``__text_signature__`` attribute.



More information about the Python-checkins mailing list