diff -r 582e8e71f635 Lib/inspect.py --- a/Lib/inspect.py Wed Apr 15 00:00:41 2015 -0400 +++ b/Lib/inspect.py Wed Apr 15 12:40:01 2015 -0400 @@ -1838,7 +1838,7 @@ return clean_signature, self_parameter, last_positional_only -def _signature_fromstr(cls, obj, s, skip_bound_arg=True): +def _signature_fromstr(cls, obj, s, skip_bound_arg=True, trusted=False): """Private helper to parse content of '__text_signature__' and return a Signature based on it. """ @@ -1864,29 +1864,51 @@ empty = Parameter.empty invalid = object() - module = None - module_dict = {} - module_name = getattr(obj, '__module__', None) - if module_name: - module = sys.modules.get(module_name, None) - if module: - module_dict = module.__dict__ - sys_module_dict = sys.modules - def parse_name(node): assert isinstance(node, ast.arg) if node.annotation != None: raise ValueError("Annotations are not currently supported") return node.arg + # 99% of the time, default values passed in from + # Argument Clinic are simple static values. But 1% + # of the time they are expressions. And sometimes + # they need to call builtins, or reference symbolic + # values from their own module or others. Examples: + # sys.maxint - 1 + # min(SOME_SOCKET_OPTION, 128) + # + # For the 1% of the time they need to compute something, + # we simulate what the module dict of a shared library + # *might* look like, by populating a dict with the contents + # of sys.modules, builtins, and the module itself, and + # using that for the globals when evaluating the expression. + empty_dict = {} + simulated_module_dict = empty_dict + + def populate_simulated_module_dict(): + nonlocal simulated_module_dict + if simulated_module_dict != empty_dict: + return + simulated_module_dict = dict(sys.modules) + bd = builtins.__dict__ + simulated_module_dict.update(bd) + simulated_module_dict['__builtins__'] = bd + module_name = getattr(obj, '__module__', None) + if module_name: + module = sys.modules.get(module_name, None) + if module: + simulated_module_dict.update(module.__dict__) + def wrap_value(s): try: - value = eval(s, module_dict) + value = eval(s, empty_dict) except NameError: try: - value = eval(s, sys_module_dict) + populate_simulated_module_dict() + value = eval(s, simulated_module_dict) except NameError: - raise RuntimeError() + raise RuntimeError("couldn't compute " + repr(s)) if isinstance(value, str): return ast.Str(value) @@ -1896,7 +1918,10 @@ return ast.Bytes(value) if value in (True, False, None): return ast.NameConstant(value) - raise RuntimeError() + if callable(value): + # function name, return unchanged + return ast.Str(s) + raise RuntimeError("couldn't determine proper ast node for " + repr(s) + ", value " + repr(value)) class RewriteSymbolics(ast.NodeTransformer): def visit_Attribute(self, node): @@ -1916,20 +1941,72 @@ raise ValueError() return wrap_value(node.id) + def evalify_node(node): + if isinstance(node, (ast.Str, ast.Bytes)): + return repr(node.s) + if isinstance(node, ast.Num): + return repr(node.n) + if isinstance(node, ast.NameConstant): + return repr(node.value) + if isinstance(node, ast.Name): + return node.id + if isinstance(node, ast.UnaryOp): + return evalify_node(node.op) + evalify_node(node.operand) + if isinstance(node, ast.BinOp): + return evalify_node(node.left) + " " + evalify_node(node.op) + " " + evalify_node(node.right) + if isinstance(node, ast.Attribute): + return evalify_node(node.value) + "." + node.attr + if isinstance(node, (ast.Sub, ast.USub)): + return "-" + if isinstance(node, ast.Add): + return "+" + if isinstance(node, ast.Mult): + return "*" + if isinstance(node, ast.Div): + return "/" + if isinstance(node, ast.FloorDiv): + return "//" + if isinstance(node, ast.keyword): + return node.arg + "=" + evalify_node(node.value) + if isinstance(node, ast.Call): + a = [node.func.s, "("] + args = [evalify_node(x) for x in node.args] + if node.starargs: + args.append("*" + evalify_node(node.starargs)) + for x in node.keywords: + args.append(evalify_node(x)) + if node.kwargs: + args.append("**" + evalify_node(node.kwargs)) + + a.append(", ".join(args)) + a.append(')') + return ''.join(a) + raise RuntimeError("unhandled node in evalify_node: " + str(node) + " " + str(type(node)) + " " + str(dir(node))) + def p(name_node, default_node, default=empty): name = parse_name(name_node) if name is invalid: return None if default_node and default_node is not _empty: try: - default_node = RewriteSymbolics().visit(default_node) - o = ast.literal_eval(default_node) + rewritten_node = RewriteSymbolics().visit(default_node) + o = ast.literal_eval(rewritten_node) except ValueError: - o = invalid + if not trusted: + o = invalid + else: + try: + populate_simulated_module_dict() + s = evalify_node(default_node) + print("ZZZZ! evalify_node", evalify_node, "eval", eval, "s", s) + o = eval(s, simulated_module_dict) + except ValueError: + o = invalid if o is invalid: return None default = o if o is not invalid else default - parameters.append(Parameter(name, kind, default=default, annotation=empty)) + parameters.append(Parameter(name, kind, + default=default, annotation=empty)) # non-keyword-only parameters args = reversed(f.args.args) @@ -1992,7 +2069,7 @@ if not s: raise ValueError("no signature found for builtin {!r}".format(func)) - return _signature_fromstr(cls, func, s, skip_bound_arg) + return _signature_fromstr(cls, func, s, skip_bound_arg, trusted=True) def _signature_from_callable(obj, *, @@ -2132,7 +2209,7 @@ if text_sig: # If 'obj' class has a __text_signature__ attribute: # return a signature based on it - return _signature_fromstr(sigcls, obj, text_sig) + return _signature_fromstr(sigcls, obj, text_sig, trusted=True) # No '__text_signature__' was found for the 'obj' class. # Last option is to check if its '__init__' is diff -r 582e8e71f635 Lib/test/test_inspect.py --- a/Lib/test/test_inspect.py Wed Apr 15 00:00:41 2015 -0400 +++ b/Lib/test/test_inspect.py Wed Apr 15 12:40:01 2015 -0400 @@ -1836,6 +1836,7 @@ self.assertEqual(p('local'), 3) self.assertEqual(p('sys'), sys.maxsize) self.assertEqual(p('exp'), sys.maxsize - 1) + self.assertEqual(p('exp2'), 15) test_callable(object) diff -r 582e8e71f635 Modules/_testcapimodule.c --- a/Modules/_testcapimodule.c Wed Apr 15 00:00:41 2015 -0400 +++ b/Modules/_testcapimodule.c Wed Apr 15 12:40:01 2015 -0400 @@ -3101,7 +3101,7 @@ "docstring_with_signature_with_defaults(module, s='avocado',\n" " b=b'bytes', d=3.14, i=35, n=None, t=True, f=False,\n" " local=the_number_three, sys=sys.maxsize,\n" -" exp=sys.maxsize - 1)\n" +" exp=sys.maxsize - 1, exp2=max(the_number_three*5, 12))\n" "--\n" "\n" "\n"