Passing a keyword argument containing a '-'

Is there some way I can use a hyphen in the name of a keyword argument as in the example below?

set_cookie("cookieName", "cookieValue", Max-Age=0, Expires="")

No. Hyphens are never valid characters in Python names. That includes function names, variable names, and argument names. Use an underscore instead.

Really, you should just follow the PEP 8 naming conventions; they’re standard across most Python code. That means for argument names, you should use all lowercase with underscores for spaces.

You could have the cookie attributes as a dictionary instead.

That is unfortunate because in the case of http cookies I want to pass optional attributes; one of which may be “Max-Age”.

options = {
    "Max-Age": 0,
    "Expires": ""
}

set_cookie("cookieName", "cookieValue", **options)

@elis.byberi I really wouldn’t do this. **kwargs is intended to let you pass keyword arguments, so it seems like a bad idea to expect things that aren’t valid keyword arguments.

If you adopted this signature, anyone using set_cookie would have to create a dictionary and unpack it. You might as well save them the step of unpacking by taking a dictionary argument directly.

Yes, I did unpack it erroneously.

options = {
    "Max-Age": 0,
    "Expires": ""
}

set_cookie("cookieName", "cookieValue", options)  # not **options

You will need to format the cookie string later, so it is easier to pass the optional values in a dictionary and simply iterate over them, or use a similar approach.

My workaround for this is usually to allow both. Have an optional positional argument that takes a dictionary context, then allow **kwargs as overrides.

def set_cookie(__cookie: CookieTD | None = None, /, **kwargs: Unpack[CookieTD]):
    opts = __cookie or {}
    opts.update(kwargs)
    ... # do something with the resolved cookie options

Note that you will likely need to use a deepcopy if any of the options are mutable containers (list, set, dict, etc), but this pattern has been pretty useful for me in a ton of cases. Especially when I’m needing to allow per function/method overrides of a whole giant set of parameters. This lets me create some defaults that can be passed as the positional, then allow small changes to be passed as arbitrary keyword arguments.

Plus if the API has something that isn’t a valid name, you can still pass it by unpacking it from a dictionary (which is annoying, but better in my opinion than renaming stuff and causing confusion later).

Passing invalid names to **kwargs is a bit of a code smell, I know, but at runtime they’re just treated as a normal dictionary, so it’s up to you if you want to ignore it or not.

It’s explicitly ok. From 6. Expressions — Python 3.14.6 documentation :

When **expression is used, each key in this mapping must be a string. … A key need not be a Python identifier (e.g. "max-temp °F" is acceptable, although it will not match any formal parameter that could be declared).

Of course, there can’t be a matching parameter name (in the normal way, at least), so clearly the function signature has to have a ** formal parameter.

The ability to provide arguments by name in a call, using names not explicitly declared in the signature and the id=value syntax, is really just a convenience for loading a dictionary to give to the ** argument of a call. Non-identifier keys turn up in some numerical libraries.

In this particular context, it might be worth looking at http.cookies — HTTP state management — Python 3.14.6 documentation and passing one of those.

The hyphen means subtract so it’s not usable in a name.

a = b-c

So Max-Age is Max subtract Age.

That’s what I was referring to as the ‘code smell’. Mainly since I was using an unpacked TypedDict to hint the kwargs. Which means the user will possibly get confusing hints. I don’t think this is really an issue if you provide an alternative name for that situation or just require them to unpack a dictionary.