summaryrefslogtreecommitdiffstats
path: root/Lib/inspect.py
diff options
context:
space:
mode:
authorLarry Hastings <larry@hastings.org>2014-01-16 19:32:01 (GMT)
committerLarry Hastings <larry@hastings.org>2014-01-16 19:32:01 (GMT)
commit2a727916c598c576507e3a7447fc54cc0e01d4a5 (patch)
treef9f4ab7d1ff8c08a44659a1c2c6a11563ded215d /Lib/inspect.py
parente1f554490de1852faa03b5c06f051756aa168bfe (diff)
downloadcpython-2a727916c598c576507e3a7447fc54cc0e01d4a5.zip
cpython-2a727916c598c576507e3a7447fc54cc0e01d4a5.tar.gz
cpython-2a727916c598c576507e3a7447fc54cc0e01d4a5.tar.bz2
Issue #20226: Major improvements to Argument Clinic.
* You may now specify an expression as the default value for a parameter! Example: "sys.maxsize - 1". This support is intentionally quite limited; you may only use values that can be represented as static C values. * Removed "doc_default", simplified support for "c_default" and "py_default". (I'm not sure we still even need "py_default", but I'm leaving it in for now in case a use presents itself.) * Parameter lines support a trailing '\\' as a line continuation character, allowing you to break up long lines. * The argument parsing code generated when supporting optional groups now uses PyTuple_GET_SIZE instead of PyTuple_GetSize, leading to a 850% speedup in parsing. (Just kidding, this is an unmeasurable difference.) * A bugfix for the recent regression where the generated prototype from pydoc for builtins would be littered with unreadable "=<object ...>"" default values for parameters that had no default value. * Converted some asserts into proper failure messages. * Many doc improvements and fixes.
Diffstat (limited to 'Lib/inspect.py')
-rw-r--r--Lib/inspect.py95
1 files changed, 58 insertions, 37 deletions
diff --git a/Lib/inspect.py b/Lib/inspect.py
index c9d10dc..d6bd8cd 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -1954,6 +1954,8 @@ class Signature:
if not s:
return None
+ Parameter = cls._parameter_cls
+
if s.endswith("/)"):
kind = Parameter.POSITIONAL_ONLY
s = s[:-2] + ')'
@@ -1969,55 +1971,74 @@ class Signature:
if not isinstance(module, ast.Module):
return None
- # ast.FunctionDef
f = module.body[0]
parameters = []
empty = Parameter.empty
invalid = object()
- def parse_attribute(node):
- if not isinstance(node.ctx, ast.Load):
- return None
-
- value = node.value
- o = parse_node(value)
- if o is invalid:
- return invalid
-
- if isinstance(value, ast.Name):
- name = o
- if name not in sys.modules:
- return invalid
- o = sys.modules[name]
-
- return getattr(o, node.attr, invalid)
-
- def parse_node(node):
- if isinstance(node, ast.arg):
- if node.annotation != None:
- raise ValueError("Annotations are not currently supported")
- return node.arg
- if isinstance(node, ast.Num):
- return node.n
- if isinstance(node, ast.Str):
- return node.s
- if isinstance(node, ast.NameConstant):
- return node.value
- if isinstance(node, ast.Attribute):
- return parse_attribute(node)
- if isinstance(node, ast.Name):
+ module = None
+ module_dict = {}
+ module_name = getattr(func, '__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
+
+ 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):
- return invalid
- return node.id
- return invalid
+ raise ValueError()
+ return wrap_value(node.id)
def p(name_node, default_node, default=empty):
- name = parse_node(name_node)
+ name = parse_name(name_node)
if name is invalid:
return None
if default_node:
- o = parse_node(default_node)
+ try:
+ default_node = RewriteSymbolics().visit(default_node)
+ o = ast.literal_eval(default_node)
+ except ValueError:
+ o = invalid
if o is invalid:
return None
default = o if o is not invalid else default