Skip to content

bpo-36542: Allow to overwrite the signature for Python functions. #12705

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions Lib/bdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
1 change: 1 addition & 0 deletions Lib/cProfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 2 additions & 0 deletions Lib/collections/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions Lib/concurrent/futures/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
1 change: 1 addition & 0 deletions Lib/concurrent/futures/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
1 change: 1 addition & 0 deletions Lib/concurrent/futures/thread.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 2 additions & 0 deletions Lib/contextlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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."""
Expand Down
1 change: 1 addition & 0 deletions Lib/curses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,4 @@ def wrapper(*args, **kwds):
echo()
nocbreak()
endwin()
wrapper.__text_signature__ = '(func, /, *args, **kwds)'
1 change: 1 addition & 0 deletions Lib/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
9 changes: 7 additions & 2 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2119,7 +2119,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
Expand All @@ -2131,6 +2131,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.
Expand Down Expand Up @@ -2286,7 +2290,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,
Expand Down
2 changes: 2 additions & 0 deletions Lib/multiprocessing/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
'''
Expand Down Expand Up @@ -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."
Expand Down
1 change: 1 addition & 0 deletions Lib/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ def runcall(*args, **kw):
return func(*args, **kw)
finally:
sys.setprofile(None)
runcall.__text_signature__ = '($self, func, /, *args, **kw)'


#******************************************************************
Expand Down
11 changes: 11 additions & 0 deletions Lib/test/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -3753,6 +3753,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):
Expand Down
1 change: 1 addition & 0 deletions Lib/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion Lib/unittest/case.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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)."""
Expand All @@ -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."
Expand Down
1 change: 1 addition & 0 deletions Lib/weakref.py
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The signature of Python functions can now be overridden by specifying the
``__text_signature__`` attribute.