|
| 1 | +PEP: 570 |
| 2 | +Title: Python Positional-Only Parameters |
| 3 | +Version: $Revision$ |
| 4 | +Last-Modified: $Date$ |
| 5 | +Author: Larry Hastings < [email protected]>, |
| 6 | + Pablo Galindo < [email protected]>, |
| 7 | + Mario Corchero < [email protected]> |
| 8 | +Discussions-To: Python-Dev < [email protected]> |
| 9 | +Status: Draft |
| 10 | +Type: Standards Track |
| 11 | +Content-Type: text/x-rst |
| 12 | +Created: 20-Jan-2018 |
| 13 | + |
| 14 | + |
| 15 | +======== |
| 16 | +Overview |
| 17 | +======== |
| 18 | + |
| 19 | +This PEP proposes a syntax for positional-only parameters in Python. |
| 20 | +Positional-only parameters are parameters without an externally-usable |
| 21 | +name; when a function accepting positional-only parameters is called, |
| 22 | +positional arguments are mapped to these parameters based solely on |
| 23 | +their position. |
| 24 | + |
| 25 | +========= |
| 26 | +Rationale |
| 27 | +========= |
| 28 | + |
| 29 | +Python has always supported positional-only parameters. |
| 30 | +Early versions of Python lacked the concept of specifying |
| 31 | +parameters by name, so naturally all parameters were |
| 32 | +positional-only. This changed around Python 1.0, when |
| 33 | +all parameters suddenly became positional-or-keyword. |
| 34 | +This allowed users to provide arguments to a function both |
| 35 | +positionally or referencing the keyword used in the definition |
| 36 | +of it. But, this is not always desired nor even available as |
| 37 | +even in current versions of Python, many CPython |
| 38 | +"builtin" functions still only accept positional-only arguments. |
| 39 | + |
| 40 | +Even if positional arguments only in a function can be achieved |
| 41 | +via using ``*args`` parameters and extracting them one by one, |
| 42 | +the solution is far from ideal and not as expressive as the one |
| 43 | +proposed in this PEP, which targets to provide syntax to specify |
| 44 | +accepting a specific number of positional-only parameters. |
| 45 | +Additionally, this will bridge the gap we currently find between |
| 46 | +builtin functions that today allows to specify positional-only |
| 47 | +parameters and pure Python implementations that lack the |
| 48 | +syntax for it. |
| 49 | + |
| 50 | +----------------------------------------------------- |
| 51 | +Positional-Only Parameter Semantics In Current Python |
| 52 | +----------------------------------------------------- |
| 53 | + |
| 54 | +There are many, many examples of builtins that only |
| 55 | +accept positional-only parameters. The resulting |
| 56 | +semantics are easily experienced by the Python |
| 57 | +programmer--just try calling one, specifying its |
| 58 | +arguments by name:: |
| 59 | + |
| 60 | + |
| 61 | + >>> help(pow) |
| 62 | + ... |
| 63 | + pow(x, y, z=None, /) |
| 64 | + ... |
| 65 | + >>> pow(x=5, y=3) |
| 66 | + Traceback (most recent call last): |
| 67 | + File "<stdin>", line 1, in <module> |
| 68 | + TypeError: pow() takes no keyword arguments |
| 69 | + |
| 70 | +Pow clearly expresses that its arguments are only positional |
| 71 | +via the ``/`` marker, but this is at the moment only documentational, |
| 72 | +Python developers cannot write such syntax. |
| 73 | + |
| 74 | +In addition, there are some functions with particularly |
| 75 | +interesting semantics: |
| 76 | + |
| 77 | +* ``range()``, which accepts an optional parameter |
| 78 | + to the *left* of its required parameter. [#RANGE]_ |
| 79 | + |
| 80 | +* ``dict()``, whose mapping/iterator parameter is optional and |
| 81 | + semantically must be positional-only. Any externally |
| 82 | + visible name for this parameter would occlude |
| 83 | + that name going into the ``**kwarg`` keyword variadic |
| 84 | + parameter dict! [#DICT]_ |
| 85 | + |
| 86 | +Obviously one can simulate any of these in pure Python code |
| 87 | +by accepting ``(*args, **kwargs)`` and parsing the arguments |
| 88 | +by hand. But this results in a disconnect between the |
| 89 | +Python function signature and what it actually accepts, |
| 90 | +not to mention the work of implementing said argument parsing. |
| 91 | + |
| 92 | +========== |
| 93 | +Motivation |
| 94 | +========== |
| 95 | + |
| 96 | +The new syntax will allow developers to further control how their |
| 97 | +API can be consumed. It will allow restricting the usage of keyword |
| 98 | +Specify arguments by adding the new type of positional-only ones. |
| 99 | + |
| 100 | +A similar PEP with a broader scope (PEP 457) was proposed |
| 101 | +to define the syntax. This PEP builds on top of part of it |
| 102 | +to define and provide an implementation for the ``/`` syntax on |
| 103 | +function signatures. |
| 104 | + |
| 105 | +================================================================= |
| 106 | +The Current State Of Documentation For Positional-Only Parameters |
| 107 | +================================================================= |
| 108 | + |
| 109 | +The documentation for positional-only parameters is incomplete |
| 110 | +and inconsistent: |
| 111 | + |
| 112 | +* Some functions denote optional groups of positional-only arguments |
| 113 | + by enclosing them in nested square brackets. [#BORDER]_ |
| 114 | + |
| 115 | +* Some functions denote optional groups of positional-only arguments |
| 116 | + by presenting multiple prototypes with varying numbers of |
| 117 | + arguments. [#SENDFILE]_ |
| 118 | + |
| 119 | +* Some functions use *both* of the above approaches. [#RANGE]_ [#ADDCH]_ |
| 120 | + |
| 121 | +One more important idea to consider: currently in the documentation |
| 122 | +there's no way to tell whether a function takes positional-only |
| 123 | +parameters. ``open()`` accepts keyword arguments, ``ord()`` does |
| 124 | +not, but there is no way of telling just by reading the |
| 125 | +documentation that this is true. |
| 126 | + |
| 127 | +==================== |
| 128 | +Syntax And Semantics |
| 129 | +==================== |
| 130 | + |
| 131 | +From the "ten-thousand foot view", and ignoring ``*args`` and ``**kwargs`` |
| 132 | +for now, the grammar for a function definition currently looks like this:: |
| 133 | + |
| 134 | + def name(positional_or_keyword_parameters, *, keyword_only_parameters): |
| 135 | + |
| 136 | +Building on that perspective, the new syntax for functions would look |
| 137 | +like this:: |
| 138 | + |
| 139 | + def name(positional_only_parameters, /, positional_or_keyword_parameters, |
| 140 | + *, keyword_only_parameters): |
| 141 | + |
| 142 | +All parameters before the ``/`` are positional-only. If ``/`` is |
| 143 | +not specified in a function signature, that function does not |
| 144 | +accept any positional-only parameters. |
| 145 | +The logic around optional values for positional-only argument |
| 146 | +Remains the same as the one for positional-or-keyword. Once |
| 147 | +a positional-only argument is provided with a default, |
| 148 | +the following positional-only and positional-or-keyword argument |
| 149 | +need to have a default as well. Positional-only parameters that |
| 150 | +don’t have a default value are "required" positional-only parameters. |
| 151 | +Therefore the following are valid signatures:: |
| 152 | + |
| 153 | + def name(p1, p2, /, p_or_kw, *, kw): |
| 154 | + def name(p1, p2=None, /, p_or_kw=None, *, kw): |
| 155 | + def name(p1, p2=None, /, *, kw): |
| 156 | + def name(p1, p2=None, /): |
| 157 | + def name(p1, p2, /, p_or_kw): |
| 158 | + def name(p1, p2, /): |
| 159 | + |
| 160 | +Whilst the followings are not:: |
| 161 | + |
| 162 | + def name(p1, p2=None, /, p_or_kw, *, kw): |
| 163 | + def name(p1=None, p2, /, p_or_kw=None, *, kw): |
| 164 | + def name(p1=None, p2, /): |
| 165 | + |
| 166 | +========================== |
| 167 | +Full grammar specification |
| 168 | +========================== |
| 169 | + |
| 170 | +A draft of the proposed grammar specification is:: |
| 171 | + |
| 172 | + new_typedargslist: |
| 173 | + tfpdef (',' tfpdef)* ',' '/' [',' [typedargslist]] | typedargslist |
| 174 | + |
| 175 | + new_varargslist: |
| 176 | + vfpdef (',' vfpdef)* ',' '/' [',' [varargslist]] | varargslist |
| 177 | + |
| 178 | +It will be added to the actual typedargslist and varargslist |
| 179 | +but for easier discussion is presented as new_typedargslist and new_varargslist |
| 180 | + |
| 181 | + |
| 182 | +=================== |
| 183 | +Implementation Plan |
| 184 | +=================== |
| 185 | + |
| 186 | +The implementation will involve a full change of the Grammar. This will |
| 187 | +involve following the steps outlined in PEP 306 [#PEP306]_. In addition, other |
| 188 | +steps are needed including: |
| 189 | + |
| 190 | +* Modifying the code object and the function object to be aware of positional |
| 191 | + only arguments. |
| 192 | + |
| 193 | +* Modifiying ``ceval.c`` (``PyEval_EvalCodeEx``, ``PyEval_EvalFrameEx``...) |
| 194 | + to correctly handle positional-only arguments. |
| 195 | + |
| 196 | +* Modifying ``marshal.c`` to account for the modifications of the code object. |
| 197 | + |
| 198 | + |
| 199 | +This does not intend to be a guide or a comprehensive recipe on how to implement |
| 200 | +this but a rough outline of the changes this will make to the codebase. |
| 201 | + |
| 202 | +The advantages of this implementation involve speed, consistency with the |
| 203 | +implementation of keyword-only parameters as in PEP 3102 and a simpler implementation |
| 204 | +of all the tools and modules that will be impacted by this change. |
| 205 | + |
| 206 | +============== |
| 207 | +Rejected Ideas |
| 208 | +============== |
| 209 | + |
| 210 | +---------- |
| 211 | +Do Nothing |
| 212 | +---------- |
| 213 | + |
| 214 | +Always an option, just not adding it. It was considered |
| 215 | +though that the benefits of adding it is worth the complexity |
| 216 | +it adds to the language. |
| 217 | + |
| 218 | +--------------------- |
| 219 | +After marker proposal |
| 220 | +--------------------- |
| 221 | + |
| 222 | +A complaint against the proposal is the fact that the modifier of |
| 223 | +the signature impacts the "already passed" tokens. |
| 224 | + |
| 225 | +This might make confusing to "human parsers" to read functions |
| 226 | +with many arguments. Example:: |
| 227 | + |
| 228 | + def really_bad_example_of_a_python_function(fist_long_argument, second_long_argument, |
| 229 | + third_long_argument, /): |
| 230 | + |
| 231 | +It is not until you reach the end of the signature that the reader |
| 232 | +realized the ``/`` and therefore the fact that the arguments are |
| 233 | +position-only. This deviates from how the keyword-only marker works. |
| 234 | + |
| 235 | +That said we could not find an implementation that would modify the |
| 236 | +arguments after the marker, as that will force the one before the |
| 237 | +marker to be position only as well. Example:: |
| 238 | + |
| 239 | + def (x, y, /, z): |
| 240 | + |
| 241 | +If we define that ``/`` makes only z position-only it won't be possible |
| 242 | +to call x and y via keyword argument. Finding a way to work around it |
| 243 | +will add confusion given that at the moment keyword arguments cannot be |
| 244 | +followed by positional arguments. ``/`` will therefore make both the |
| 245 | +preceding and following position-only. |
| 246 | + |
| 247 | +------------------- |
| 248 | +Per-argument marker |
| 249 | +------------------- |
| 250 | + |
| 251 | +Using a per argument marker might be an option as well. The approach |
| 252 | +basically adds a token to each of the arguments that are position only |
| 253 | +and requires those to be placed together. Example:: |
| 254 | + |
| 255 | + def (.arg1, .arg2, arg3): |
| 256 | + |
| 257 | +Note the dot on arg1 and arg2. Even if this approach might look easier |
| 258 | +to read it has been discarded as ``/`` goes further inline with the |
| 259 | +keyword-only approach and is less error prone. |
| 260 | + |
| 261 | + |
| 262 | +---------------- |
| 263 | +Using decorators |
| 264 | +---------------- |
| 265 | + |
| 266 | +It has been suggested on python-ideas [#python-ideas-decorator-based]_ to provide |
| 267 | +a decorator written in Python as an implementation for this feature. This approach |
| 268 | +has the advantage that keeps parameter declaration more easy to read but also |
| 269 | +introduces an asymmetry on how parameter behaviour is declared. Also, as the ``/`` |
| 270 | +syntax is already introduced for C functions, this inconsistency will make more |
| 271 | +difficult to implement all tools and modules that deal with this syntax including |
| 272 | +but not limited to, the argument clinic, the inspect module and the ast module. |
| 273 | +Another disadvantage of this approach is that calling the decorated functions |
| 274 | +will be slower than the functions generated if the feature was implemented directly |
| 275 | +in C. |
| 276 | + |
| 277 | +====== |
| 278 | +Thanks |
| 279 | +====== |
| 280 | + |
| 281 | +Credit for most of the content of this PEP is contained in Larry Hastings’s PEP 457. |
| 282 | + |
| 283 | +Credit for the use of '/' as the separator between positional-only and positional-or-keyword |
| 284 | +parameters go to Guido van Rossum, in a proposal from 2012. [#GUIDO]_ |
| 285 | + |
| 286 | +Credit for discussion about the simplification of the grammar goes to |
| 287 | +Braulio Valdivieso. |
| 288 | + |
| 289 | +.. [#DICT] |
| 290 | + http://docs.python.org/3/library/stdtypes.html#dict |
| 291 | +
|
| 292 | +.. [#RANGE] |
| 293 | + http://docs.python.org/3/library/functions.html#func-range |
| 294 | +
|
| 295 | +.. [#BORDER] |
| 296 | + http://docs.python.org/3/library/curses.html#curses.window.border |
| 297 | +
|
| 298 | +.. [#SENDFILE] |
| 299 | + http://docs.python.org/3/library/os.html#os.sendfile |
| 300 | +
|
| 301 | +.. [#ADDCH] |
| 302 | + http://docs.python.org/3/library/curses.html#curses.window.addch |
| 303 | +
|
| 304 | +.. [#GUIDO] |
| 305 | + Guido van Rossum, posting to python-ideas, March 2012: |
| 306 | + https://mail.python.org/pipermail/python-ideas/2012-March/014364.html |
| 307 | + and |
| 308 | + https://mail.python.org/pipermail/python-ideas/2012-March/014378.html |
| 309 | + and |
| 310 | + https://mail.python.org/pipermail/python-ideas/2012-March/014417.html |
| 311 | +
|
| 312 | +.. [#PEP306] |
| 313 | + https://www.python.org/dev/peps/pep-0306/ |
| 314 | +
|
| 315 | +.. [#python-ideas-decorator-based] |
| 316 | + https://mail.python.org/pipermail/python-ideas/2017-February/044888.html |
| 317 | +
|
| 318 | +========= |
| 319 | +Copyright |
| 320 | +========= |
| 321 | + |
| 322 | +This document has been placed in the public domain. |
0 commit comments