-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
PEP 692: Introduce proposed enhancements and expand Motivation section #2732
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
01c4ead
2b2973f
5e6d8ec
aef51e7
0452b55
cda313c
cf2d134
6a7442b
52d151b
a5b1144
ae2fcc3
257fa01
d8de2d5
d5367da
257ec59
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,9 +41,66 @@ type annotating ``**kwargs`` is not possible. This is especially a problem for | |
already existing codebases where the need of refactoring the code in order to | ||
introduce proper type annotations may be considered not worth the effort. This | ||
in turn prevents the project from getting all of the benefits that type hinting | ||
can provide. As a consequence, there has been a `lot of discussion <mypyIssue4441_>`__ | ||
around supporting more precise ``**kwargs`` typing and it became a | ||
feature that would be valuable for a large part of the Python community. | ||
can provide. | ||
|
||
Moreover, one pattern that ``**kwargs`` are especially useful for is when a | ||
function should accommodate optional keyword-only arguments that don't have | ||
default values. A need for a pattern like that can arise when values that are | ||
usually used as defaults to indicate no user input, such as ``None``, can be | ||
passed in by a user and should result in a valid, non-default behavior. | ||
|
||
As an example, let's consider a hypothetical ``HTTPClient`` class:: | ||
|
||
class HTTPClient: | ||
def __init__( | ||
self, | ||
*, | ||
auth: AuthType, | ||
timeout: TimeoutType, | ||
base_url: UrlType, | ||
# And some more arguments... | ||
): ... | ||
|
||
def request( | ||
self, | ||
*, | ||
auth, | ||
timeout | ||
# And some more arguments... | ||
): ... | ||
|
||
# And a bunch of other methods... | ||
10000 td> |
|
|
In this case, the default authorization and timeout strategies are passed in | ||
via the constructor. However, calls to the ``request`` method can override the | ||
defaults. For example, passing ``auth=None`` explicitly, could mean that no | ||
authorization strategy should be used at all, and the same could be true for | ||
``timeout``. In this situation there are no obvious default values for those | ||
parameters. | ||
|
||
This example is based on real world use cases taken from popular `httpx | ||
<httpxSentinel_>`__ and `urllib3 <urllib3Sentinel_>`__ libraries. Those | ||
libraries deal with this problem by introducing sentinel objects that are used | ||
as defaults. One issue of such a solution is that if the clients subclass a | ||
class that is part of a public API and want to override the ``request`` method, | ||
they may need to import that sentinel object, which usually should not be made | ||
public. Using ``**kwargs`` in this example would not have this problem and is | ||
arguably simpler. However, currently there is no way to properly type hint such | ||
a solution. | ||
|
||
In addition, ``**kwargs`` can be used to reduce the amount of code needed in | ||
cases when there is a top-level method that is a part of a public API and it | ||
calls a bunch of helper functions, all of which expect the same keyword | ||
arguments. Unfortunately, if those helper functions were to use ``**kwargs``, | ||
there is no way to properly type hint them if the keyword arguments they expect | ||
are of different types. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This strikes me as the main use case -- the need to avoid repetition when one function calls another with an identical signature. Maybe you could elaborate more on how frequent this pattern is? I've encountered it enough to think it's quite often -- e.g. I recall writing many wrappers for (The other legit use case is adding type annotations to a legacy code base that uses There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added the example you mentioned to the paragraph. Also, in the next paragraph I linked mypy issue 4441 and mentioned that it links to specific examples - on one hand feels sort of lazy but I think it is a nice way to show that there is indeed a real need for that proposal. |
||
|
||
As described in the :ref:`Intended Usage <pep-692-intended-usage>` section, | ||
using ``**kwargs`` is not always the best tool for the job, but still it is a | ||
widely used pattern. As a consequence, there has been a | ||
`lot of discussion <mypyIssue4441_>`__ around supporting more precise | ||
``**kwargs`` typing and it became a feature that would be valuable for a large | ||
part of the Python community. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This paragraph is kind of hanging here by itself. Maybe it needs to be made part of the Introduction instead? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved it so it appears earlier in the Motivation section - without a detailed example of "facilitating optional keyword arguments that don't have default values" I think it is a nice transition from the previous two paragraphs. |
||
|
||
Rationale | ||
========= | ||
|
@@ -345,6 +402,8 @@ to perform the function call:: | |
name = kwargs["name"] | ||
takes_name(name) | ||
|
||
.. _pep-692-intended-usage: | ||
|
||
Intended Usage | ||
-------------- | ||
|
||
|
@@ -368,6 +427,12 @@ Another useful pattern that justifies using and typing ``**kwargs`` as proposed | |
is when the function's API should allow for optional keyword arguments that | ||
don't have default values. | ||
|
||
Moreover, using ``**kwargs`` with more precise type hinting would allow for | ||
less code and less copy pasting in cases when a module contains a bunch of | ||
functions that expect similar keyword arguments. Using ``**kwargs`` with a | ||
``TypedDict`` as propsed in this PEP is much more concise than explicitly | ||
repeating the keyword arguments every time. | ||
|
||
However, it has to be pointed out that in some cases there are better tools | ||
for the job than using ``TypedDict`` to type ``**kwargs`` as proposed in this | ||
PEP. For example, when writing new code if all the keyword arguments are | ||
|
@@ -399,6 +464,9 @@ explicitly as:: | |
|
||
def foo(name: str, year: int): ... | ||
|
||
Also, for the benefit of IDEs and documentation pages, functions that are part | ||
of the public API should prefer explicit keyword arguments whenever possible. | ||
|
||
Grammar Changes | ||
=============== | ||
|
||
|
@@ -562,6 +630,8 @@ overloaded:: | |
References | ||
========== | ||
|
||
.. _httpxSentinel: https://github.com/encode/httpx/issues/1384 | ||
.. _urllib3Sentinel: https://github.com/urllib3/urllib3/issues/2252 | ||
.. _mypyIssue4441: https://github.com/python/mypy/issues/4441 | ||
.. _mypyPull10576: https://github.com/python/mypy/pull/10576 | ||
.. _mypyExtensionsPull22: https://github.com/python/mypy_extensions/pull/22/files | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this a particularly weak argument -- optional keyword args without defaults seems an obscure edge case, and marker objects usually work just fine. Even if there are real-world examples of this need (as you show below), I don't think it's worth nearly 50 lines of text in the PEP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For what its worth, as a random datapoint from a random Python user, I don't think I've ever seen this pattern in real life—I'm sure it happens, but "especially useful" seems like it might be overstating it and this might be more helpful nearer the end of the section, particularly considering the utility of the other two (I see them absolutely everywhere).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense, I deleted the detailed example and just linked the httpx issue. Also, moved the paragraphs around and changed the "especially useful" bit to "also convenient".