diff --git a/py/method.go b/py/method.go index c8b0ab03..438ad5f4 100644 --- a/py/method.go +++ b/py/method.go @@ -84,6 +84,7 @@ const ( InternalMethodImport InternalMethodEval InternalMethodExec + InternalMethodVars ) var MethodType = NewType("method", "method object") diff --git a/stdlib/builtin/builtin.go b/stdlib/builtin/builtin.go index 290cb939..96715321 100644 --- a/stdlib/builtin/builtin.go +++ b/stdlib/builtin/builtin.go @@ -63,7 +63,7 @@ func init() { py.MustNewMethod("setattr", builtin_setattr, 0, setattr_doc), py.MustNewMethod("sorted", builtin_sorted, 0, sorted_doc), py.MustNewMethod("sum", builtin_sum, 0, sum_doc), - // py.MustNewMethod("vars", builtin_vars, 0, vars_doc), + py.MustNewMethod("vars", py.InternalMethodVars, 0, vars_doc), } globals := py.StringDict{ "None": py.None, @@ -1189,6 +1189,11 @@ const globals_doc = `globals() -> dictionary Return the dictionary containing the current scope's global variables.` +const vars_doc = `vars([object]) -> dictionary + +Without an argument, equivalent to locals(). +With an argument, equivalent to object.__dict__.` + const sum_doc = `sum($module, iterable, start=0, /) -- Return the sum of a \'start\' value (default: 0) plus an iterable of numbers diff --git a/stdlib/builtin/tests/builtin.py b/stdlib/builtin/tests/builtin.py index ae4e8a5f..fc6bcdf8 100644 --- a/stdlib/builtin/tests/builtin.py +++ b/stdlib/builtin/tests/builtin.py @@ -107,6 +107,39 @@ def fn(x): assert locals()["x"] == 1 fn(1) +doc="vars" +def fn(x): + assert vars()["x"] == 1 +fn(1) + +# Test vars() with an object that has __dict__ (function objects have __dict__) +def test_func(): + pass + +assert vars(test_func) == test_func.__dict__ +assert isinstance(vars(test_func), dict) + +ok = False +try: + vars(test_func, test_func) +except TypeError: + ok = True +assert ok, "TypeError not raised for too many arguments" + +ok = False +try: + vars(x=1) +except TypeError: + ok = True +assert ok, "TypeError not raised for keyword arguments" + +ok = False +try: + vars(test_func, y=1) +except TypeError: + ok = True +assert ok, "TypeError not raised for keyword arguments with object" + def func(p): return p[1] diff --git a/vm/eval.go b/vm/eval.go index d32cf734..9db0fae9 100644 --- a/vm/eval.go +++ b/vm/eval.go @@ -1599,6 +1599,23 @@ func callInternal(fn py.Object, args py.Tuple, kwargs py.StringDict, f *py.Frame case py.InternalMethodExec: f.FastToLocals() return builtinExec(f.Context, args, kwargs, f.Locals, f.Globals, f.Builtins) + case py.InternalMethodVars: + if len(kwargs) > 0 { + return nil, py.ExceptionNewf(py.TypeError, "vars() takes no keyword arguments") + } + switch len(args) { + case 0: + f.FastToLocals() + return f.Locals, nil + case 1: + attr, err := py.GetAttrString(args[0], "__dict__") + if err != nil { + return nil, err + } + return attr, nil + default: + return nil, py.ExceptionNewf(py.TypeError, "vars() takes at most 1 argument (%d given)", len(args)) + } default: return nil, py.ExceptionNewf(py.SystemError, "Internal method %v not found", x) }