From b4565b41eab684722d9fcb2deee322869e47c359 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 19 Sep 2025 13:38:26 +1000 Subject: [PATCH] inspect: Implement a very basic signature function. This implements a very basic `inspect.signature()` function. At the moment it returns only a simple `Signature` instance with a `parameters` attribute that holds an `OrderedDict` whose length matches the arity of the input function (the number of arguments it takes). So, the following code works and is compatible with CPython: def f(a, b, *, c): pass print(len(inspect.signature(f).parameters)) That should print 3. Signed-off-by: Damien George --- python-stdlib/inspect/inspect.py | 57 +++++++++++++++++++++++++++ python-stdlib/inspect/manifest.py | 2 +- python-stdlib/inspect/test_inspect.py | 13 ++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/python-stdlib/inspect/inspect.py b/python-stdlib/inspect/inspect.py index c16c6b3e3..9074549bb 100644 --- a/python-stdlib/inspect/inspect.py +++ b/python-stdlib/inspect/inspect.py @@ -80,3 +80,60 @@ def currentframe(): def getframeinfo(frame, context=1): return ("", -1, "", [""], 0) + + +class Signature: + pass + + +# This `signature()` function is very limited. It's main purpose is to work out +# the arity of the given function, ie the number of arguments it takes. +# +# The return value is an instance of `Signature` with a `parameters` member which +# is an OrderedDict whose length is the number of arguments of `f`. +def signature(f): + import collections + import uctypes + + s = Signature() + s.parameters = collections.OrderedDict() + + t = type(f) + if t is type(globals): + # A zero-parameter built-in. + num_args = 0 + elif t is type(abs): + # A one-parameter built-in. + num_args = 1 + elif t is type(hasattr): + # A two-parameter built-in. + num_args = 2 + elif t is type(setattr): + # A three-parameter built-in. + num_args = 3 + elif t is type(signature): + # A bytecode function, work out the number of arguments by inspecting the bytecode data. + fun_obj = uctypes.struct(id(f), (uctypes.ARRAY | 0, uctypes.LONG | 4)) + bytecode = uctypes.bytearray_at(fun_obj[3], 8) + # See py/bc.h:MP_BC_PRELUDE_SIG_DECODE_INTO macro. + i = 0 + z = bytecode[i] + i += 1 + A = z & 0x3 + K = 0 + n = 0 + while z & 0x80: + z = bytecode[i] + i += 1 + A |= (z & 0x4) << n + K |= ((z & 0x08) >> 3) << n + num_args = A + K + else: + raise NotImplementedError("unsupported function type") + + # Add dummy arguments to the OrderedDict. + for i in range(num_args): + a = "x{}".format(i) + s.parameters[a] = a + + return s diff --git a/python-stdlib/inspect/manifest.py b/python-stdlib/inspect/manifest.py index e99e659f2..119237c45 100644 --- a/python-stdlib/inspect/manifest.py +++ b/python-stdlib/inspect/manifest.py @@ -1,3 +1,3 @@ -metadata(version="0.1.3") +metadata(version="0.2.0") module("inspect.py") diff --git a/python-stdlib/inspect/test_inspect.py b/python-stdlib/inspect/test_inspect.py index 29ed80f11..f5110de70 100644 --- a/python-stdlib/inspect/test_inspect.py +++ b/python-stdlib/inspect/test_inspect.py @@ -1,3 +1,4 @@ +import collections import inspect import unittest @@ -58,3 +59,15 @@ def test_isclass(self): def test_ismodule(self): self._test_is_helper(inspect.ismodule, entities[6]) + + def test_signature(self): + self.assertEqual(inspect.signature(globals).parameters, collections.OrderedDict()) + self.assertEqual(len(inspect.signature(abs).parameters), 1) + self.assertEqual(len(inspect.signature(hasattr).parameters), 2) + self.assertEqual(len(inspect.signature(setattr).parameters), 3) + self.assertEqual(len(inspect.signature(lambda: 0).parameters), 0) + self.assertEqual(len(inspect.signature(lambda x: 0).parameters), 1) + self.assertEqual(len(inspect.signature(lambda *, x: 0).parameters), 1) + self.assertEqual(len(inspect.signature(lambda x, y: 0).parameters), 2) + self.assertEqual(len(inspect.signature(lambda x, y, z: 0).parameters), 3) + self.assertEqual(len(inspect.signature(lambda x, y, *, z: 0).parameters), 3)