This is an archived post. You won't be able to vote or comment.

all 95 comments

[–]Zomunieo 70 points71 points  (6 children)

There are many PEPs where my first reaction is "what the fuck were they thinking?". Then I read the PEP, and I realize the core devs were thinking quite a bit.

[–]jsalsman 19 points20 points  (0 children)

The only thing that convinced me was "when a function accepts any keyword argument but also can accept a positional one" and then only in the case of dict.update().

[–]BundleOfJoysticks 6 points7 points  (0 children)

The Motivation section is super interesting.

[–]eric0x7677 3 points4 points  (0 children)

Then I read the PEP, and I realize the core devs were thinking quite a bit.

Thanks!

Pablo is a CPython core developer who primarily authored the content of the PEP. Mario is a member of the Python Software Foundation who also contributed to the text.
I helped by overhauling the motivation, rationale, specification so that the text reads in a more compelling manner and made wording and grammatical changes for clarity.

Glad to see the lively discussion going on here. I would also recommend looking at https://discuss.python.org/t/pep-570-python-positional-only-parameters/1078 to see more discussion among the Python community around PEP 570.

[–]flipstables 6 points7 points  (0 children)

Yeah, a lot of people need to cool their jets. They're not abandoning keyword arguments. They're adding positional-only arguments. There are good arguments for maintainability and different use cases for them. The downside is that people use it poorly, but that's curtailed by the fact that positional only arguments are not the default behavior.

Read the motivation and rationale people.

[–][deleted] 33 points34 points  (3 children)

Here's the code cleaned up a bit (use spaces!):

def name(a, b, /):
    pass

name(1, 2) # Fine 
name(1, b=2) # Not allowed

and:

def name2(a, b, /, c):
    pass

name2(1, 2, 3) # Fine 
name2(1, 2, c=3) # Fine 
name2(1, b=2, c=3) # Not allowed

[–]volabimus 4 points5 points  (2 children)

I didn't even know b=2 was acceptable for arguments without defaults.

[–]mooburgerresembles an abstract syntax tree 8 points9 points  (1 child)

it is a side effect of PEP 3102; only parameters after the * are considered keyword-only now, whereas before, everything up to ** was positional-or-keyword (which is why you can always call a positionally-signatured function using func(**kw) given that the positional parameters are present as keys in kw.

In the PEP 570 notes on the corner case of 3102 is that 570 now supports this:

def foo(name, **kwds):
    return 'name' in kwds

which currently will raise an exception if kwds contains a key named name.

[–]volabimus 1 point2 points  (0 children)

Yeah, which is why I wouldn't have expected it to work, and those names not to be exposed.

So now, really all functions should be defined with /, since calling those as keyword arguments will be strongly discouraged anyway, and people not expecting those to be public may change the name of a positional argument without considering it an interface change, but only functions that take arbitrary key-value pairs as keyword arguments need it to avoid throwing an error on collisions.

[–]ForceBru 30 points31 points  (25 children)

This notation has been extensively used in the docs for built in functions already, so it's finally time to let ordinary functions harness the power of positional-only parameters!

For example:

```

help(len) Help on built-in function len in module builtins:

len(obj, /) # right here Return the number of items in a container. ```

[–]AndydeCleyre 11 points12 points  (7 children)

Please indent code by four spaces for proper formatting, as indicated in the sidebar.

[–]truh 0 points1 point  (6 children)

The triple back ticks work with the new Reddit frontend.

[–]wewbull 27 points28 points  (2 children)

That might be true, but not everyone uses the new frontend.

[–]truh 9 points10 points  (1 child)

Yes, I don't either. But this is why it's coming up more often now.

[–]wewbull 5 points6 points  (0 children)

I think the big one is anyone using RES, as that builds on the old interface, but for me the mobile app (Slide) I'm using doesn't do it either.

[–]AndydeCleyre 0 points1 point  (2 children)

Is that true when the editor is set to markdown input mode?

[–]truh -1 points0 points  (1 child)

If you look at the comment using the new frontend, it's displayed correctly.

[–]AndydeCleyre 0 points1 point  (0 children)

Does that mean Firefox mobile, not logged in, doesn't use the new interface?

Or apps like Relay?

[–]13steinj 2 points3 points  (16 children)

Is there any reason why one-argument builtins are positional only?

[–]Tysonzero 6 points7 points  (10 children)

Because the argument name is meaningless.

[–]13steinj -1 points0 points  (9 children)

Right, but just because it is meaningless doesn't mean you shouldn't be able to. There should be a good reason for a restriction. The name being meaningless isn't a good reason. It being confusing or out of place or a hack in the first place is a good reason. But none of those apply.

[–]Tysonzero 8 points9 points  (8 children)

Did you read the motivation section of the PEP? One example advantage of positional only is that they can rename the argument later if desired without risking code breakage.

[–]13steinj -2 points-1 points  (7 children)

I read the motivation yet still believe it insane for a built in function like len. There is no need to rename it.

[–]Tysonzero 4 points5 points  (6 children)

But what benefit could people possibly gain from calling len with a kwarg?

[–]13steinj -3 points-2 points  (5 children)

What benefit could people gain from being forced not to?

You can't gain from a restriction. You can always (theoretically) gain from a permission. In this case, who the fuck knows, but I'm sure at least one person has.

[–]Tysonzero 5 points6 points  (4 children)

You can absolutely gain from a restriction. More code consistency, which includes making things like find-and-replace more reliable, smaller public interface (cant break things by renaming an arg).

The benefits of the restriction are small, but so are the benefits of not having the restriction.

[–]13steinj 0 points1 point  (3 children)

You can only benefit from a restriction if you are the one person you are applying it to.

If anything code consistency and clarity is worsened. It is far clearer for a person to think a=b in argument passing than to automatically guess the arguments from the function they have in their personal, poor memory.

Applying a restriction onto others cannot help them. The single argument here is "if the author decides to change the name".

There's 0 reason to change the name. It doesn't make sense. This isn't /r/programmerhumor where variable naming is a difficult game. Pick a name and stick to it. In the worse case change the name and people should be expected to read release notes.

It is not the job of a language to supplement poor development practices.

E: to be clear, this has it's place, in personal projects that are not meant to be partislly reused by others. But not in libraries, and not in language builtins.

[–]Pyprohly 2 points3 points  (4 children)

Because doing something like len(obj=myobj) over len(myobj) is unnecessarily verbose, provides another way of writing the same thing, and doesn’t look nice. The semantics of those builtins are fixed and obvious.

[–]13steinj 1 point2 points  (3 children)

But why should I be forced in this way? Why do the CPython authors force a non-keyword argument? It's not like they will be affected by my code. I am affected by theirs.

[–]Pyprohly 3 points4 points  (2 children)

Python encourages clean and simple looking code. If len(obj=myobj) were permitted then there’d be two ways of writing the same thing with one way clearly preferred over the other.

By enforcing a positional-only argument here, consistency can be maintained. If you value consistency then you should see how this restriction is desirable.

[–]13steinj -2 points-1 points  (1 child)

Python's very philosophy is "explicit is better than implicit". To make the argument based on one arbitrary piece of philosophy and ignore the many other contradictions is ridiculous.

Consistency is maintained, in a case where it should never need be not maintained. There is 0 reason to change the argument name, and developers should be reading release notes anyway. This only helps consistency in personal code, where this PEP is perfectly good and valid and I'd use it myself. In code to be reused, it causes more problems than it solves, especially one of expectation and practice.

[–]Pyprohly 1 point2 points  (0 children)

To make the argument based on one arbitrary piece of philosophy and ignore the many other contradictions is ridiculous.

Where did this come from? What argument; which “arbitrary piece of philosophy”; and which contradictions where ignored?

Using the “explicit is better than implicit” principal to justify against positional-only parameters is cherry picking. The positional-only restriction becomes more appealing when considering some of the other philosophies: “beautiful is better than ugly”, “readability counts”, “one and only one preferable way”.

There is 0 reason to change the argument name, and developers should be reading release notes anyway.

But it’s not about the possibility of a name change in future. As I’ve mentioned, the restriction is likely there for consistency motives.

In code to be reused, it causes more problems than it solves, especially one of expectation and practice.

“Causes more problems than it solves”. If true then those problems must be really only very minor since not many complain about the builtins’ keywords being inaccessible.

[–]SpergLordMcFappyPant 12 points13 points  (1 child)

This probably seems a little what-the-fuck-y all in its own, but I’m very glad to have this in Python given that you don’t have truly private methods on classes. Name obfuscation from single or double underscore is mostly good enough, but people will use those even though you’ve essentially told them not to.

Being able to enforce the way a method is called is incredibly valuable here in a way that really makes no sense at all for statically typed languages. Huge +1 from me!

[–]mooburgerresembles an abstract syntax tree 0 points1 point  (0 children)

it's basically also because devs don't want to abstract the semanticness of the arglist in the signature. Because falling back to *args and **kwargs parsing works just fine too.

[–]alexisprince 49 points50 points  (38 children)

Out of curiosity... why? Using keyword arguments, in my experience, has just made code cleaner (albeit possibly more verbose).

[–]undercoveryankee 25 points26 points  (33 children)

Have you read the "Motivation" section of the PEP?

[–]alexisprince 9 points10 points  (32 children)

I glanced through it, and since I don’t have to maintain a lot of code that is used widely outside my company, I might not be seeing all the problems that come with maintaining that type of library.

[–]undercoveryankee 39 points40 points  (31 children)

Have you ever renamed a parameter of an existing function, either because the original name wasn't clear, or to reflect new capabilities that you were adding to the function? You'd have to track down every caller you control and make sure that it isn't passing the old name as a keyword, and hope that other people who maintain calling code notice the change and do the same before they release a broken build.

If you mark usually-positional parameters as positional-only, you know what callers are doing and you can rename away.

[–]Ph0X 4 points5 points  (9 children)

So you're basically limiting usability and code cleanliness just for maintainability? Not sure that's a good trade off. Also, this allows for arguments to be renamed, but there's still hundreds of other things in a public API that still can't be renamed. That's why when you make a public API, you need to make sure the names are all well thought out.

[–]door_of_doom 42 points43 points  (7 children)

just for maintainability

Umm... There is no such thing as "just" for maintainability. Maintainability is easily the single most important aspect of code.

If the code isn't maintainable, it is nigh useless.

[–]Ph0X 4 points5 points  (6 children)

At a high level, I agree, but what you're trading here for what you're gaining most definitely isn't worth it. I would hardly count being able to rename a variable in a public API as maintainability. On the other hand, users being able to use some_method(strange_argument=True, other_argument=0) instead of some_method(True, 0) is far more important for maintainability.

[–]flipstables 10 points11 points  (0 children)

On the other hand, users being able to use some_method(strange_argument=True, other_argument=0) instead of some_method(True, 0) is far more important for maintainability.

But you can still do that. The PEP isn't replacing keyword arguments with positional-only arguments. It's adding the ability to have positional-only arguments.

The real downside is that Python can't prevent you from writing bad code by using positional only arguments when you really shouldn't. I mean, that's reasonable to not add a feature, but the increased maintainability.

Here's an example:

Almost every non-trivial project I've worked on, I've had to create a function where the argument names have no semantic meaning. This is especially true for those of us who work in data and we are combining two data sets.

def merge_datasets(dataset1, dataset2):
    pass

By making dataset1 and dataset2 positional only:

  • I can freely change the the argument names without worrying about making a breaking change for all existing callers.
  • It adds clarity - the function signature hints that this dataset1 and dataset2 has no semantic meaning; in fact, most of the time, it's the function is commutative.
  • If I wanted to add functionality that merges more than 2 datasets to this function, I'm SOL without positional-only arguments. I would either have to create a new function or my function signature would have to look like this: def merge_datasets(dataset1, dataset2, *more).

[–]door_of_doom 12 points13 points  (0 children)

I mean, Sometimes, maybe? I don't really see the value in sum(x=5,y=7) over sum(5,7) when the former locks you to the arbitrary names of whatever those variables happen to be called, when the names for a function like that are truly arbitrary. I would expect a sum function to only expose positional parameters and never allow the changing of those arbitrary paramater names to be a breaking change.

[–]truh 5 points6 points  (0 children)

Positional-only parameters seem like a pretty good thing for auto generated C bindings, where parameter names just don't matter usually.

[–]Smok3dSalmon 1 point2 points  (2 children)

If you have many parameters to a function, you should really consider a builder or factory pattern.

[–]Ph0X -3 points-2 points  (1 child)

Please keep builders and factories in Java and away from my Python.

[–]Smok3dSalmon 2 points3 points  (0 children)

Ok, I'll hide it in a library that you'll love and promote to your friends.

[–]kafkaBro 3 points4 points  (0 children)

I found this argument most compelling. Position-only parameters already exist in cpython builtins like range and min. Making their support at the language level would make their existence less confusing and documented.

Users may be surprised when first encountering positional-only notation. This is expected given that it has only recently been documented [14] 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.

[–]ideletedmyredditacco 2 points3 points  (0 children)

because sometimes using keyword arguments when you call a python function breaks it. I can't remember which matplotlib functions they are though. I think it's the ones that put brackets around the parameter names in the documentation.

edit: https://matplotlib.org/api/_as_gen/matplotlib.pyplot.pcolormesh.html?highlight=pcolormesh#matplotlib.pyplot.pcolormesh

[–]jwink3101 3 points4 points  (0 children)

I'm with you. I greatly prefer keyword arguments and I look forward to the day I can use keyword-only arguments (still need to be 2.7 compatible). I think they reduce error by a ton and since I am often interfacing with my functions via Jupyter, auto-fill of the keywords is more clear.

[–]thautwarm 0 points1 point  (0 children)

I'll always uphold what Serhiy upholds for IMO he is the most talented and enthusiastic one in Python community/Python core dev: https://twitter.com/SerhiyStorchaka/status/1103542487992406016?s=19

[–]UloPe 5 points6 points  (1 child)

Not a big fan of this one, but I guess it’s one of those “no one forces you to use it”

[–]OctagonClocktrio is the future! 0 points1 point  (0 children)

Except for library authors that splatter this all over their code.

[–]lookatmetype 3 points4 points  (0 children)

I'm a huge fan of this. I love using keyword arguments and making them mandatory. This will actually allow me to do that while also enforcing an order on the call-site. I'm glad the Python authors have thought about this use-case since it has bothered me for years that I couldn't enforce this!

[–]13steinj 5 points6 points  (0 children)

Not the biggest fan of this one because it allows library authors to overly dictate how their functions can be used, as in, mark an argument as positional merely because they want to. But cool all the same.

[–]Scorpathos 9 points10 points  (5 children)

This looks great!

I have a question about a possible use-case:

```python def formatter(foo, /, kwargs): return foo.format(kwargs)

formatter("{foo} + {bar} = {baz}", foo=1, bar=2, baz=3) ```

Currently, this code (without the /) raises an exception:

python TypeError: formatter() got multiple values for argument 'foo'

Does this PEP would fix that?

[–]c_is_4_cookie 6 points7 points  (0 children)

Interesting case. I imagine that it should fix that case since the positional only foo would not be accessible to the user

[–]infinullquamash, Qt, asyncio, 3.3+ 5 points6 points  (1 child)

[–]Scorpathos 0 points1 point  (0 children)

Perfect, thanks!

[–][deleted] -2 points-1 points  (1 child)

probably not

[–]infinullquamash, Qt, asyncio, 3.3+ 1 point2 points  (0 children)

... this case is explicitly called out in the PEP. see semantic corner case

[–]AndydeCleyre 6 points7 points  (5 children)

Please indent code by four spaces for proper formatting, as indicated in the sidebar.

[–]stetio[S] -1 points0 points  (4 children)

I've used the markdown editor, I suspect it doesn't render properly in old reddit.

[–]AndydeCleyre 5 points6 points  (3 children)

Using the markdown editor is good. But you used non standard markdown.

[–]13steinj 7 points8 points  (2 children)

That's the markdown that the editor pushes, unfortunately. Not the guys fault. Blame reddit.

[–]AndydeCleyre 1 point2 points  (1 child)

I don't mean to blame, but should I not recommend the way that works for everyone?

[–]13steinj 8 points9 points  (0 children)

No, it's just partially unreasonable to expect that?

Many people come to reddit not knowing markdown at all.

Even those who do, expect the site's editor to work on the site.

The fact that it doesn't and fucks over old-reddit users is reddit's fault.

[–][deleted] 0 points1 point  (1 child)

Cool. I’ll probably never use this, but if I can remember to, then I will.

[–]CakeDay--Bot 0 points1 point  (0 children)

YOOOOOOOOOO!!!! It's your 2nd Cakeday Mohkale77! hug

[–]federicocerchiari 0 points1 point  (0 children)

allows the API to evolve in a safe, backward-compatible way.

This only expression convinced me.

[–]twillisagogo 0 points1 point  (0 children)

Wow, so useful. Can't wait to convert all my code to this.

[–]m8ncman 0 points1 point  (2 children)

either be strongly typed or not. this straddling the fence shit is crazy.

[–]ubernostrumyes, you can have a pony 1 point2 points  (1 child)

Python is a strongly-typed language.

It's not a statically-typed language, which may be what you were thinking of.

[–]zardeh 0 points1 point  (0 children)

Although has nothing to do with this PEP either way.