Syntactic sugar to encourage use of named arguments

yeah I hate that and dataclasses do it better :joy:

Yeah this is the Rust struct version that makes more sense to me but breaks backwards compatibility for sure. I suspect that most code broken by this could probably use the rewrite [1], but that’s not sufficient justification for breaking it in the first place.


  1. because they have a variable with the name of one argument and the meaning of another ↩

1 Like

I posted my 2 cents on this on lwn earlier, so I’ll copypaste it here. Just some thoughts/ramblings from someone who enjoys using Python as a language and usually quietly reads the PEPs and discussions for interest.


I just don’t like the postfix version ( func(arg=) ) that’s been recommended, at all. I really hope this doesn’t get added to the language.

I wouldn’t mind seeing the prefix version ( func(=arg) ), but the best solution is probably to do nothing.

My reasoning:

You read and write from left to right.

The prefix version makes it clear straight-up “this arg is being passed from a local!” and then you get to read the name of the arg.

The postfix version on the other hand looks like unfinished code: when reading it, you just think “did I start adding a new argument here and then get distracted? I better finish that
 oh, it’s just supposed to be an implicit arg, right.” Alternatively, when writing, suppose you did distracted after writing the ‘=’, and never did come back to finish it. If that arg happens to exist as a local and doesn’t cause an obvious immediate error, you’re boned.

Personally, whenever a func needs more than about six arguments (or more than about TWO optional arguments) I just stick them all in a dataclass (I’ve made myself a convention of naming this kind of dataclass something ending “Params”). That solves the problem for me: I can now just pass that instance around, instead of a bunch of arguments. Hence why I don’t think this is really needed at all, and do-nothing is the best solution here.


As a side note, I don’t use f-strings (as I find those unreadable too), so I wasn’t aware of the f'{var=}' syntax until now - that might have biased me on this one, but then if I did use f-strings I’d probably give the same “it looks like unfinished code” reasoning to the syntax there.

2 Likes

I don’t like this idea, but I think it’s time for those interested to just choose one of the many proposed syntaxes, find a sponsor and write a PEP. It doesn’t seem like there’s much more to add before a concrete proposal is made.

5 Likes

This syntax would be more appropriate in match statements, which would naturally lead to more convenient destructuring syntax.

I have implemented this feature into CPython here(own-fork): Added shorthand keyword argument. · Hels15/cpython@bd99a94 · GitHub

Joshua is working on the PEP.

3 Likes

I have just spent two days making plots with matplotlib, which is very keyword-laden, and tried both proposed syntax variants. [1]

I have to say that while I prefer variable= in the abstract, I find the =variable syntax is clearly better in practice.

This:

    cbar = fig.colorbar(
        ...
        =ax,
        =location,
        =fraction,
        =pad,
        =shrink,
        =aspect,
        =label,
    )

flows better than this:

    cbar = fig.colorbar(
        ...
        ax=,
        location=,
        fraction=,
        pad=,
        shrink=,
        aspect=,
        label=,
    )

because the variables on the right-hand side are things you will see defined around the function calls. So I find that location= creates a brief moment of confusion (“What am I passing there?”) which =location does not. Besides, I also think the neatly aligned = signs help readability a lot.


  1. Fixing my files before running, not using an implementation. ↩

10 Likes

As an alternative to the proposed syntax, what about the option of having the call be the same as the signature:


def foo(x,y, /, z, *, a, b):
    ...

# Current usage
a = 4
b = 5    

foo(1,2,3,a=4,b=5)

# Pass as a dictionary
kwargs = {
    'z': 3,
    'a': a,
    'b': b
}

foo(1,2, **kwargs)


# With new shorthand

foo(1,2, z=3, *, a, b)
foo(1,2, *, b, a) # Order doesn't matter
7 Likes

Going slightly on a tangent, I came here after reading the LWN article because of this sort of thing: you’ve improved the function call syntax by changing it to accept an object argument, but that object still needs to be constructed! The initialization of that object will fall into the same trap as this topic covers, either because the object’s constructor will need the variables passed so they can be stored into its attributes, or if the object is actually a dict then the variables need to be passed and the key names provided.

I’ve seen (and written) plenty of code that looks like:

foo = { "a": a, "b": b, "c": c }

If this idea can somehow be extended to allow construction of dicts in a way that infers the keys from the names of the variables provided as values, that would be a big win in my opinion.

2 Likes

Python has the dict() constructor, so these two are interchangeable:

foo = {"a": a, "b": b, "c": c}
foo = dict(a=a, b=b, c=c)
2 Likes

Ahh, of course. As long as the proposed mechanism will work for ‘dynamic’ kwargs, then it would work for dict() too.

1 Like

This might be the case if you were working with a functional language and mutable objects didn’t exist.
One of the things I love the most about Python is duck typing and how nicely it works with mutable state.
It allows you to do something like this:

class Order:
    def __init__(self, order_number):
        self.order_number = order_number
        self.description = 'Description not found'
        self.customer = ''
        self.customer_name = 'Customer not found'

    def populate(self):
        add_order_info_from_db(self.order_number, self)
        add_customer_info_from_db(self.customer, self)

my_order = Order(some_order)
my_order.populate()

The beauty of this is if any database call fails, you still have a valid object to display to the user.
Additionally, because of duck typing, add_order_info_from_db is usable for any object supporting order information.

IMO, having a signature like this:

class Order:
    def __init__(
            self, 
            order_number,
            description,
            customer,
            customer_name,
        ):
        ...

reduces readability and couples details about the order object to all the places orders are constructed.
I know that a functional style can be beneficial, but IMO, I prefer the former for this particular case.

When I see a signature with many parameters, I typically start thinking of the best way to factor them out.

Yep. Since this is a change at the call site, it doesn’t make any difference whether it’s **kwargs or specific pos-or-kw parameters, or specific kwonly parameters. They will all work the same way.

1 Like

IMO this is completely orthogonal to this proposal. What you’re describing here is a way of “half initializing” an object, rather than ensuring that there is enough information provided to __init__ to fully construct everything it needs. You say that you want to refactor them away, but why? This is not simply a refactor, unless you mandate that objects are constructed and then more-constructed; it’s a fundamental change in the way the object behaves.

Large numbers of parameters are not a problem - but also, this proposal can be useful even with a single parameter.

2 Likes

Thats fair.
And I agree that this approach is fundamentally different.

My point was that the post I was responding to was implying that the only way to create an object with multiple attributes is to supply them all upon construction. That’s simply not true.

This is also not a toy example. The main legacy codebase I work with, although not written in Python and without named arguments, suffered from extremely long function signatures following the latter pattern. To the point where it was quite common for bugs to pop up from them being in the wrong order. I also personally find the longer signatures to be less readable than ones with fewer.

That all being said, my stance hasn’t changed here since the initial proposal. I’m not strongly opposed, although I wouldn’t use it myself.

I also believe there’s little more to be added here without a PEP.

Depending on your definitions, both “of course” and “of course not” :slight_smile:

That sounds like a solid justification for keyword arguments, more than a reason to dislike long function signatures.

5 Likes

I really, really like the idea of @joshuabambrick making it possible to shorten

my_function(
  my_first_variable=my_first_variable,
  my_second_variable=my_second_variable,
  my_third_variable=my_third_variable,
)

to something else. But I am not so convinced with the syntax

my_function(=my_first_variable, =my_second_variable, =my_third_variable)

or even

my_function(my_first_variable=, my_second_variable=, my_third_variable=)

My input is highly subjective, but I just do not see the syntax visually appealing, and in my opinion, it would not make Python code look nicer. The original problem though is something I’ve stumbled upon a million times and wished that python had a clean solution for.

alternative: autodict

Consider having a set of variables with values:

my_first_variable = 'First value'
my_second_variable = 'Second value'
my_third_variable = 'Third value'

Many of the solutions could be handled just by adding a special dictionary class which could be populated with following syntax (be it autodict or pundict or something else):

>>> autodict(my_first_variable, my_second_variable, my_third_variable)
{'my_first_variable': 'First value',
 'my_second_variable': 'Second value',
 'my_third_variable': 'Third value'}

This would be quite easy to use in function call:

my_function(**autodict(my_first_variable, my_second_variable, my_third_variable))

alternative: &dict

It could, alternatively, be syntactic sugar building on top of dict with for example & operator:

my_function(**&dict(my_first_variable, my_second_variable, my_third_variable))

alternative: @dict

We have used to “@” being something used to decorate things. So it could also be

my_function(**@dict(my_first_variable, my_second_variable, my_third_variable))

alternative: & or @ before items

Other solution, similar to what @malemburg mentioned

my_function(**dict(&my_first_variable, &my_second_variable, &my_third_variable))

or

my_function(**{&my_first_variable, &my_second_variable, &my_third_variable})

or then with @

my_function(**dict(@my_first_variable, @my_second_variable, @my_third_variable))

alternative: double curly braces

We have {a, b, c} for sets, but what if {{a, b, c}} would mean an autodict?

my_function(**{{my_first_variable, my_second_variable, my_third_variable}})

I really hope that we as python community can find some way to get the functionality to the language!

3 Likes

This syntax would be more appropriate in match statements, which would naturally lead to more convenient destructuring syntax.

Would you be willing to elaborate on this a little more?

1 Like

100% YES PLEASE! on this idea.

I have a mild preference for
func(lengthy_parameter_name_1=, another_long_parameter_name=)
over
func(=lengthy_parameter_name_1, =another_long_parameter_name)
but frankly, I’d just be happy to have it part of the language in whatever capacity. It’s yet another application of the DRY principle that I’m hugely in favour of.

But either one is decidedly better than
func(lengthy_parameter_name_1=lengthy_parameter_name_1, another_long_parameter_name=another_long_parameter_name)

Rant to naysayers

It’s worth noting that the inclusion of such a feature to the language doesn’t obligate you to use it. It also doesn’t preclude using variable names for arguments that are different from the parameter names they’re assigned to. If you don’t like it, don’t use it. If your team is split on its use, discuss it and come to a decision together. If you have the authority to make a unilateral decision on this, make it. If you have no authority, just do as your told.

Everyone worried about linters
 they’re customizable. You can decide what language feature you want them to flag or enforce. Take the time to customize the linters to suit your team and your project.

If you have a junior programmer who doesn’t get it
 your company needs to do better in its hiring process, or it needs to invest more in training.

I actually want this for dictionaries much more than for function calls. However, with this, you can have both my calling dict explicitly rather than using the {} notation.

old_dict = {"var1": var1, "var2": var2, "var3": var3}
new_dict = dict(var1=, var2=, var3=)
assert old_dict == new_dict
2 Likes

Not quite: this principle is about not duplicating some logic in multiple places, because they will very probably become out of sync and cause issues that will take time to diagnose. It’s about a useful unit of code, it does not mean to never write the same name twice!

6 Likes

I prefer the foo= syntax over =foo because an assignment describes the flow of data, and it feels more natural to me to have the destination, the final product, the left part of the assignment to be explicitly stated and make the source, the right part optional. So, if this PEP gets accepted, one of the places it could be extended to is match statements. But the data flows from left to right there, and when extracting object attributes into variables it feels more natural to leave out the left parts of the assignments. And if you squint hard enough, then this looks like destructuring.

match foo:
    case Foo(=bar, =baz, =quux): pass

It’s looks clunky, has lots of boilerplate, but it does assign attributes to variables without repeating their names twice. So maybe something can come out of it eventually


3 Likes