diff -r 582e8e71f635 Lib/inspect.py --- a/Lib/inspect.py Wed Apr 15 00:00:41 2015 -0400 +++ b/Lib/inspect.py Thu Apr 23 01:30:38 2015 -0700 @@ -1838,7 +1838,25 @@ return clean_signature, self_parameter, last_positional_only -def _signature_fromstr(cls, obj, s, skip_bound_arg=True): +def _eval_ast_expr(node, symbols, *, filename='-'): + """ + Takes an ast.Expr node. Compiles and evaluates it. + Returns the result of the expression. + + symbols represents the globals dict the expression + should see. (There's no equivalent for "locals" here.) + """ + + if isinstance(node, ast.Expr): + node = node.value + + expr = ast.Expression(node) + code_object = compile(expr, filename, 'eval') + function = types.FunctionType(code_object, symbols) + return function() + + +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. """ @@ -1862,74 +1880,56 @@ parameters = [] 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: + + # 99% of the time, default values passed in from + # Argument Clinic are simple static values. But 1% + # of the time they're 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. + + simulated_module_dict = None + def populate_simulated_module_dict(): + nonlocal simulated_module_dict + 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 p(name_node, default_node, default=empty): + assert isinstance(name_node, ast.arg) + if name_node.annotation != None: raise ValueError("Annotations are not currently supported") - return node.arg - - def wrap_value(s): - try: - value = eval(s, module_dict) - except NameError: - try: - value = eval(s, sys_module_dict) - except NameError: - raise RuntimeError() - - if isinstance(value, str): - return ast.Str(value) - if isinstance(value, (int, float)): - return ast.Num(value) - if isinstance(value, bytes): - return ast.Bytes(value) - if value in (True, False, None): - return ast.NameConstant(value) - raise RuntimeError() - - class RewriteSymbolics(ast.NodeTransformer): - def visit_Attribute(self, node): - a = [] - n = node - while isinstance(n, ast.Attribute): - a.append(n.attr) - n = n.value - if not isinstance(n, ast.Name): - raise RuntimeError() - a.append(n.id) - value = ".".join(reversed(a)) - return wrap_value(value) - - def visit_Name(self, node): - if not isinstance(node.ctx, ast.Load): - raise ValueError() - return wrap_value(node.id) - - def p(name_node, default_node, default=empty): - name = parse_name(name_node) - if name is invalid: - return None + name = name_node.arg + if default_node and default_node is not _empty: try: - default_node = RewriteSymbolics().visit(default_node) - o = ast.literal_eval(default_node) + default = ast.literal_eval(default_node) 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)) + if not trusted: + return + try: + if simulated_module_dict is None: + populate_simulated_module_dict() + default = _eval_ast_expr(default_node, simulated_module_dict) + except ValueError: + return None + + parameters.append(Parameter(name, kind, + default=default, annotation=empty)) # non-keyword-only parameters args = reversed(f.args.args) @@ -1992,7 +1992,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 +2132,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 Thu Apr 23 01:30:38 2015 -0700 @@ -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 Thu Apr 23 01:30:38 2015 -0700 @@ -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"