mirror of
https://github.com/pocketpy/pocketpy
synced 2026-08-05 05:37:10 +08:00
Add inspect.signature (#526)
* Add inspect.signature tests * Implement inspect.signature
This commit is contained in:
parent
5d20084db7
commit
21248a0baf
@ -11,6 +11,7 @@ extern const char kPythonLibs_dataclasses[];
|
|||||||
extern const char kPythonLibs_datetime[];
|
extern const char kPythonLibs_datetime[];
|
||||||
extern const char kPythonLibs_functools[];
|
extern const char kPythonLibs_functools[];
|
||||||
extern const char kPythonLibs_heapq[];
|
extern const char kPythonLibs_heapq[];
|
||||||
|
extern const char kPythonLibs_inspect[];
|
||||||
extern const char kPythonLibs_long_v1[];
|
extern const char kPythonLibs_long_v1[];
|
||||||
extern const char kPythonLibs_operator[];
|
extern const char kPythonLibs_operator[];
|
||||||
extern const char kPythonLibs_typing[];
|
extern const char kPythonLibs_typing[];
|
||||||
|
|||||||
58
python/inspect.py
Normal file
58
python/inspect.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
class _empty:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Parameter:
|
||||||
|
POSITIONAL_ONLY = 0
|
||||||
|
POSITIONAL_OR_KEYWORD = 1
|
||||||
|
VAR_POSITIONAL = 2
|
||||||
|
KEYWORD_ONLY = 3
|
||||||
|
VAR_KEYWORD = 4
|
||||||
|
|
||||||
|
empty = _empty
|
||||||
|
|
||||||
|
def __init__(self, name, kind, *default):
|
||||||
|
self.name = name
|
||||||
|
self.kind = kind
|
||||||
|
# pocketpy only allows literal defaults, so use *default as sentinel
|
||||||
|
self.default = default[0] if default else _empty
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
res = self.name
|
||||||
|
if self.default is not _empty:
|
||||||
|
res += '=' + repr(self.default)
|
||||||
|
if self.kind == Parameter.VAR_POSITIONAL:
|
||||||
|
res = '*' + res
|
||||||
|
elif self.kind == Parameter.VAR_KEYWORD:
|
||||||
|
res = '**' + res
|
||||||
|
return res
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<Parameter "' + str(self) + '">'
|
||||||
|
|
||||||
|
|
||||||
|
class Signature:
|
||||||
|
empty = _empty
|
||||||
|
|
||||||
|
def __init__(self, parameters):
|
||||||
|
self.parameters = {p.name: p for p in parameters}
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '(' + ', '.join([str(p) for p in self.parameters.values()]) + ')'
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<Signature ' + str(self) + '>'
|
||||||
|
|
||||||
|
|
||||||
|
def _make_params(func):
|
||||||
|
return [Parameter(*entry) for entry in _signature_data(func)]
|
||||||
|
|
||||||
|
|
||||||
|
def signature(obj):
|
||||||
|
if not callable(obj):
|
||||||
|
raise TypeError(repr(obj) + ' is not a callable object')
|
||||||
|
if isinstance(obj, type):
|
||||||
|
return Signature(_make_params(obj.__init__)[1:])
|
||||||
|
if hasattr(obj, '__func__'):
|
||||||
|
return Signature(_make_params(obj.__func__)[1:])
|
||||||
|
return Signature(_make_params(obj))
|
||||||
File diff suppressed because one or more lines are too long
@ -1,6 +1,7 @@
|
|||||||
#include "pocketpy/pocketpy.h"
|
#include "pocketpy/pocketpy.h"
|
||||||
#include "pocketpy/objects/object.h"
|
#include "pocketpy/objects/object.h"
|
||||||
#include "pocketpy/interpreter/vm.h"
|
#include "pocketpy/interpreter/vm.h"
|
||||||
|
#include "pocketpy/common/_generated.h"
|
||||||
|
|
||||||
static bool inspect_isgeneratorfunction(int argc, py_Ref argv) {
|
static bool inspect_isgeneratorfunction(int argc, py_Ref argv) {
|
||||||
PY_CHECK_ARGC(1);
|
PY_CHECK_ARGC(1);
|
||||||
@ -26,9 +27,62 @@ static bool inspect_is_user_defined_type(int argc, py_Ref argv) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a tuple of (name, kind[, default]) entries.
|
||||||
|
static bool inspect__signature_data(int argc, py_Ref argv) {
|
||||||
|
PY_CHECK_ARGC(1);
|
||||||
|
if(!py_istype(argv, tp_function)) {
|
||||||
|
return ValueError("no signature found for '%t' object", argv->type);
|
||||||
|
}
|
||||||
|
Function* fn = py_touserdata(argv);
|
||||||
|
FuncDecl* decl = fn->decl;
|
||||||
|
const CodeObject* co = &decl->code;
|
||||||
|
|
||||||
|
bool has_starred_arg = decl->starred_arg != -1;
|
||||||
|
bool has_starred_kwarg = decl->starred_kwarg != -1;
|
||||||
|
int total = decl->args.length + decl->kwargs.length + (int)has_starred_arg +
|
||||||
|
(int)has_starred_kwarg;
|
||||||
|
|
||||||
|
py_TValue* items = py_newtuple(py_retval(), total);
|
||||||
|
int j = 0;
|
||||||
|
for(int i = 0; i < decl->args.length; i++) {
|
||||||
|
int32_t index = c11__getitem(int32_t, &decl->args, i);
|
||||||
|
py_Name name = c11__getitem(py_Name, &co->varnames, index);
|
||||||
|
py_TValue* entry = py_newtuple(&items[j++], 2);
|
||||||
|
py_newstr(&entry[0], py_name2str(name));
|
||||||
|
py_newint(&entry[1], 1);
|
||||||
|
}
|
||||||
|
if(has_starred_arg) {
|
||||||
|
py_Name name = c11__getitem(py_Name, &co->varnames, decl->starred_arg);
|
||||||
|
py_TValue* entry = py_newtuple(&items[j++], 2);
|
||||||
|
py_newstr(&entry[0], py_name2str(name));
|
||||||
|
py_newint(&entry[1], 2);
|
||||||
|
}
|
||||||
|
for(int i = 0; i < decl->kwargs.length; i++) {
|
||||||
|
FuncDeclKwArg kv = c11__getitem(FuncDeclKwArg, &decl->kwargs, i);
|
||||||
|
py_TValue* entry = py_newtuple(&items[j++], 3);
|
||||||
|
py_newstr(&entry[0], py_name2str(kv.key));
|
||||||
|
// defaults after *args can only be passed by keyword
|
||||||
|
py_newint(&entry[1], has_starred_arg ? 3 : 1);
|
||||||
|
entry[2] = kv.value;
|
||||||
|
}
|
||||||
|
if(has_starred_kwarg) {
|
||||||
|
py_Name name = c11__getitem(py_Name, &co->varnames, decl->starred_kwarg);
|
||||||
|
py_TValue* entry = py_newtuple(&items[j++], 2);
|
||||||
|
py_newstr(&entry[0], py_name2str(name));
|
||||||
|
py_newint(&entry[1], 4);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void pk__add_module_inspect() {
|
void pk__add_module_inspect() {
|
||||||
py_Ref mod = py_newmodule("inspect");
|
py_Ref mod = py_newmodule("inspect");
|
||||||
|
|
||||||
py_bindfunc(mod, "isgeneratorfunction", inspect_isgeneratorfunction);
|
py_bindfunc(mod, "isgeneratorfunction", inspect_isgeneratorfunction);
|
||||||
py_bindfunc(mod, "is_user_defined_type", inspect_is_user_defined_type);
|
py_bindfunc(mod, "is_user_defined_type", inspect_is_user_defined_type);
|
||||||
|
py_bindfunc(mod, "_signature_data", inspect__signature_data);
|
||||||
|
|
||||||
|
if(!py_exec(kPythonLibs_inspect, "inspect.py", EXEC_MODE, mod)) {
|
||||||
|
py_printexc();
|
||||||
|
c11__abort("failed to execute inspect.py");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,3 +47,136 @@ assert not isgeneratorfunction(a.not_gen_instance)
|
|||||||
assert isgeneratorfunction(a.gen_instance)
|
assert isgeneratorfunction(a.gen_instance)
|
||||||
assert not isgeneratorfunction(A.not_gen_instance)
|
assert not isgeneratorfunction(A.not_gen_instance)
|
||||||
assert isgeneratorfunction(A.gen_instance)
|
assert isgeneratorfunction(A.gen_instance)
|
||||||
|
|
||||||
|
# ---------------- inspect.signature ----------------
|
||||||
|
from inspect import signature, Parameter
|
||||||
|
|
||||||
|
# simple positional-or-keyword parameters
|
||||||
|
def f1(a, b):
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
sig = signature(f1)
|
||||||
|
assert str(sig) == '(a, b)'
|
||||||
|
params = sig.parameters
|
||||||
|
assert list(params) == ['a', 'b']
|
||||||
|
assert params['a'].name == 'a'
|
||||||
|
assert params['a'].kind == Parameter.POSITIONAL_OR_KEYWORD
|
||||||
|
assert params['a'].default is Parameter.empty
|
||||||
|
assert params['b'].kind == Parameter.POSITIONAL_OR_KEYWORD
|
||||||
|
|
||||||
|
# no parameters
|
||||||
|
def f2():
|
||||||
|
pass
|
||||||
|
|
||||||
|
sig = signature(f2)
|
||||||
|
assert str(sig) == '()'
|
||||||
|
assert len(sig.parameters) == 0
|
||||||
|
|
||||||
|
# default values
|
||||||
|
def f3(a, b=2, c='x'):
|
||||||
|
pass
|
||||||
|
|
||||||
|
sig = signature(f3)
|
||||||
|
assert str(sig) == "(a, b=2, c='x')"
|
||||||
|
params = sig.parameters
|
||||||
|
assert params['a'].default is Parameter.empty
|
||||||
|
assert params['b'].default == 2
|
||||||
|
assert params['c'].default == 'x'
|
||||||
|
|
||||||
|
# *args and **kwargs
|
||||||
|
def f4(a, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
sig = signature(f4)
|
||||||
|
assert str(sig) == '(a, *args, **kwargs)'
|
||||||
|
params = sig.parameters
|
||||||
|
assert list(params) == ['a', 'args', 'kwargs']
|
||||||
|
assert params['a'].kind == Parameter.POSITIONAL_OR_KEYWORD
|
||||||
|
assert params['args'].kind == Parameter.VAR_POSITIONAL
|
||||||
|
assert params['args'].default is Parameter.empty
|
||||||
|
assert params['kwargs'].kind == Parameter.VAR_KEYWORD
|
||||||
|
assert params['kwargs'].default is Parameter.empty
|
||||||
|
|
||||||
|
# keyword-only parameters (defaults after *args)
|
||||||
|
def f5(a, *c, b=1, **d):
|
||||||
|
pass
|
||||||
|
|
||||||
|
sig = signature(f5)
|
||||||
|
assert str(sig) == '(a, *c, b=1, **d)'
|
||||||
|
assert list(sig.parameters) == ['a', 'c', 'b', 'd']
|
||||||
|
assert sig.parameters['b'].kind == Parameter.KEYWORD_ONLY
|
||||||
|
assert sig.parameters['b'].default == 1
|
||||||
|
|
||||||
|
# lambda
|
||||||
|
sig = signature(lambda x, y=3: x + y)
|
||||||
|
assert str(sig) == '(x, y=3)'
|
||||||
|
assert list(sig.parameters) == ['x', 'y']
|
||||||
|
assert sig.parameters['y'].default == 3
|
||||||
|
|
||||||
|
# generator function
|
||||||
|
sig = signature(g)
|
||||||
|
assert str(sig) == '(a, b)'
|
||||||
|
|
||||||
|
# methods: bound methods drop `self`, unbound functions keep it
|
||||||
|
class B:
|
||||||
|
def m(self, x, y=1):
|
||||||
|
pass
|
||||||
|
@staticmethod
|
||||||
|
def sm(x, y):
|
||||||
|
pass
|
||||||
|
@classmethod
|
||||||
|
def cm(cls, x):
|
||||||
|
pass
|
||||||
|
|
||||||
|
b = B()
|
||||||
|
assert str(signature(B.m)) == '(self, x, y=1)'
|
||||||
|
assert str(signature(b.m)) == '(x, y=1)'
|
||||||
|
assert list(signature(b.m).parameters) == ['x', 'y']
|
||||||
|
assert str(signature(B.sm)) == '(x, y)'
|
||||||
|
assert str(signature(b.sm)) == '(x, y)'
|
||||||
|
assert str(signature(B.cm)) == '(x)'
|
||||||
|
assert str(signature(b.cm)) == '(x)'
|
||||||
|
|
||||||
|
# calling signature() on a class inspects __init__ (without `self`)
|
||||||
|
class C:
|
||||||
|
def __init__(self, a, b=5):
|
||||||
|
pass
|
||||||
|
|
||||||
|
sig = signature(C)
|
||||||
|
assert str(sig) == '(a, b=5)'
|
||||||
|
assert list(sig.parameters) == ['a', 'b']
|
||||||
|
|
||||||
|
# decorated function (plain wrapper)
|
||||||
|
def deco(fn):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
return fn(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
@deco
|
||||||
|
def f6(a, b):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert str(signature(f6)) == '(*args, **kwargs)'
|
||||||
|
|
||||||
|
# Parameter equality / repr basics
|
||||||
|
p = signature(f3).parameters['b']
|
||||||
|
assert p.name == 'b'
|
||||||
|
assert p.default == 2
|
||||||
|
assert p.kind == Parameter.POSITIONAL_OR_KEYWORD
|
||||||
|
|
||||||
|
# kinds are distinct
|
||||||
|
kinds = [
|
||||||
|
Parameter.POSITIONAL_OR_KEYWORD,
|
||||||
|
Parameter.VAR_POSITIONAL,
|
||||||
|
Parameter.KEYWORD_ONLY,
|
||||||
|
Parameter.VAR_KEYWORD,
|
||||||
|
]
|
||||||
|
assert len(set(kinds)) == 4
|
||||||
|
|
||||||
|
# non-callable raises TypeError
|
||||||
|
try:
|
||||||
|
signature(42)
|
||||||
|
print('failed to raise TypeError')
|
||||||
|
exit(1)
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user