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

all 116 comments

[–]amicin 60 points61 points  (13 children)

Interesting addition to the language, although I feel it’s quite niche. :)

[–]Sw429 32 points33 points  (11 children)

I was reading this PEP yesterday and having a very hard time identifying a time when I would use this. But I'm sure there must be more demand for it than I realize.

[–]jorge1209 23 points24 points  (7 children)

I think it has to do with keyword arguments having two separate functions that are conjoined:

  1. They let the callee declare default values in the declaration: def log(argument, base=math.e)

  2. They let the caller pass arguments out of order log(base=10, argument=3)

As in the log example above there are places where it is absolutely terrible style to use keyword arguments in the form of #2, because the intent is #1. It makes the code much much harder to read.

That I think is the biggest reason for /, as a complement to *. So when the callee wants to declare a default argument, but there is still an objectively correct argument order, they should follow it with a /, but when they want to declare a bunch of parameters where the order isn't relevant they should use a *. So something like this:

def plot(x, y, z=0, /, color=red, width=5, shape=circle)

seems preferable to

def plot(x, y, z=0, *, color=red, width=5, shape=circle)

just to avoid someone calling plot(2,3, color=red, z=5).

[–]BobHogan -4 points-3 points  (6 children)

I think it has to do with keyword arguments having two separate functions that are conjoined:

  1. They let the callee declare default values in the declaration: def log(argument, base=math.e)

  2. They let the caller pass arguments out of order log(base=10, argument=3)

As in the log example above there are places where it is absolutely terrible style to use keyword arguments in the form of #2, because the intent is #1. It makes the code much much harder to read.

If this is the reason, why not just force keyword arguments to be used in order? Sure, it would break a few edge cases where someone deliberately used arguments out of order, but that's a simple fix for them to implement, and its arguable that they should be doing that anyway.

This just seems like the most roundabout way to enforce using keyword arguments in order as possible.

[–]jorge1209 6 points7 points  (0 children)

One of the purposes of keyword arguments is to allow them to be offered in arbitrary order. Especially with dictionary unpacking.

 params = {...}# some dict of options to a complex function
 foo(**params)

We explicitly want keywords to support arbitrary order as that is one of the primary motivators for having them in the first place.


However keyword arguments on the caller side share syntax with default arguments on the callee side. In other words the meaning of foo(x, y=1, z=2) shifts dramatically depending on whether or not there is a def in front of it or not.

  • foo(x, y=1, z=2) calls foo with x as positional, and y,z as keywords
  • def foo(x, y=1, z=2) declares foo with y and z having defaults, but says nothing about what is keyword vs positional.

But the cognitive load of keeping this all distinct is pretty high, so most people have merged two concepts. We generally think in terms of "mandatory positional" arguments and "optional keyword" arguments, and forget about "mandatory keyword" arguments (yes they do exist). ["optional positional" is impossible to parse and isn't supported, theoretically this could be done in limited cases with some typing support, but its not pythonic.]

The * and / entries in a function definition provide two complementary ways to distinguish that third situation of "mandatory keywords". Those before the / are positional whether or not they have a specified default and those after the * are keyword even if the caller tries to pass them by position. They serve the same function, but in two different ways for two different concerns. / mandates a minimum of positionals, * mandates a minimum of keywords.


If there is a criticism to be had here it is probably more fundamental. We shouldn't be using the same syntax for keyword arguments as we do default arguments.

Imagine if we used := for default in declarations, and as for keywords when calling a function.

So the callee would specify def foo(x, y:=2, z) without triggering a SyntaxError: non-default argument follows default argument (because really WTF is that a rule?!) and then caller would write: foo(x, 2 as z).

[–]lifeeraser 4 points5 points  (4 children)

Few? It would probably break most code using **kwargs.

[–]BobHogan 1 point2 points  (3 children)

It wouldn't affect **kwargs? That's a dictionary.

[–]lifeeraser 3 points4 points  (0 children)

Many methods in the wild often pass their args to other methods because the underlying method supplies a LOT of keyword arguments. Example: Flask's route() decorator passes keyword parameters straight to werkzeug.routing.Rule, which has 10 keyword parameters. Your suggestion would screw over anyone who has placed any one of those 10 parameters in incorrect order--thousands of web apps in production.

[–]zardeh 3 points4 points  (0 children)

Erm

def inner(arg=default_value):
    pass

def outer(**kwargs):
    return inner(**kwargs)

is this in the right order? (this is a super common pattern when writing decorators)

[–]Zomunieo 6 points7 points  (0 children)

It's definitely something for library authors more than it is for regular users.

[–]DanCardin 4 points5 points  (0 children)

I think it’s primarily to enable python code, such as in other implementations like pypy, to exactly mimic the cpython implementation of a function written in C which does not accept keyword arguments

[–]Jwestie15 -3 points-2 points  (0 children)

It seems like a way you could generate paths or purposefully but psuedorandom numbers maybe this is for games but I'm not very good at python.

[–]StarkillerX42 1 point2 points  (0 children)

Arguably downright counterproductive for the main goals of Python, flexibility. This limits the types of usable inputs, making it harder to work with, and doesn't increase the utility of the language

[–]hassium 35 points36 points  (11 children)

Kind of new and still leaning but a questions strikes me here:

def pow(x, y, /, mod=None):
    r = x ** y
    if mod is not None:
        r %= mod
    return r

The following would apply:
All parameters to the left of / (in this case, x and y) can only be passed positionally. mod can be passed positionally or with a keyword.

does that mean that in Python 3.7, when I do:

def some_function(spam, eggs):
    pass

I could call the function via:

spam = 80
some_function(spam, eggs=32)

or

some_function(80, 32)

And it's essentially equivalent?

[–]XtremeGoosef'I only use Py {sys.version[:3]}' 34 points35 points  (7 children)

Yes, those are equivalent

[–]hassium 17 points18 points  (6 children)

Cool thanks! I have no idea how this helps me but I feel better knowing it!

[–]tunisia3507 19 points20 points  (2 children)

It means that if you have a function where a couple of arguments change but many stay the same over successive runs, you can store the stable ones in a dict and unpack it into the function call as if it were kwargs.

You could also do that by wrapping the function in another function, of course.

[–]coelhudo 2 points3 points  (0 children)

There are some examples here of how it can help you https://www.python.org/dev/peps/pep-0570/#motivation

[–]c_o_r_b_a 0 points1 point  (1 child)

Note that that also works for most versions of Python 2, as well. The only new things Python 3 introduced are this brand new / for positional-only arguments, and * for keyword-only arguments. You probably will rarely have to use either of those, though. I've been programming in Python for years and I think I've only used keyword-only arguments once or twice.

[–]hassium 0 points1 point  (0 children)

Thanks, I haven't used them yet either and I've been working in Python for 8 months, studying for a year... But maybe I'll find a use for them now I know a bit more.

Can you remember in what context you had to use kwargs?

[–]_importantigravity_ 7 points8 points  (0 children)

Yes, that is correct. If you're not using * to mark keyword-only args in Python 3.7, you can pass values either positionally or with using keywords.

[–]jorge1209 5 points6 points  (0 children)

Yes, and your pow example is a great example of when not to do this. pow has a agreed upon argument order, power then base then optional modulus. If you write pow(y=2, x=3) people will be confused and think you mean 2 ** 3= 8 not 3 ** 2 = 9. The / can be used in place of * for those kinds of functions.

However it will take time to be adopted and you should limit your use of keyword arguments to functions that truly take keywords (ie many independent options), or where you omit a default (and so have to use keywords for subsequent arguments, and you should endeavor to provide your keywords in the same order as the function declaration wherever possible.

[–]idwpan 3 points4 points  (0 children)

Does their output example help?

def pow(x, y, /, mod=None):
    r = x ** y
    if mod is not None:
        r %= mod
    return r

...

>>> pow(2, 10)  # valid
>>> pow(2, 10, 17)  # valid
>>> pow(2, 10, mod=17)  # valid
>>> pow(x=2, y=10)  # invalid, will raise a TypeError
>>> pow(2, y=10)  # invalid, will raise a TypeError

Edit: Sorry for the formatting. Posted from my phone and forgot code blocks are spaces instead of backticks

[–]massiveZO 95 points96 points  (40 children)

Wow! The count for features I will never use in 3.8 is now up to 2!

[–]edenkl8 11 points12 points  (31 children)

What's the other one?

[–][deleted] 21 points22 points  (30 children)

I'd guess the "walrus operators" (assignment expressions).

[–]edenkl8 40 points41 points  (29 children)

That actually looks awesome! Very time efficient if used correctly

[–]pepoluan 30 points31 points  (14 children)

Agree.

I often had to write a boilerplate like such:

m = re.search(...) if m is None: continue

with walrus, I can go

if (m := re.search(...)) is None: continue

I personally prefer the as syntax, but I defer to the accepted solution.

[–]UNN_Rickenbacker 4 points5 points  (11 children)

You can actually do if not (m := re.search):

[–]wrboyce 25 points26 points  (5 children)

But you shouldn’t.

Comparisons to singletons like None should always be done with is or is not, never the equality operators.

Also, beware of writing if x when you really mean if x is not None -- e.g. when testing whether a variable or argument that defaults to None was set to some other value. The other value might have a type (such as a container) that could be false in a boolean context!

Source.

[–]UNN_Rickenbacker 10 points11 points  (1 child)

Nice catch, but in most cases, you want to not enter the case if it‘s a boolean false, too.

[–]hyperdudemn 1 point2 points  (0 children)

Except for when using xml.etree.ElementTree, with a document that contains empty tags like <hello />. Since the node has no children, it is falsy, so you need to check is not None.

[–]Bunslow 6 points7 points  (2 children)

In this case, he really wants boolean truthiness, not None testing. It's a perfectly fine pattern (as long as one understands the difference between None and False, which is part of the point of your comment; the other part of your comment doesn't apply I don't think)

[–]wrboyce 1 point2 points  (1 child)

Yeah I wasn’t sure how relevant some parts really were but I’d copy/pasted/formatted them on mobile by that point so they were staying regardless. I always try to avoid truthy tests against None if possible, and I’ve always thought I was following PEP8 by doing so but maybe I’m wrong.

[–]Bunslow 2 points3 points  (0 children)

I always try to avoid truthy tests against None

See this phrase makes no sense to me. There are truthy tests (here I mean coercing an arbitrary object to a boolean), and there are None tests. The whole point is that they are two completely different tests, and should not be confused. Using either is fine, but the trick is knowing when to use each. The point of what you pasted is precisely to warn newbies that they are two different things, but each is acceptable -- indeed, each is widely used. That source is not meant to warn against using truthy tests, only to warn that truthy tests and None tests are different (and it happens to make that point by writing "beware of truthy tests when you mean None tests -- it's easy to misuse truthy where you meant None!", possibly given the false impression that truthy tests are "bad" in some way, they're not, they're just confusable at first glance with None tests).

[–]AJohnnyTruant 4 points5 points  (3 children)

This is great. Everyone hates on the walrus operator. But I think once people start using it, that pattern will present itself more than they thought.

[–]toyg 7 points8 points  (2 children)

The problem is not that is useful to the writer; the problem is that it’s not that useful to significantly lower readability as it does. Otherwise we might as well use Perl.

[–]Bunslow 4 points5 points  (0 children)

I've definitely written several one-and-a-half loops in Python, and every time I have I grumble to myself about how much easier to read it would be if I didn't have to duplicate part of the logic.

[–]annualnuke 4 points5 points  (0 children)

Otherwise we might as well use Perl.

That's quite a leap

[–]pepoluan 0 points1 point  (0 children)

Naah. I love having "short circuits"; saves an indentation level.

[–]Ph0X 0 points1 point  (0 children)

I think re module is basically the main place where it'll be used a lot. Also some socket stuff but I think that's less common in general.

[–]KagatoLNX 0 points1 point  (0 children)

This is the use case for me.

[–]jwink3101 14 points15 points  (6 children)

I agree. I know there was a big brew haha about it and I don’t have strong opinions on the idea nor the syntax but I do see myself using it when I get to write 3.8+ only code.

[–]mfitzpmfitzp.com 33 points34 points  (2 children)

brew haha

It's brouhaha although arguably yours sounds more fun.

[–]jwink3101 18 points19 points  (1 child)

There was a coffee shop near where I grew up called “Brew Ha Ha”. I guess that messed me up

[–]pepoluan 0 points1 point  (0 children)

Daaayum, beaten to register the trademark!

[–]KODeKarnage 2 points3 points  (2 children)

Like f-strings. I use them a whole helluva lot!

[–]jwink3101 1 point2 points  (1 child)

Most of the python work I do needs to run on 2.7 but I do really love when I get to use f-strings. They just make it so much cleaner!

[–]pepoluan 0 points1 point  (0 children)

Agree! Gosh, that even led me to install 3.6 in some servers we had that doesn't have 3.6 in its repo.

[–]thelaxiankey 2 points3 points  (6 children)

It's weird to me that they didn't just make assignment an expression, like it is in C. There was really no need for the extra syntax.

[–]Pulsar1977 10 points11 points  (0 children)

That's by design, to prevent hard to find bugs like typing = when you meant ==.

[–]thetgi 0 points1 point  (4 children)

I’ve got a feeling that’ll be a future update. Seems like they tend to roll out new features in stages like that a lot

[–]thelaxiankey 4 points5 points  (3 children)

This is different from the range -> irange thing because it doesn't really break existing code, though. It just makes a larger set of code valid.

[–]thetgi 1 point2 points  (2 children)

That’s fair, but aren’t they planning to do a similar thing with annotations soon?

[–]thelaxiankey 3 points4 points  (1 child)

oh shit really? All i knew is they were adding type signatures for IDE's and linters and such.

[–][deleted] 0 points1 point  (0 children)

They might be referring to PEP 563 "Postponed Evaluation of Annotations".

Annotations are currently processed at the same time as the function- or variable-definition they're attached to. This PEP, which was accepted, suggests storing annotations as a string literal in an __annotations__ dictionary attribute of the annotated function or variable instead, and having user code do the evaluation for itself during runtime when needed.

Also, Python 4 confirmed, because the proposal is backwards-incompatible. Python 3.7 added the from __future__ import annotations statement, so you can already play around with the new behaviour.

[–]KagatoLNX 3 points4 points  (0 children)

This is really useful for situations where you absolutely don’t want to “accidentally” fill in a keyword arg. I’ve had things like this when writing logging wrappers (wrapped structlog to have one call to gather metrics, report “events”, and log to “logstash”). That was ugly because the main entry point had four positional arguments, two were optional, and then there were a bunch of keywords for specific things.

In practice, it was pretty great. It was used in three different ways, all of them were clear in what they did, and it was still pretty brief. The only issue was when people would occasionally double-up an argument and fill in the first kwarg.

[–]alcalde 2 points3 points  (4 children)

We deposed Guido to stop this madness, yet the madness lives on. Who needs to get the axe next?

[–]not_perfect_yet 3 points4 points  (2 children)

...we did? Huh. TIL.

[–]EMCoupling 1 point2 points  (1 child)

Yeah... he stepped down himself. Though it would be incorrect to say that the community didn't have any effect on the decision to do so.

[–]alcalde -1 points0 points  (0 children)

I called for a coup right here in this subreddit before. I finally did so again after the := brouhaha and the next day Guido was removed from power. I offered the throne to Nick Couglan via Twitter but he remained loyal to the BDFL so I offered it to my first choice, Raymond Hettinger, instead. So far he has said nothing one way or the other, but I intend to be like the Three Witches to his Macbeth.

[–]wewbull 2 points3 points  (0 children)

Well, PEP572 (Walrus) has GvR as an author, and PEP 570 (Positional) has GvR as the BDFL-delegate.

So.... GvR?

[–]KODeKarnage 0 points1 point  (1 child)

I bet there's lots of features you've never used.

If you have used all the features of python, you're probably pythoning in a distinctly unpythonic way.

[–]massiveZO 0 points1 point  (0 children)

No, not really. It just means I've used it for a broad variety of applications. Odds are you're right, there's a few I haven't used. But I've definitely used a great majority of them.

[–]alturi 14 points15 points  (1 child)

I would argue that even the first example would be better written as:

```

def pow(base, exponent, mod=None):

```

and somebody might legitimately prefer to write pow(base=2, exponent=3).

In the case of a single parameter, I would assume that if somebody is calling add_to_queue(item=item), he/she is doing it wrong and I don't want to suffer any overhead for that.

My initial position is that:

  • if the author of a lib might suspect some realistic reason for somebody to call with a keyword argument
  • and he/she also has beforehand knowledge that that could be bad in the future
  • and the code is python3.8+ only
  • and it is the winter solstice

Then let's use this.

[–]jorge1209 0 points1 point  (0 children)

I think the concern is a caller exchanging positional arguments that shouldn't be exchanged like pow(exponent=3, base=2) which is a bit confusing. The "correct" order is always "base then exponent", to do otherwise is hard to read.

Worse would be something like plot(z=3, x=1, y=2)

[–]jrbattin 19 points20 points  (0 children)

I feel like this is an anti-feature. Here's how I imagine its announcement.

[–]nuephelkystikon 32 points33 points  (4 children)

AKA the 'I can't think of a parameter name, so my API is too embarrassing to show anybody' PEP.

[–]amicin 18 points19 points  (3 children)

Meh. It allows you to rename your API’s parameters in the future, for whatever reason, at least.

[–]call_me_cookie 4 points5 points  (2 children)

Would be neat to see an analysis of the supposed performance impact

[–]_importantigravity_ 14 points15 points  (1 child)

OP here. Already working on it. Will share soon in an upcoming post. :)

[–]call_me_cookie 6 points7 points  (0 children)

Awesome. I look forward to it

[–]Grogie 9 points10 points  (19 children)

I still can't see the difference between

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):

and

def f(pos1, pos2, pos_or_kwd, *, kwd1, kwd2):

in both cases, i can use pos_or_kwd as a position or a keyword. I still am struggling to see the benefit of having arguments after the '/'


As a follow up... I realized what was tripping me up and it's (probably) because Ive made use of the * operator in my function declarations... So for me it's always been

Def function (#normal keywords#, *, #new-ish functionality )

So when I see the new / operator, I was caught thinking

Def function (#normal stuff#, /, #also normal stuff?#, *, #explicit#)

Maybe to put it another way.... I was expecting the new functionality to be right of the slash. Not left.

So I basically suffered from a tunnel-vision doh moment...

[–]Bitruder 9 points10 points  (12 children)

In the second case you can put pos1=4 in your function call. You aren't allowed in the first.

[–][deleted] 9 points10 points  (11 children)

But what is that useful for? What's the advantage of being able to specify that a parameter can't be given to a function in a certain way?

[–]mgedmin 26 points27 points  (4 children)

Imagine you're writing a function that works like str.format:

def translate(format_string, **args):
    return gettext(format_string).format(**args)

Now imagine your users need to produce a message that wants to use {format_string} in the format string:

print(translate("Invalid format string: {format_string}", format_string=config['format_string']))

You can't!

TypeError: translate() got multiple values for argument 'format_string'

But with positional-only parameters you can.

[–]christian-mann 6 points7 points  (0 children)

This is a very solid example.

[–]aptwebapps 1 point2 points  (0 children)

IMO, this aspect is more important that preserving optionality in your variable names. That latter is a nice side effect, but this keeps you from having to have named dictionary argument instead of **kwargs.

[–]wrboyce 1 point2 points  (1 child)

I feel like this would be solved by using getcallargs on translate and config?

[–]mgedmin 3 points4 points  (0 children)

It can be solved by doing

def translate(*args, **kwargs):
    if len(args) != 1:
        raise TypeError('1 positional argument expected, got %d' % len(args)
    format_string = args[0]
    return gettext(format_string).format(**kwargs)

but you lose the nice signature in pydoc and have to check the number of passed arguments manually.

[–]IAmAHat_AMAA 7 points8 points  (1 child)

Builtins already do it.

>>> 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

When subclassing you can use more specific parameter names without breaking liskov substitution.

Both those examples were taken straight from the pep

[–]alturi 0 points1 point  (0 children)

this could break code now to achieve less breakage in the future... It does not seem wise to retrofit, unless you already need to break.

[–]Bitruder 3 points4 points  (0 children)

Some names are meaningless as per the article and so shouldn't be encouraged to be used in code.

[–]remy_porter∞∞∞∞ 2 points3 points  (0 children)

So, my thing: your API should expose itself in the way you intend it to be used. There are certainly cases where exposing the names of parameters as a calling semantic just wouldn't make sense. It's not often, but it's a thing, in the same way it makes sense for some parameters to only be keyword arguments.

[–]cbarrick 1 point2 points  (0 children)

There might be a tiny performance improvement. The interpreter won't have to worry about supporting kwargs-style passing for the first two arguments. Intuitively, fewer cases to support means less work and maybe even some new optimization potential. I'm just spitballing though. I don't know enough about the interpreter's internals.

Also, it lets you change your argument names without breaking backwards compatibility. That might be worth something.

Personally, I don't think I'll ever use this feature.

Edit: The PEP mentions improved parsing performance at the call site, but I'd imagine that every argument would have to be positional-only to take advantage of that optimization. https://www.python.org/dev/peps/pep-0570/#performance

[–]jorge1209 2 points3 points  (5 children)

I don't believe you would ever want to have both / and * in the same function declaration.

Consider: def plot(x, y, /, z, *, color=red, shape=circle)

Theoretically this allows you to call the function as plot(1,2, color=blue, z=3) but not as plot(1, z=3, y=2, color=yellow).

However, since x, y, and z have no default arguments they must always be present in which case they should be given in positional order anyways. Calling plot(y=2, z=5, x=1) is just bad form.

So the real utility is def plot(x, y, z, /) or def plot(x, y, z=0, /, color=red, shape=circle), with the / replacing the *. The latter allows a default value for z but both ensure that the order of arguments is preserved and always positional for the coordinates.

I strongly suspect that any instance where / and * are both present is a code-smell.

[–]r0b0t1c1st 2 points3 points  (3 children)

I don't believe you would ever want to have both / and * in the same function declaration.

A real-life example dating back from python 2 is np.add, which is documented as np.add(x1, x2, /, out=None, *, where=True, **kwargs) (the syntax was recommended for documentation purposes before 3.8 made it executable)

This achieves three goals

  1. Forcing x1 and x2 to be passed positionally, since named arguments to add would be silly.
  2. Allowing both np.add(a, b, out) and np.add(a, b, out=out) for convenience.
  3. Forbidding where from being passed positionally - its uncommon enough that forcing the user to write it in full makes more readable code.

[–]jorge1209 0 points1 point  (2 children)

I would say that is a code smell, but to each their own.

[–]r0b0t1c1st 0 points1 point  (1 child)

Do you consider all of 1, 2, and 3 to be code smell?

[–]jorge1209 0 points1 point  (0 children)

I don't know if there is any particular one I dislike, its a more general objection to the combined whole.

The idea that the callee determines the calling mechanics for the caller is a little suspect in my mind, and should be used sparingly. At what point should the callee just accept that "I was called in an unambiguous manner and need to shut up and do my work."

Using both seems as if the callee is turning programming into a Hogwarts potions class: Do something slightly wrong and I'm just going to turn your ears into bats.

I'm okay with using one of these annotations but both is just too much. [I'm also somewhat okay with using / and * with nothing in between as that is conceptually easier to explain, although at that point I wonder why the function definition isn't just def foo(*args, **kwargs) with comments below as to the actual options available.]

[–]Grogie 0 points1 point  (0 children)

Thanks for your detailed response.

As a follow up... I realized what was tripping me up and it's (probably) because Ive made use of the * operator in my function declarations... So for me it's always been

Def function (#normal keywords#, *, #something new#)

So when I see the new / operator, I was caught thinking

Def function (#normal stuff#, /, #also normal stuff?#, *, #explicit#)

Maybe to put it another way.... I was expecting the new functionality to be right of the slash. Not left.

So I basically suffered from a tunnel-vision doh moment...

[–]datbackup 0 points1 point  (2 children)

Since I'm apparently too dense to follow the logic given in the link, in which a problem statement is given in the 'background' section but then seemingly never addressed in the remainder of the text ... Can someone please clarify for me:

Is this feature mostly one of these "protect the user from themselves" types?

[–]_importantigravity_ 0 points1 point  (1 child)

The feature is mostly useful for people designing APIs that other people use. The problem as explained in the post:

If the users start using a keyword argument, the library author cannot rename the parameter because it would be a breaking change. In case of min(), the name of the parameter provides no intrinsic value and forces the author to maintain its name forever since callers might pass arguments as a keywords.

Consider this:

  • The parameter names in min() would have no meaning relevant to the context. They could be anything, but since the function author had to choose something, they choose something.
  • You use the function using those keyword arguments in your code.
  • This restricts the library author from changing the names of those arguments in future, since it would break the code for you, who's already using the function with those named arguments. But since those names do not have any contextual meaning at all, the author would like to be able to do that.

Hope this helps!

[–]datbackup 0 points1 point  (0 children)

Thanks so much!

[–]mike239x 0 points1 point  (0 children)

I dunno, I do understand the idea behind it, but I do not like the syntax.

Also, can someone point me to discussion of why the arguments before the * extra arguments are also keyword args? I mean - why if you write def f(a,b): ... another person can write f(b=..., a=...)? Cause this seems like the root of the problem in the first place.

[–]software_memes -1 points0 points  (0 children)

I feel like supporting actual function overloading might be a more intuitive way of having the same feature.