Ok re: my IDE thing: The current behavior in my IDE (pycharm) is that if I have
foo(x=x)
foo(x=x,)
If I put my cursor anywhere after the = (but before the closing , or )) and hit the jump-to-declaration hotkey it jumps to the definition of the variable x. This behavior can be preserved after PEP 736.
One upside that I donât see explicitly mentioned: This will be really nice for those rare cases where parameters (and the variables that will be passed into them) warrant long names to be sufficiently descriptive. Currently if you have really long parameter + variable names you run the risk of exceeding the project line length limit. Of course this depends a little bit on the line limit chosen and how choices are made about multi-function call whitespace.
Some people might see this as a downside if it makes people more comfortable with overly long names.
Instead of debating the semantics of âimplicitâ, which itself is an appeal to Zen, how about answering the question âis this level of abstraction a boon or a blightâ and leave Zen out of it?
It seems established that the best case for 736 is client code calling an api with named args (where the inputs are true locals and not named args from the containing function), rather than the âpassthroughâ args in e.g numpy as argued in the PEP. Kwargs feels a more appropriate fit for the latter (itâs clearly more maintainable when adding new args to deeply-nested calls, as are in numpy, matplotlib, etc). To be a little sassy, Zen also says âNamespaces are one honking great ideaâ!
I chose _ for the âget me the dictionaryâ property to avoid clashing with any attributes you want to add. But you can make your own variation. For example, by defining __call__ you could have n() give you the kwargs dictionary.
Of course, this isnât in the stdlib - but conversely, something that was in the stdlib would need to be over-engineered to cover every use case, whereas something you write yourself can literally be just a few lines.
As has been said before, not every 3-line function (or class) needs to be in the stdlib.
Having said all of this, itâs very much a digression from the question of whether PEP 736 is a good idea. I still dislike it for all the reasons Iâve already stated, even though I donât think âlook, you can rewrite things using **kwargsâ is a convincing argument against the proposalâŚ
func(**vars(ns)) is more pleasant. I also tossed this out, which is effectively the same, but for some reason the SimpleNamespace folks donât really like it
We are only debating what âimplicitâ means since that is the stick most often used to beat PEP 736. Some commenters are saying âI do not like PEP 736 because the variable name is implicitâ. This has been addressed in the PEP, but apparently not to the full satisfaction of everyone. And hence we try to clarify this point. For example, what I was trying to point out is that the variable name is explicitly the same as the keyword name, and hence not implicit at all.
If you leave the Zen out of it, which I think you should, then what exactly is the criticism? âIs this level of abstraction [âŚ]â does not make sense to me, because there is no abstraction here in the first place. PEP 736 merely proposes a short-hand notation where f(x=) means the same as f(x=x), which is about as concrete as anything can be.
That was regarding Chrisâs sentiment that implicit roughly means abstraction, and itâs abstractions all the way down (which is not a bad thing). I was trying to choose less binary wording.
Restating my reservation b.c. megathread, I worry that 736 pulls hard towards named arguments (by design), and so it also pulls away from kwargs, which appears to me to be the âbetterâ design in many cases. I like PEP 692âs approach for allowing âniceâ function signatures including kwargs, but 736 and 692 are mutually exclusive.
Restating my earlier example (which is real code I wrote that day), consider that I am plotting something with matplotlib, and this is the code that I am writing currently in my script or Jupyter notebook:
The syntax couples the name as used in the function signature to the name of a local variable in the caller in a way that canât be controlled by the user (in the sense that if you use this construct the names must match). I understand thatâs by design, but itâs a design that assumes that ânot having to write the name twiceâ is more important than âbeing able to use a local name of your choiceâ. I think thatâs a bad trade-off and the more general existing construct is sufficient[1]
To put it another way, the proposalâs claimed benefit is that you can omit the argument when itâs âobviousâ what it is. I disagree that omitting the argument is a sufficient benefit, and I also donât think that the construct will be common enough that a small benefit will scale sufficiently to be an overall improvement.
Also, the transition if you rename a local variable is unnatural and error-prone. Rather than simply renaming the variable in all the places it occurs, you have to also add the variable in a place where itâs currently omitted (and locating those places, while not hard, is not trivial either).
sorry if this is worded a bit badly, I was struggling to avoid using the word âexplicitâ so that I wouldnât get accused of invoking the Zen⌠âŠď¸
Where are those variables defined? Here are my arguments depending on the answer:
If theyâre locals used once, why not pass the literals directly (save seven lines)?
If theyâre locals used multiple times, why not use a dict and ** (just pass the one namespace to multiple functions, not seven named args to each)? Even if they require processing (they arenât simply holding literals), putting them in a namespace and referring to them as attributes isnât exactly bending over backwards IMO.
If theyâre arguments to a containing function you wrote, why not refactor as **kwargs and use PEP 692 to declare which names are expected by kwargs (if youâre mindful of developer quality of life, IDE suggestions, etc, but if not then donât)? Using kwargs lets you pass-through additional arguments without changes (seems like a significant plus to me, and this is the pattern I use all the time with windowing/gui/plotting libraries that require a lot of config).
If theyâre arguments to a containing function where you have no control over the signature, then, sure use 736, but this feels like a âbandaidâ just because **kwargs was excluded and the signature was set in stone. Chances are the function was already written, so not worth the âdrive-byâ refactor to use 736. This alone dosenât seem worth the risk of providing incentives away from kwargs when kwargs seems to be the âbestâ solution in the other cases.
Restating my issue with this example (and others like it), this feels like bad API design in matplotlib. Itâs understandable (designs like this can âjust growâ over the lifetime of a long-running and popular project, and redesigning them can be impossible for compatibility reasons) but Iâd be happier if the proposal was motivated by APIs that were more clearly âgood practiceâ.
Basically, a languageâs capabilities influence the design of APIs in the language (look at the popularity of âfluentâ APIs in Javascript for an example of this) and itâs not clear to me that this proposal is going to encourage better API design[1]. In fact, it feels to me that itâs more likely to encourage APIs with lots of generically named arguments, which is exactly the sort of design Iâd argue against.
You can argue forever over what a âgood APIâ looks like, and I want to avoid using the term âPythonicâ to simply mean âwhatever I likeâ. But IMO it is possible, if you take an unbiased look, to distinguish APIs that fit ânaturallyâ in Python from ones that feel like they are awkward, or imported from other languages. Look at the stdlib logging and unittest APIs for a examples of designs that are clearly imported unchanged from Java⌠âŠď¸
Iâll defend matplotlib because it uses **kwargs, and specifically NOT a wall of named arguments. My only criticism is that itâs not developer-friendly because the expected kwargs are buried in documentation, but PEP 692 solves that problem.
I think that problem of âcouplingâ has been voiced a few times, and I agree this is fair criticism. The PEP should reflect it carefully. I would respond that we see in the real world that often (and people have quantified how often) the name of a local variable is the same as the keyword of a function, purely because that is a good name for the thing at hand. This happens today, without PEP 736. And in that case, where it already makes sense for a local variable to have the same name as a function parameter, I would consider PEP 736 to exploit such âconvergenceâ, instead of enforcing any sort of âcouplingâ.
Again, I take the point that people may start calling local variables the same as function parameters just to apply PEP 736, even when that does not make sense. To which I can only say: maybe donât do that? I would certainly flag it in a code review, same as today.
Iâm not sure this syntax change will actually achieve what it sets out to do. Assuming it makes it into 3.13, only leaf packages which target 3.13 and higher will be able to use the new syntax (as older versions will straight-up reject the code, as it canât effectively be put behind a conditional). Packages which for now need to maintain support for older versions (see e.g. NEP 29 â Recommend Python and NumPy version support as a community policy standard â NumPy Enhancement Proposals, which has a 2 year/42 months support cycle) wonât be able to adopt it, nor any of their dependencies (and so on down the dependency tree). This change also targets the caller, not the callee, when the move to using keyword arguments needs to be done on the callee side (as if the callee does not expect users to use keyword arguments, then they may change the name used in the function definition, causing the caller code to break).
Perhaps a better way (assuming the intent of this PEP was for encouraging the use of keyword arguments) would be to introduce a helper decorator which allowed callees to raise a warning about a future switch to requiring keyword arguments, and which was written in such a way that the various linters and static analysis tools could easily detect and warn their users (and perform fixups if thatâs a feature they had). This would require no syntax changes, so would keep working on older versions of Python (meaning it could be adopted by trunk projects much faster).
By default, parameters are positional-or-keyword. So the caller CAN switch to keyword arguments without any change on the libraryâs end. Since this has always been possible, renaming arguments in this way would already cause potential breakage, so I donât see how this proposal makes this any worse.
Completely independent of my thoughts on this proposal, this is a weird statement. By design, and the stated goal of this PEP, this encourages the use of keyword arguments, meaning the goal is for more people to use them. This means (if the PEP succeeds) name coupling becomes a larger âproblemâ and renaming parameters is more likely to cause problems.
It also additional means that adopting to parameter name changes is a slightly larger patch, although I donât find that all that relevant to consider.
Well, yes. This is a change which can not pay off for the ecosystem till ~3.18. Thats how PEPs, especially syntax changes, work. They are lomg term goals. Short term, something like the decorator you suggest might work, but even that has a timescale of ~2-3 years I would guess.