"" default values for parameters that had no default value. * Converted some asserts into proper failure messages. * Many doc improvements and fixes. [#20226]">

cpython: abf87e1fbc62 (original) (raw)

Mercurial > cpython

changeset 88512:abf87e1fbc62

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. [#20226]

Larry Hastings larry@hastings.org
date Thu, 16 Jan 2014 11:32:01 -0800
parents 18a71ae9e715
children cd3fdf21a6e4
files Doc/c-api/arg.rst Doc/howto/clinic.rst Lib/inspect.py Lib/test/test_inspect.py Misc/NEWS Modules/_cursesmodule.c Modules/_dbmmodule.c Modules/_opcode.c Modules/_testcapimodule.c Modules/posixmodule.c Modules/zlibmodule.c Tools/clinic/clinic.py Tools/clinic/clinic_test.py
diffstat 13 files changed, 472 insertions(+), 187 deletions(-)[+] [-] Doc/c-api/arg.rst 2 Doc/howto/clinic.rst 261 Lib/inspect.py 89 Lib/test/test_inspect.py 5 Misc/NEWS 3 Modules/_cursesmodule.c 4 Modules/_dbmmodule.c 6 Modules/_opcode.c 4 Modules/_testcapimodule.c 4 Modules/posixmodule.c 4 Modules/zlibmodule.c 4 Tools/clinic/clinic.py 250 Tools/clinic/clinic_test.py 23

line wrap: on

line diff

--- a/Doc/c-api/arg.rst +++ b/Doc/c-api/arg.rst @@ -294,6 +294,8 @@ Other objects the object pointer is stored. If the Python object does not have the required type, :exc:TypeError is raised. +.. _o_ampersand: + O& (object) [converter, anything] Convert a Python object to a C variable through a converter function. This takes two arguments: the first is a function, the second is the address of a C

--- a/Doc/howto/clinic.rst +++ b/Doc/howto/clinic.rst @@ -127,6 +127,12 @@ 4. Cut the docstring and paste it in bet margin, with no line wider than 80 characters. (Argument Clinic will preserve indents inside the docstring.)

-The NULL default value ----------------------- - -For string and object parameters, you can set them to None to indicate -that there is no default. However, that means the C variable will be -initialized to Py_None. For convenience's sakes, there's a special -value called NULL for just this case: from Python's perspective it -behaves like a default value of None, but the C variable is initialized -with NULL. - Converting functions using PyArg_UnpackTuple -------------------------------------------- @@ -654,35 +654,70 @@ the same converters. All arguments to Argument Clinic converters are keyword-only. All Argument Clinic converters accept the following arguments: -py_default

+

-c_default

+

+

-required

+

+

+

-annotation

+ +Please note, not every possible combination of arguments will work. +Often these arguments are implemented internally by specific PyArg_ParseTuple +format units, with specific behavior. For example, currently you cannot +call str and pass in zeroes=True without also specifying an encoding; +although it's perfectly reasonable to think this would work, these semantics don't +map to any existing format unit. So Argument Clinic doesn't support it. (Or, at +least, not yet.) Below is a table showing the mapping of legacy converters into real Argument Clinic converters. On the left is the legacy converter, @@ -720,9 +755,9 @@ on the right is the text you'd replace i 'u' Py_UNICODE 'U' unicode 'w*' Py_buffer(types='bytearray rwbuffer') -'y#' str(type='bytes', length=True) +'y#' str(types='bytes', length=True) 'Y' PyByteArrayObject -'y' str(type='bytes') +'y' str(types='bytes') 'y*' Py_buffer 'Z#' Py_UNICODE(nullable=True, length=True) 'z#' str(nullable=True, length=True) @@ -789,6 +824,90 @@ This restriction doesn't seem unreasonab hard-coded encoding strings for parameters whose format units start with e. +.. _default_values: + +Parameter default values +------------------------ + +Default values for parameters can be any of a number of values. +At their simplest, they can be string, int, or float literals:: +

+ +They can also use any of Python's built-in constants:: +

+ +There's also special support for a default value of NULL, and +for simple expressions, documented in the following sections. + + +The NULL default value +-------------------------- + +For string and object parameters, you can set them to None to indicate +that there's no default. However, that means the C variable will be +initialized to Py_None. For convenience's sakes, there's a special +value called NULL for just this reason: from Python's perspective it +behaves like a default value of None, but the C variable is initialized +with NULL. + +Expressions specified as default values +--------------------------------------- + +The default value for a parameter can be more than just a literal value. +It can be an entire expression, using math operators and looking up attributes +on objects. However, this support isn't exactly simple, because of some +non-obvious semantics. + +Consider the following example:: +

+ +sys.maxsize can have different values on different platforms. Therefore +Argument Clinic can't simply evaluate that expression locally and hard-code it +in C. So it stores the default in such a way that it will get evaluated at +runtime, when the user asks for the function's signature. + +What namespace is available when the expression is evaluated? It's evaluated +in the context of the module the builtin came from. So, if your module has an +attribute called "max_widgets", you may simply use it:: +

+ +If the symbol isn't found in the current module, it fails over to looking in +sys.modules. That's how it can find sys.maxsize for example. (Since you +don't know in advance what modules the user will load into their interpreter, +it's best to restrict yourself to modules that are preloaded by Python itself.) + +Evaluating default values only at runtime means Argument Clinic can't compute +the correct equivalent C default value. So you need to tell it explicitly. +When you use an expression, you must also specify the equivalent expression +in C, using the c_default parameter to the converter:: +

+ +Another complication: Argument Clinic can't know in advance whether or not the +expression you supply is valid. It parses it to make sure it looks legal, but +it can't actually know. You must be very careful when using expressions to +specify values that are guaranteed to be valid at runtime + +Finally, because expressions must be representable as static C values, there +are many restrictions on legal expressions. Here's a list of Python features +you're not permitted to use: + +* Function calls. +* Inline if statements (3 if foo else 5). +* Automatic sequence unpacking (*[1, 2, 3]). +* List/set/dict comprehensions and generator expressions. +* Tuple/list/set/dict literals. + + + Using a return converter ------------------------ @@ -1096,7 +1215,73 @@ any arguments. You can still use a self converter, a return converter, and specify a type argument to the object converter for METH_O. -Using Argument Clinic in Python files +The #ifdef trick +---------------------------------------------- + +If you're converting a function that isn't available on all platforms, +there's a trick you can use to make life a little easier. The existing +code probably looks like this:: +

+ +And then in the PyMethodDef structure at the bottom you'll have:: +

+ +In this scenario, you should change the code to look like the following:: +

+ +Run Argument Clinic on the code in this state, then refresh the file in +your editor. Now you'll have the generated code, including the #define +for the PyMethodDef, like so:: +

+ +Change the #endif at the bottom as follows:: +

+ +Now you can remove the #ifdefs around the PyMethodDef structure +at the end, and replace those three lines with MODULE_FUNCTIONNAME. +If the function is available, the macro turns into the PyMethodDef +static value, including the trailing comma; if the function isn't +available, the macro turns into nothing. Perfect + +(This is the preferred approach for optional functions; in the future, +Argument Clinic may generate the entire PyMethodDef structure.) + ------------------------------------- It's actually possible to use Argument Clinic to preprocess Python files.

--- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1954,6 +1954,8 @@ class Signature: if not s: return None

+ if s.endswith("/)"): kind = Parameter.POSITIONAL_ONLY s = s[:-2] + ')' @@ -1969,55 +1971,74 @@ class Signature: if not isinstance(module, ast.Module): return None

parameters = [] empty = Parameter.empty invalid = object()

-

+

+

def p(name_node, default_node, default=empty):

--- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -1601,12 +1601,15 @@ class TestSignatureObject(unittest.TestC self.assertTrue(isinstance(signature, inspect.Signature)) def p(name): return signature.parameters[name].default self.assertEqual(p('s'), 'avocado')

def test_signature_on_non_function(self): with self.assertRaisesRegex(TypeError, 'is not a callable object'):

--- a/Misc/NEWS +++ b/Misc/NEWS @@ -88,6 +88,9 @@ Tests Tools/Demos ----------- +- Issue #20226: Argument Clinic now permits simple expressions

--- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -618,7 +618,7 @@ curses_window_addch(PyObject *self, PyOb int group_right_1 = 0; long attr = 0;

@@ -650,7 +650,7 @@ curses_window_addch(PyObject self, PyOb static PyObject curses_window_addch_impl(PyObject self, int group_left_1, int x, int y, PyObject ch, int group_right_1, long attr) -/[clinic end generated code: checksum=44ed958b891cde91205e584c766e048f3999714f]/ +/[clinic end generated code: checksum=b073327add8197b6ba7fb96c87062422c8312954]/ { PyCursesWindowObject *cwself = (PyCursesWindowObject *)self; int coordinates_group = group_left_1;

--- a/Modules/_dbmmodule.c +++ b/Modules/_dbmmodule.c @@ -297,7 +297,7 @@ dbm_dbm_get(PyObject *self, PyObject *ar int group_right_1 = 0; PyObject *default_value = NULL;

@@ -318,7 +318,7 @@ dbm_dbm_get(PyObject *self, PyObject *ar static PyObject dbm_dbm_get_impl(dbmobject dp, const char key, Py_ssize_clean_t key_length, int group_right_1, PyObject default_value) -/[clinic end generated code: checksum=28cf8928811bde51e535d67ae98ea039d79df717]/ +/[clinic end generated code: checksum=2c3209571267017f1b9abbd19e1b521849fd5d4a]/ { datum dbm_key, val; @@ -450,7 +450,7 @@ dbm.open as dbmopen flags: str="r" How to open the file. "r" for reading, "w" for writing, etc.

--- a/Modules/_opcode.c +++ b/Modules/_opcode.c @@ -39,7 +39,7 @@ static PyObject * int oparg = 0; int _return_value;

@@ -64,7 +64,7 @@ exit: static int _opcode_stack_effect_impl(PyModuleDef module, int opcode, int group_right_1, int oparg) -/[clinic end generated code: checksum=e880e62dc7b0de73403026eaf4f8074aa106358b]/ +/[clinic end generated code: checksum=47e76ec27523da4ab192713642d32482cd743aa4]*/ { int effect; if (HAS_ARG(opcode)) {

--- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2870,7 +2870,7 @@ PyDoc_STRVAR(docstring_with_signature_an ); PyDoc_STRVAR(docstring_with_signature_with_defaults, -"docstring_with_signature_with_defaults(s='avocado', d=3.14, i=35, c=sys.maxsize, n=None, t=True, f=False)\n" +"docstring_with_signature_with_defaults(s='avocado', b=b'bytes', d=3.14, i=35, n=None, t=True, f=False, local=the_number_three, sys=sys.maxsize, exp=sys.maxsize - 1)\n" "\n" "\n" "\n" @@ -3317,6 +3317,8 @@ PyInit__testcapi(void) Py_INCREF(&PyInstanceMethod_Type); PyModule_AddObject(m, "instancemethod", (PyObject *)&PyInstanceMethod_Type);

+ TestError = PyErr_NewException("_testcapi.error", NULL, NULL); Py_INCREF(TestError); PyModule_AddObject(m, "error", TestError);

--- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -2401,7 +2401,7 @@ class dir_fd_converter(CConverter): /*[clinic input] -os.stat -> object(doc_default='stat_result') +os.stat path : path_t(allow_fd=True) Path to be examined; can be string, bytes, or open-file-descriptor int. @@ -2523,7 +2523,7 @@ posix_lstat(PyObject *self, PyObject ar #define OS_ACCESS_DIR_FD_CONVERTER dir_fd_unavailable #endif /[clinic input] -os.access -> object(doc_default='True if granted, False otherwise') +os.access path: path_t(allow_fd=True) Path to be tested; can be string, bytes, or open-file-descriptor int.

--- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -202,7 +202,7 @@ zlib_compress(PyModuleDef *module, PyObj int group_right_1 = 0; int level = 0;

@@ -227,7 +227,7 @@ zlib_compress(PyModuleDef module, PyObj static PyObject zlib_compress_impl(PyModuleDef module, Py_buffer bytes, int group_right_1, int level) -/[clinic end generated code: checksum=2c59af563a4595c5ecea4011701f482ae350aa5f]/ +/[clinic end generated code: checksum=66c4d16d0b8b9dd423648d9ef00d6a89d3363665]/ { PyObject *ReturnVal = NULL; Byte *input, *output = NULL;

--- a/Tools/clinic/clinic.py +++ b/Tools/clinic/clinic.py @@ -55,6 +55,13 @@ class Null: NULL = Null() +class Unknown:

+ +unknown = Unknown() + + def _text_accumulator(): text = [] def output(): @@ -197,7 +204,7 @@ def version_splitter(s): accumulator = [] def flush(): if not accumulator:

@@ -596,7 +603,7 @@ static {impl_return_type} count_min = sys.maxsize count_max = -1

@@ -1069,6 +1076,7 @@ class Clinic: self.filename = filename self.modules = collections.OrderedDict() self.classes = collections.OrderedDict()

global clinic clinic = self @@ -1343,29 +1351,7 @@ class Parameter: def is_keyword_only(self): return self.kind == inspect.Parameter.KEYWORD_ONLY -py_special_values = {

-} - -def py_repr(o):

- - -c_special_values = {

-} - -def c_repr(o):

+ def add_c_converter(f, name=None): if not name: @@ -1407,8 +1393,7 @@ class CConverter(metaclass=CConverterAut """ For the init function, self, name, function, and default must be keyword-or-positional parameters. All other

@@ -1418,23 +1403,23 @@ class CConverter(metaclass=CConverterAut # The Python default value for this parameter, as a Python value. # Or the magic value "unspecified" if there is no default.

-

- # "default" converted into a C value, as a string. # Or None if there is no default. c_default = None

+ # The default value used to initialize the C variable when # there is no default, but not specifying a default may # result in an "uninitialized variable" warning. This can @@ -1485,12 +1470,12 @@ class CConverter(metaclass=CConverterAut # Only used by format units ending with '#'. length = False

if default is not unspecified:

@@ -1498,23 +1483,19 @@ class CConverter(metaclass=CConverterAut fail("{}: default value {!r} for field {} is not of type {}".format( self.class.name, default, name, types_str)) self.default = default

+

+ if annotation != unspecified: fail("The 'annotation' parameter is not currently permitted.")

def converter_init(self): pass def is_optional(self):

def render(self, parameter, data): """ @@ -1655,8 +1636,9 @@ class bool_converter(CConverter): c_ignored_default = '0' def converter_init(self):

class char_converter(CConverter): type = 'char' @@ -1665,7 +1647,7 @@ class char_converter(CConverter): c_ignored_default = "'\0'" def converter_init(self):

@@ -1797,8 +1779,8 @@ class object_converter(CConverter): @add_legacy_c_converter('s#', length=True) -@add_legacy_c_converter('y', type="bytes") -@add_legacy_c_converter('y#', type="bytes", length=True) +@add_legacy_c_converter('y', types="bytes") +@add_legacy_c_converter('y#', types="bytes", length=True) @add_legacy_c_converter('z', nullable=True) @add_legacy_c_converter('z#', nullable=True, length=True) class str_converter(CConverter): @@ -1993,8 +1975,8 @@ class CReturnConverter(metaclass=CReturn # Or the magic value "unspecified" if there is no default. default = None

@@ -2212,6 +2194,7 @@ class DSLParser: self.indent = IndentStack() self.kind = CALLABLE self.coexist = False

def directive_version(self, required): global version @@ -2244,15 +2227,18 @@ class DSLParser: self.block.signatures.append(c) def at_classmethod(self):

def at_staticmethod(self):

def at_coexist(self):

@@ -2503,6 +2489,7 @@ class DSLParser: if not self.indent.infer(line): return self.next(self.state_function_docstring, line)

@@ -2516,6 +2503,10 @@ class DSLParser: p.group = -p.group def state_parameter(self, line):

+ if self.ignore_line(line): return @@ -2529,6 +2520,11 @@ class DSLParser: # we indented, must be to new parameter docstring column return self.next(self.state_parameter_docstring_start, line)

+ line = line.lstrip() if line in ('*', '/', '[', ']'): @@ -2547,48 +2543,123 @@ class DSLParser: else: fail("Function " + self.function.name + " has an unsupported group configuration. (Unexpected state " + str(self.parameter_state) + ")")

function_args = module.body[0].args parameter = function_args.args[0]

- parameter_name = parameter.arg name, legacy, kwargs = self.parse_converter(parameter.annotation)

+

+

+

+

+

+

+

+

+

+

+

dict = legacy_converters if legacy else converters legacy_str = "legacy " if legacy else "" @@ -2777,7 +2848,7 @@ class DSLParser: if p.converter.is_optional(): a.append('=') value = p.converter.default

@@ -2788,9 +2859,18 @@ class DSLParser: add(fix_right_bracket_count(0)) add(')')

docstring_first_line = output() @@ -2998,8 +3078,8 @@ def main(argv): # print(" ", short_name + "".join(parameters)) print()

if ns.make:

--- a/Tools/clinic/clinic_test.py +++ b/Tools/clinic/clinic_test.py @@ -231,20 +231,20 @@ xyz self._test_clinic(""" verbatim text here lah dee dah -/[copy] +/[copy input] def -[copy]/ +[copy start generated code]/ abc -/[copy checksum: 03cfd743661f07975fa2f1220c5194cbaff48451]/ +/[copy end generated code: checksum=03cfd743661f07975fa2f1220c5194cbaff48451]/ xyz """, """ verbatim text here lah dee dah -/[copy] +/[copy input] def -[copy]/ +[copy start generated code]/ def -/[copy checksum: 7b18d017f89f61cf17d47f92749ea6930a3f1deb]/ +/[copy end generated code: checksum=7b18d017f89f61cf17d47f92749ea6930a3f1deb]/ xyz """) @@ -292,17 +292,6 @@ os.access p = function.parameters['path'] self.assertEqual(1, p.converter.args['allow_fd'])

-module os -os.stat as os_stat_fn -> object(doc_default='stat_result') -

- def test_function_docstring(self): function = self.parse_function(""" module os