GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing peps.python.org — PEP 570 – Python Positional-Only Parameters | peps.python.org
PEP 570 – Python Positional-Only Parameters
PEP 570 – Python Positional-Only Parameters
AbstractThis PEP proposes to introduce a new syntax, Positional-only parameters have no externally-usable name. When a function accepting positional-only parameters is called, positional arguments are mapped to these parameters based solely on their order. When designing APIs (application programming interfaces), library authors try to ensure correct and intended usage of an API. Without the ability to specify which parameters are positional-only, library authors must be careful when choosing appropriate parameter names. This care must be taken even for required parameters or when the parameters have no external semantic meaning for callers of the API. In this PEP, we discuss:
Within context of the motivation, we then:
MotivationHistory of Positional-Only Parameter Semantics in PythonPython originally supported positional-only parameters. Early versions of the language lacked the ability to call functions with arguments bound to parameters by name. Around Python 1.0, parameter semantics changed to be positional-or-keyword. Since then, users have been able to provide arguments to a function either positionally or by the keyword name specified in the function’s definition. In current versions of Python, many CPython “builtin” and standard library functions only accept positional-only parameters. The resulting semantics can be easily observed by calling one of these functions using keyword arguments: >>> help(pow) ... pow(x, y, z=None, /) ... >>> pow(x=5, y=3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: pow() takes no keyword arguments
There are functions with other interesting semantics:
One can emulate these semantics in Python code by accepting
Additionally, the Problems Without Positional-Only ParametersWithout positional-only parameters, there are challenges for library authors and users of APIs. The following subsections outline the problems encountered by each entity. Challenges for Library AuthorsWith positional-or-keyword parameters, the mix of calling conventions is not
always desirable. Authors may want to restrict usage of an API by disallowing
calling the API with keyword arguments, which exposes the name of the parameter when
part of the public API. This approach is especially useful for required function
parameters that already have semantic meaning (e.g,
Positional-only parameters can be emulated by extracting arguments from
Challenges for Users of an APIUsers may be surprised when first encountering positional-only notation. This is expected given that it has only recently been documented [13] and it is not possible to use in Python code. For these reasons, this notation is currently an outlier that appears only in CPython APIs developed in C. Documenting the notation and making it possible to use it in Python code would eliminate this disconnect. Furthermore, the current documentation for positional-only parameters is inconsistent:
Another point the current documentation does not distinguish is
whether a function takes positional-only parameters. Benefits of Positional-Only ParametersPositional-only parameters give more control to library authors to better express the intended usage of an API and allows the API to evolve in a safe, backward-compatible way. Additionally, it makes the Python language more consistent with existing documentation and the behavior of various “builtin” and standard library functions. Empowering Library AuthorsLibrary authors would have the flexibility to change the name of positional-only parameters without breaking callers. This flexibility reduces the cognitive burden for choosing an appropriate public-facing name for required parameters or parameters that have no true external semantic meaning. Positional-only parameters are useful in several situations such as:
A key
scenario is when a function accepts any keyword argument but can also accepts a
positional one. Prominent examples are Another scenario where positional-only parameters are useful is when the parameter name has no true external semantic meaning. For example, let’s say we want to create a function that converts from one type to another: def as_my_type(x): ... The name of the parameter provides no intrinsic value and forces the API author
to maintain its name forever since callers might pass Additionally, positional-only parameters are useful when an API’s parameters are required and is unambiguous with respect to function. For example: def add_to_queue(item: QueueItem): ... The name of the function makes clear the argument expected. A keyword argument provides minimal benefit and also limits the future evolution of the API. Say at a later time we want this function to be able to take multiple items, while preserving backwards compatibility: def add_to_queue(items: Union[QueueItem, List[QueueItem]]): ... or to take them by using argument lists: def add_to_queue(*items: QueueItem): ... the author would be forced to always keep the original parameter name to avoid potentially breaking callers. By being able to specify positional-only parameters, an author can change the
name of the parameters freely or even change them to io.FileIO.write(self=f, b=b"data") Indeed, function definitions from the standard library implemented in C usually
take >>> help(io.FileIO.write) Help on method_descriptor: write(self, b, /) Write buffer b to file, return number of bytes written. Improving Language ConsistencyThe Python language would be more consistent with positional-only
parameters. If the concept is a normal feature of Python rather than a feature
exclusive to extension modules, it would reduce confusion for users
encountering functions with positional-only parameters. Some major
third-party packages are already using the Bridging the gap found between “builtin” functions which
specify positional-only parameters and pure Python implementations that lack
the positional syntax would improve consistency. The Another essential aspect to consider is PEP 399, which mandates that
pure Python versions of modules in the standard library must have the same
interface and semantics that the accelerator modules implemented in C. For
example, if RationaleWe propose to introduce positional-only parameters as a new syntax to the Python language. The new syntax will enable library authors to further control how their API can be called. It will allow designating which parameters must be called as positional-only, while preventing them from being called as keyword arguments. Previously, (informational) PEP 457 defined the syntax, but with a much more vague
scope. This PEP takes the original proposal a step further by justifying
the syntax and providing an implementation for the PerformanceIn addition to the aforementioned benefits, the parsing and handling of
positional-only arguments is faster. This performance benefit can be
demonstrated in this thread about converting keyword arguments to positional:
[11]. Due to this speedup, there has been a recent
trend towards moving builtins away from keyword arguments: recently,
backwards-incompatible changes were made to disallow keyword arguments to
MaintainabilityProviding a way to specify positional-only parameters in Python will make it easier to maintain pure Python implementations of C modules. Additionally, library authors defining functions will have the choice for choosing positional-only parameters if they determine that passing a keyword argument provides no additional clarity. This is a well discussed, recurring topic on the Python mailing lists:
Logical orderingPositional-only parameters also have the (minor) benefit of enforcing some
logical order when calling interfaces that make use of them. For example, the
range(stop=5, start=0, step=2) range(stop=5, step=2, start=0) range(step=2, start=0, stop=5) range(step=2, stop=5, start=0) at the price of disallowing the use of keyword arguments for the (unique) intended order: range(start=0, stop=5, step=2) Compatibility for Pure Python and C ModulesAnother critical motivation for positional-only parameters is PEP 399: Pure Python/C Accelerator Module Compatibility Requirements. This PEP states that:
If the C code is implemented using the existing capabilities to implement positional-only parameters using the argument clinic, and related machinery, it is not possible for the pure Python counterpart to match the provided interface and requirements. This creates a disparity between the interfaces of some functions and classes in the CPython standard library and other Python implementations. For example: $ python3 # CPython 3.7.2 >>> import binascii; binascii.crc32(data=b'data') TypeError: crc32() takes no keyword arguments $ pypy3 # PyPy 6.0.0 >>>> import binascii; binascii.crc32(data=b'data') 2918445923 Other Python implementations can reproduce the CPython APIs manually, but this goes against the spirit of PEP 399 to avoid duplication of effort by mandating that all modules added to Python’s standard library must have a pure Python implementation with the same interface and semantics. Consistency in SubclassesAnother scenario where positional-only parameters provide benefit occurs when a subclass overrides a method of the base class and changes the name of parameters that are intended to be positional: class Base: def meth(self, arg: int) -> str: ... class Sub(Base): def meth(self, other_arg: int) -> str: ... def func(x: Base): x.meth(arg=12) func(Sub()) # Runtime error This situation could be considered a Liskov violation — the subclass cannot be
used in a context when an instance of the base class is expected. Renaming
arguments when overloading methods can happen when the subclass has reasons to
use a different choice for the parameter name that is more appropriate for the
specific domain of the subclass (e.g., when subclassing OptimizationsA final argument in favor of positional-only parameters is that they allow some
new optimizations like the ones already present in the argument clinic due to
the fact that parameters are expected to be passed in strict order. For example, CPython’s
internal SpecificationSyntax and SemanticsFrom the “ten-thousand foot view”, eliding def name(positional_or_keyword_parameters, *, keyword_only_parameters): Building on that example, the new syntax for function definitions would look like: def name(positional_only_parameters, /, positional_or_keyword_parameters, *, keyword_only_parameters): The following would apply:
Therefore, the following would be valid function definitions: def name(p1, p2, /, p_or_kw, *, kw): def name(p1, p2=None, /, p_or_kw=None, *, kw): def name(p1, p2=None, /, *, kw): def name(p1, p2=None, /): def name(p1, p2, /, p_or_kw): def name(p1, p2, /): Just like today, the following would be valid function definitions: def name(p_or_kw, *, kw): def name(*, kw): While the following would be invalid: def name(p1, p2=None, /, p_or_kw, *, kw): def name(p1=None, p2, /, p_or_kw=None, *, kw): def name(p1=None, p2, /): Full Grammar SpecificationA simplified view of the proposed grammar specification is: typedargslist: tfpdef ['=' test] (',' tfpdef ['=' test])* ',' '/' [',' # and so on varargslist: vfpdef ['=' test] (',' vfpdef ['=' test])* ',' '/' [',' # and so on Based on the reference implementation in this PEP, the new rule for
typedargslist: (tfpdef ['=' test] (',' tfpdef ['=' test])* ',' '/' [',' [tfpdef ['=' test] (',' tfpdef ['=' test])* [',' [ '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]] | '**' tfpdef [',']]] | '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]] | '**' tfpdef [',']] ] )| ( tfpdef ['=' test] (',' tfpdef ['=' test])* [',' [ '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]] | '**' tfpdef [',']]] | '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]] | '**' tfpdef [',']) and for varargslist: vfpdef ['=' test ](',' vfpdef ['=' test])* ',' '/' [',' [ (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [ '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]] | '**' vfpdef [',']]] | '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]] | '**' vfpdef [',']) ]] | (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [ '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]] | '**' vfpdef [',']]] | '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]] | '**' vfpdef [','] ) Semantic Corner CaseThe following is an interesting corollary of the specification. Consider this function definition: def foo(name, **kwds): return 'name' in kwds There is no possible call that will make it return >>> foo(1, **{'name': 2}) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: foo() got multiple values for argument 'name' >>> But using def foo(name, /, **kwds): return 'name' in kwds Now the above call will return In other words, the names of positional-only parameters can be used in
Origin of “/” as a SeparatorUsing
How To Teach ThisIntroducing a dedicated syntax to mark positional-only parameters is closely analogous to existing keyword-only arguments. Teaching these concepts together may simplify how to teach the possible function definitions a user may encounter or design. This PEP recommends adding a new subsection to the Python documentation, in the section “More on Defining Functions”, where the rest of the argument types are discussed. The following paragraphs serve as a draft for these additions. They will introduce the notation for both positional-only and keyword-only parameters. It is not intended to be exhaustive, nor should it be considered the final version to be incorporated into the documentation. By default, arguments may be passed to a Python function either by position or explicitly by keyword. For readability and performance, it makes sense to restrict the way arguments can be passed so that a developer need only look at the function definition to determine if items are passed by position, by position or keyword, or by keyword. A function definition may look like: def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2): ----------- ---------- ---------- | | | | Positional or keyword | | - Keyword only -- Positional only where Positional-or-Keyword ArgumentsIf Positional-Only ParametersLooking at this in a bit more detail, it is possible to mark certain parameters
as positional-only. If positional-only, the parameters’ order matters, and
the parameters cannot be passed by keyword. Positional-only parameters would
be placed before a Parameters following the Keyword-Only ArgumentsTo mark parameters as keyword-only, indicating the parameters must be passed
by keyword argument, place an Function ExamplesConsider the following example function definitions paying close attention to the
markers >>> def standard_arg(arg): ... print(arg) ... >>> def pos_only_arg(arg, /): ... print(arg) ... >>> def kwd_only_arg(*, arg): ... print(arg) ... >>> def combined_example(pos_only, /, standard, *, kwd_only): ... print(pos_only, standard, kwd_only) The first function definition >>> standard_arg(2) 2 >>> standard_arg(arg=2) 2 The second function >>> pos_only_arg(1) 1 >>> pos_only_arg(arg=1) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: pos_only_arg() got an unexpected keyword argument 'arg' The third function >>> kwd_only_arg(3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given >>> kwd_only_arg(arg=3) 3 And the last uses all three calling conventions in the same function definition: >>> combined_example(1, 2, 3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: combined_example() takes 2 positional arguments but 3 were given >>> combined_example(1, 2, kwd_only=3) 1 2 3 >>> combined_example(1, standard=2, kwd_only=3) 1 2 3 >>> combined_example(pos_only=1, standard=2, kwd_only=3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: combined_example() got an unexpected keyword argument 'pos_only' RecapThe use case will determine which parameters to use in the function definition: def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2): As guidance:
Reference ImplementationAn initial implementation that passes the CPython test suite is available for evaluation [10]. The benefits of this implementations are speed of handling positional-only parameters, consistency with the implementation of keyword-only parameters (PEP 3102), and a simpler implementation of all the tools and modules that would be impacted by this change. Rejected IdeasDo NothingAlways an option — the status quo. While this was considered, the aforementioned benefits are worth the addition to the language. DecoratorsIt has been suggested on python-ideas [9] to provide a decorator written in Python for this feature. This approach has the benefit of not polluting function definition with additional syntax. However, we have decided to reject this idea because:
Per-Argument MarkerA per-argument marker is another language-intrinsic option. The approach adds a token to each of the parameters to indicate they are positional-only and requires those parameters to be placed together. Example: def (.arg1, .arg2, arg3): Note the dot (i.e., It should be noted that some libraries already use leading underscore [12] to conventionally indicate parameters as positional-only. Using “__” as a Per-Argument MarkerSome libraries and applications (like
Group Positional-Only Parameters With ParenthesesTuple parameter unpacking is a Python 2 feature which allows the use of a tuple as a parameter in a function definition. It allows a sequence argument to be unpacked automatically. An example is: def fxn(a, (b, c), d): pass Tuple argument unpacking was removed in Python 3 (PEP 3113). There has been a proposition to reuse this syntax to implement positional-only parameters. We have rejected this syntax for indicating positional only parameters for several reasons:
After Separator ProposalMarking positional-parameters after the def (x, y, /, z): If we define that ThanksCredit for some of the content of this PEP is contained in Larry Hastings’s PEP 457. Credit for the use of Credit for discussion about the simplification of the grammar goes to Braulio Valdivieso. CopyrightThis document has been placed in the public domain. |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |