"AI Coffee" Grand Opening This Monday • A Story About Parameters and Arguments in Python Functions
Parameters and arguments • Positional and keyword arguments • Parameters with default values • *args and **kwargs • Positional-only and keyword-only arguments • Let's discuss all of this over coffee
Alex had one last look around. You could almost see a faint smile emerge from the deep sigh—part exhaustion and part satisfaction. He was as ready as he could be. His new shop was as ready as it could be. There was nothing left to set up. He locked up and headed home. The grand opening was only seven hours away, and he'd better get some sleep.
Grand Opening sounds grand—too grand. Alex had regretted putting it on the sign outside the shop's window the previous week. This wasn't a vanity project. He didn't send out invitations to friends, journalists, or local politicians. He didn't hire musicians or waiters to serve customers. Grand Opening simply meant opening for the first time.
Alex didn't really know what to expect on the first day. Or maybe he did—he wasn't expecting too many customers. Another coffee shop on the high street? He may need some time to build a loyal client base.
• • •
He had arrived early on Monday. He'd been checking the lights, the machines, the labels, the chairs, the fridge. And then checking them all again. He glanced at the clock—ten minutes until opening time. But he saw two people standing outside. Surely they were just having a chat, only standing there by chance. He looked again. They weren't chatting. They were waiting.
Waiting for his coffee shop to open? Surely not?
But rather than check for the sixth time that the labels on the juice bottles were facing outwards, he decided to open the door a bit early. And those people outside walked in. They were AI Coffee's first customers.
Today's article is an overview of the parameters and arguments in Python's functions. It takes you through some of the key principles and discusses the various types of parameters you can define and arguments you can pass to a Python function. There are five numbered sections interspersed within the story in today's article:
Parameters and Arguments
Positional and Keyword Arguments
Args and Kwargs
Optional Arguments with Default Values
Positional-Only and Keyword-Only Arguments
Espressix ProtoSip v0.1 (AlphaBrew v0.1.3.7)
Introducing the Espressix ProtoSip, a revolutionary coffee-making machine designed to elevate the art of brewing for modern coffee shops. With its sleek, futuristic design and cutting-edge technology, this prototype blends precision engineering with intuitive controls to craft barista-quality espresso, cappuccino, and more. Tailored for innovators, the Espressix delivers unparalleled flavour extraction and consistency, setting a new standard for coffee excellence while hinting at the bold future of café culture.
Alex had taken a gamble with the choice of coffee machine for his shop. His cousin set up a startup some time earlier that developed an innovative coffee machine for restaurants and coffee shops. The company had just released its first prototype, and they offered Alex one at a significantly reduced cost since it was still a work in progress—and he was the founder's cousin!
The paragraph you read above is the spiel the startup has on its website and on the front cover of the slim booklet that came with the machine. There was little else in the booklet. But an engineer from the startup company had spent some time explaining to Alex how to use the machine.
The Espressix didn't have a user interface yet—it was still a rather basic prototype. Alex connected the machine to a laptop. He was fine calling functions from the AlphaBrew Python API directly from a terminal window—AlphaBrew is the software that came with the Espressix.
What the Espressix did have, despite being an early prototype, is a sleek and futuristic look. One of the startup's cofounders was a product design graduate, so she went all out with style and looks.
1. Parameters and Arguments
"You're AI Coffee's first ever customer", Alex told the first person to walk in. "What can I get you?"
"Wow! I'm honoured. Could I have a strong Cappuccino, please, but with a bit less milk?"
"Sure", and Alex tapped at his laptop:

And the Espressix started whizzing. A few seconds later, the perfect brew poured into a cup.
Here's the signature for the brew_coffee()
function Alex used:
Alex was a programmer before deciding to open a coffee shop. He was comfortable with this rudimentary API to use the machine, even though it wasn't ideal. But then, he wasn't paying much to lease the machine, so he couldn't complain!
The coffee_type
parameter accepts a string, which must match one of the available coffee types. Alex is already planning to replace this with enums to prevent typos, but that's not a priority for now.
The strength
parameter accepts integers between 1
and 5
. And milk
also accepts integers up to 5
, but the range starts from 0
to cater for coffees with no milk.
Terminology can be confusing, and functions come with plenty of terms. Parameter and argument are terms that many confuse. And it doesn't matter too much if you use one instead of the other. But, if you prefer to be precise, then:
Use parameter for the name you choose to refer to values you pass into a function. The parameter is the name you place within parentheses when you define a function. This is the variable name you use within the function definition. The parameters in the above example are
coffee_type
,strength
, andmilk_amount
.Use argument for the object you pass to the function when you call it. An argument is the value you pass to a function. In the example above, the arguments are
"Cappuccino"
,4
, and2
.When you call a function, you pass arguments. These arguments are assigned to the parameter names within the function.
To confuse matters further, some people use formal parameters to refer to parameters and actual parameters to refer to arguments. But the terms parameters and arguments as described in the bullet points above are more common in Python, and they're the ones I use here and in all my writing.
Alex's first day went better than he thought it would. He had a steady stream of customers throughout the day. And they all seemed to like the coffee.
But let's see what happened on Alex's second day!
2. Positional and Keyword Arguments
Chloezz @chloesipslife • 7m
Just visited the new AI Coffee shop on my high street, and OMG, it’s like stepping into the future! The coffee machine is a total sci-fi vibe—sleek, futuristic, and honestly, I have no clue how it works, but it’s powered by AI and makes a mean latte! The coffee? Absolutely delish. If this is what AI can do for my morning brew, I’m here for it! Who’s tried it? #AICoffee #CoffeeLovers #TechMeetsTaste
— from social media
Alex hadn't been on social media after closing the coffee shop on the first day. Even if he had, he probably wouldn't have seen Chloezz's post. He didn't know who she was. But whoever she is, she has a massive following.
Alex was still unaware his coffee shop had been in the spotlight when he opened up on Tuesday. There was a queue outside. By mid-morning, he was no longer coping. Tables needed cleaning, fridge shelves needed replenishing, but there had been no gaps in the queue of customers waiting to be served.
And then Alex's sister popped in to have a look.
"Great timing. Here, I'll show you how this works." Alex didn't hesitate. His sister didn't have a choice. She was now serving coffees while Alex dealt with everything else.
• • •
But a few minutes later, she had a problem. A take-away customer came back in to complain about his coffee. He had asked for a strong Americano with a dash of milk. Instead, he got what seemed like the weakest latte in the world.
Alex's sister had typed the following code to serve this customer:
But the function's signature is:
I dropped the type hints, and I won't use them further in this article to focus on other characteristics of the function signature.
Let's write a demo version of this function to identify what went wrong:
The first argument, "Americano"
, is assigned to the first parameter, coffee_type
. So far, so good…
But the second argument, 1
, is assigned to strength
, which is the second parameter. Python can only determine which argument is assigned to which parameter based on the position of the argument in the function call. Python is a great programming language, but it still can't read the user's mind!
And then, the final argument, 4
, is assigned to the final parameter, milk_amount
.
Alex's sister had swapped the two integers. An easy mistake to make. Instead of a strong coffee with a little milk, she had input the call for a cup of hot milk with just a touch of coffee. Oops!
Here's the output from our demo code to confirm this error:
Coffee type: Americano
Strength: 1
Milk Amount: 4
Alex apologised to the customer, and he made him a new coffee.
"You can do this instead to make sure you get the numbers right," he showed his sister as he prepared the customer's replacement drink:
Note how the second and third arguments now also include the names of the parameters.
"This way, it doesn't matter what order you input the numbers since you're naming them", he explained.
Here's the output now:
Coffee type: Americano
Strength: 4
Milk Amount: 1
Even though the integer 1
is still passed as the second of the three arguments, Python now knows it needs to assign this value to milk_amount
since the parameter is named in the function call.
When you call a function such as
brew_coffee()
, you have the choice to use either positional arguments or keyword arguments.Arguments are positional when you pass the values directly without using the parameter names, as you do in the following call:
brew_coffee("Americano", 1, 4)
You don't use the parameter names. You only include the values within the parentheses. These arguments are assigned to parameter names depending on their order.
Keyword arguments are the arguments you pass using the parameter names, such as the following call:
brew_coffee(coffee_type="Americano", milk_amount=1, strength=4)
In this example, all three arguments are keyword arguments. You pass each argument matched to its corresponding parameter name. The order in which you pass keyword arguments no longer matters.
Keyword arguments can also be called named arguments.
Positional and keyword arguments: Mixing and matching
But look again at the code Alex used when preparing the customer's replacement drink:
The first argument doesn't have the parameter name. The first argument is a positional argument and, therefore, it's assigned to the first parameter, coffee_type
.
However, the remaining arguments are keyword arguments. The order of the second and third arguments no longer matters.
Therefore, you can mix and match positional and keyword arguments.
But there are some rules! Try the following call:
You try to pass the first and third arguments as positional and the second as a keyword argument, but…
File "...", line 8
brew_coffee("Americano", milk_amount=1, 4)
^
SyntaxError: positional argument follows
keyword argument
Any keyword arguments must come after all the positional arguments. Once you include a keyword argument, all the remaining arguments must also be passed as keyword arguments.
And this rule makes sense. Python can figure out which argument goes to which parameter if they're in order. But the moment you include a keyword argument, Python can no longer assume the order of arguments. To avoid ambiguity—we don't like ambiguity in programming—Python doesn't allow any more positional arguments once you include a keyword argument.
3. Args and Kwargs
Last week, AI Coffee, a futuristic new coffee shop, opened its doors on High Street, drawing crowds with its sleek, Star Trek-esque coffee machine. This reporter visited to sample the buzzworthy brews and was wowed by the rich, perfectly crafted cappuccino, churned out by the shop’s mysterious AI-powered machine. Eager to learn more about the technology behind the magic, I tried to chat with the owner, but the bustling shop kept him too busy for a moment to spare. While the AI’s secrets remain under wraps for now, AI Coffee is already a local hit, blending cutting-edge tech with top-notch coffee.
— from The Herald, a local paper
Alex had started to catch up with the hype around his coffee shop—social media frenzy, articles in local newspapers, and lots of word-of-mouth. He wasn't complaining, but he was perplexed at why his humble coffee shop had gained so much attention and popularity within its first few days. Sure, his coffee was great, but was it so much better than others? And his prices weren't the highest on the high street, but they weren't too cheap, either.
However, with the increased popularity, Alex also started getting increasingly complex coffee requests. Vanilla syrup, cinnamon powder, caramel drizzle, and lots more.
Luckily, the Espressix ProtoSip was designed with the demanding urban coffee aficionado in mind.
Args
Alex made some tweaks to his brew_coffee()
function:
There's a new parameter in brew_coffee()
. This is the *args
parameter, which has a leading *
in front of the parameter name. This function can now accept any number of positional arguments following the first three. We'll explore what the variable name args
refers to shortly. But first, let's test this new function:
You call the function with five arguments. And here's the output from this function call:
Coffee type: Latte
Strength: 3
Milk Amount: 2
Add-ons: cinnamon, hazelnut syrup
The first argument,
"Latte"
, is assigned to the first parameter,coffee_type
.The second argument,
3
, is assigned to the second parameter,strength
.The third argument,
2
, is assigned to the third parameter,milk_amount
.The remaining two arguments,
"cinnamon"
and"hazelnut syrup"
, are assigned toargs
, which is a tuple.
You can confirm that args
is a tuple with a small addition to the function:
The first two lines of the output from this code are shown below:
args=('cinnamon', 'hazelnut syrup')
<class 'tuple'>
The parameter name args
is a tuple containing the remaining positional arguments in the function call once the function deals with the first three.
There's nothing special about the name args
What gives *args
its features? It's not the name args
. Instead, it's the leading asterisk, *
, that makes this parameter one that can accept any number of positional arguments. The parameter name args
is often used in this case, but you can also use a name that's more descriptive to make your code more readable:
Alex uses the name add_ons
instead of args
. This parameter name still has the leading *
in the function signature. Colloquially, many Python programmers will still call a parameter with a leading *
the args parameter, even though the parameter name is different.
Therefore, you can now call this function with three or more arguments. You can add as many arguments as you wish after the third one, including none at all:
The output confirms that add_ons
is now an empty tuple:
add_ons=()
<class 'tuple'>
Coffee type: Latte
Strength: 3
Milk Amount: 2
Add-ons:
This coffee doesn't have any add-ons.
We have a problem
However, Alex's sister, who was now working in the coffee shop full time, could no longer use her preferred way of calling the brew_coffee()
function:
This raises an error:
File "...", line 9
brew_coffee("Latte", strength=3,
milk_amount=2, "vanilla syrup")
^
SyntaxError: positional argument follows
keyword argument
This is a problem you've seen already. Positional arguments must come before keyword arguments in a function call. And *add_ons
in the function signature indicates that Python will collect all remaining positional arguments from this point in the parameter list. Therefore, none of the parameters defined before *add_ons
can be assigned a keyword argument if you also include args as arguments. They must all be assigned positional arguments.
All arguments preceding the args arguments in a function call must be positional arguments.
Alex refactored the code:
The *add_ons
parameter is now right after coffee_type
. The remaining parameters, strength
and milk_amount
, come next. Unfortunately, this affects how Alex and his growing team can use brew_coffee()
in other situations, too. The strength
and milk_amount
arguments must now come after any add-ons, and they must be used as keyword arguments.
See what happens if you try to pass positional arguments for strength
and milk_amount
:
This raises an error:
Traceback (most recent call last):
File "...", line 9, in <module>
brew_coffee("Latte", "vanilla syrup", 3, 2)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: brew_coffee() missing
2 required keyword-only arguments:
'strength' and 'milk_amount'
The args parameter, which is *add_ons
in this example, marks the end of the positional arguments in a function. Therefore, strength
and milk_amount
must be assigned arguments using keywords.
Alex instructed his team on these two changes:
Any add-ons must go after the coffee type.
They must use keyword arguments for
strength
andmilk_amount
.
It's a bit annoying that they have to change how to call the function but they're all still learning and Alex feels this is a safer option.
Kwargs
But Alex's customers also had other requests. Some wanted their coffee extra hot, others needed oat milk, and others wanted their small coffee served in a large cup.
Alex included this in brew_coffee()
by adding another parameter:
The new parameter Alex added at the end of the signature, **kwargs
, has two leading asterisks, **
. This parameter indicates that the function can accept any number of optional keyword arguments after all the other arguments.
Whereas *args
creates a tuple called args
within the function, the double asterisk in **kwargs
creates a dictionary called kwargs
. The best way to see this is to call this function with additional keyword arguments:
The final two arguments use the keywords milk_type
and temperature
. These are not parameters in the function definition.
Let's explore these six arguments:
The first argument,
"Latte"
, is the required argument assigned tocoffee_type
.The second argument,
"vanilla syrup"
, is also a positional argument. Therefore, Python assigns this toadd_ons
. There's only one additional positional argument in this call but, in general, you can have more.Next, you have the two required keyword arguments,
strength=3
andmilk_amount=2
.But there are also two more keyword arguments,
milk_type="oat"
andtemperature="extra hot"
. These are the additional optional keyword arguments, and they're assigned to the dictionarykwargs
.
Here is the first part of the output from this call:
kwargs={
'milk_type': 'oat',
'temperature': 'extra hot'
}
<class 'dict'>
This confirms that kwargs
is a dictionary. The keywords are the keys, and the argument values are the dictionary values.
The rest of the output shows the additional special instructions in the printout:
Coffee type: Latte
Strength: 3
Milk Amount: 2
Add-ons: vanilla syrup
Instructions:
milk type: oat
temperature: extra hot
There's nothing special about the name kwargs
You've seen this when we talked about args. There's nothing special about the parameter name kwargs
. It's the leading double asterisk that does the trick. So, you can use any descriptive name you wish in your code:
Warning: the following paragraph is dense with terminology!
So, in its current form, this function needs a required argument assigned to coffee_type
and two required keyword arguments assigned to strength
and milk_amount
. And you can also have any number of optional positional arguments, which you add after the first positional argument but before the required keyword arguments. These are the add-ons a customer wants in their coffee.
But you can also add any number of keyword arguments at the end of the function call. These are the special instructions from the customer.
Both args and kwargs are optional. So, you can still call the function with only the required arguments:
The output shows that this gives a strong espresso with no milk, no add-ons, and no special instructions:
instructions={}
<class 'dict'>
Coffee type: Espresso
Strength: 4
Milk Amount: 0
Add-ons:
Instructions:
Note that in this case, since there are no args, you can also pass the first argument as a keyword argument:
But this is only possible when there are no add-ons—no args. We'll revisit this case in a later section of this article.
A quick recap before we move on.
Args and kwargs are informal terms used for parameters with a leading single and double asterisk.
The term args refers to a parameter with a leading asterisk in the function's signature, such as
*args
. This parameter indicates that the function can accept any number of optional positional arguments following any required positional arguments. The term args stands for arguments, but you've already figured that out!And kwargs refers to a parameter with two leading asterisks, such as
**kwargs
, which indicates that the function can accept any number of optional keyword arguments following any required keyword arguments. The 'kw' in kwargs stands for keyword.
Coffee features often when talking about programming. Here's another coffee-themed article, also about functions: What Can A Coffee Machine Teach You About Python's Functions?
4. Optional Arguments with Default Values
Alex's team grew rapidly. The coffee shop now had many regular customers and a constant stream of coffee lovers throughout the day.
Debra, one of the staff members, had some ideas to share in a team meeting:
"Alex, many customers don't care about the coffee strength. They just want a normal coffee. I usually type in 3
for the strength argument for these customers. But it's time-consuming to have to write strength=3
for all of them, especially when it's busy."
"We can easily fix that", Alex was quick to respond:
The parameter strength
now has a default value. This makes the argument corresponding to strength
an optional argument since it has a default value of 3
. The default value is used by the function only if you don't pass the corresponding argument.
Alex's staff can now leave this argument out if they want to brew a "normal strength" coffee:
This gives a medium strength espresso with no add-ons or special instructions:
Coffee type: Espresso
Strength: 3
Milk Amount: 0
Add-ons:
Instructions:
The output confirms that the coffee strength has a value of 3
, which is the default value. And here's a coffee with some add-ons that also uses the default coffee strength:
Here's the output confirming this normal-strength caramel-drizzle latte:
Coffee type: Latte
Strength: 3
Milk Amount: 2
Add-ons: caramel drizzle
Instructions:
Ambiguity, again
Let's look at the function's signature again:
The coffee_type
parameter can accept a positional argument. Then, *add_ons
collects all remaining positional arguments, if there are any, that the user passes when calling the function. Any argument after this must be a keyword argument. Therefore, when calling the function, there's no ambiguity whether strength
, which is optional, is included or not, since all the arguments after the add-ons are named.
Why am I mentioning this? Consider a version of this function that doesn't have the args parameter *add_ons
:
I commented out the lines with *add_ons
to highlight they've been removed temporarily in this function version. When you run this code, Python raises an error. Note that the error is raised in the function definition before the function call itself:
File "...", line 5
milk_amount,
^^^^^^^^^^^
SyntaxError: parameter without a default follows
parameter with a default
Python doesn't allow this function signature since this format introduces ambiguity. To see this ambiguity, let's use a positional argument for the amount of milk, since this would now be possible as *add_ons
is no longer there. Recall that in the main version of the function with the parameter *add_ons
, all the arguments that follow the args must be named:
As mentioned above, note that the error is raised by the function definition and not the function call. I'm showing these calls to help with the discussion.
Is the value 0
meant for strength
, or is your intention to use the default value for strength
and assign the value 0
to milk_amount
? To avoid this ambiguity, Python function definitions don't allow parameters without a default value to follow a parameter with a default value. Once you add a default value, all the following parameters must also have a default value.
Of course, there would be no ambiguity if you use a keyword argument. However, this would lead to the situation where the function call is ambiguous with a positional argument, but not when using a keyword argument, even though both positional and keyword arguments are possible. Python doesn't allow this to be on the safe side!
This wasn't an issue when you had *add_ons
as part of the signature. Let’s put *add_ons
back in:
There's no ambiguity in this case since strength
and milk_amount
must both have keyword arguments.
However, even though this signature is permitted in Python, it's rather unconventional. Normally, you don't see many parameters without default values after ones with default values, even when you're already in the keyword-only region of the function (after the args).
In this case, Debra's follow-up suggestion fixes this unconventional function signature:
"And we also have to input milk_amount=0
for black coffees, which are quite common. Can we do a similar trick for coffees with no milk?"
"Sure we can"
Now, there's also a default value for milk_amount
. The default is a black coffee.
In this version of the function, there's only one required argument—the first one that's assigned to coffee_type
. All the other arguments are optional either because they're not needed to make a coffee, such as the add-ons and special instructions, or because the function has default values for them, such as strength
and milk_amount
.
A parameter can have a default value defined in the function's signature. Therefore, the argument assigned to a parameter with a default value is an optional argument.
And let's confirm you can still include add-ons and special instructions:
Here's the output from this function call:
Coffee type: Cappuccino
Strength: 3
Milk Amount: 2
Add-ons: chocolate sprinkles, vanilla syrup
Instructions:
temperature: extra hot
cup size: large cup
Note that you rely on the default value for strength
in this example since the argument assigned to strength
is not included in the call.
A common pitfall with default values in function definitions is the mutable default value trap. You can read more about this in section 2, The Teleportation Trick, in this article: Python Quirks? Party Tricks? Peculiarities Revealed…
5. Positional-Only and Keyword-Only Arguments
Let's summarise the requirements for all the arguments in Alex's current version of the brew_coffee()
function. Here's the current function signature:
The first parameter is
coffee_type
, and the argument you assign to this parameter can be either a positional argument or a keyword argument. But—and this is important—you can only use it as a keyword argument if you don't pass any arguments assigned to*add_ons
. Remember that positional arguments must come before keyword arguments in function calls. Therefore, you can only use a keyword argument for the first parameter if you don't have args. We'll focus on this point soon.As long as the first argument, the one assigned to
coffee_type
, is positional, any further positional arguments are assigned to the tupleadd_ons
.Next, you can add named arguments (which is another term used for keyword arguments) for
strength
andmilk_amount
. Both of these arguments are optional, and the order in which you use them in a function call is not important.Finally, you can add more keyword arguments using keywords that aren't parameters in the function definition. You can include as many keyword arguments as you wish.
Read point 1 above again. Alex thinks that allowing the first argument to be either positional or named is not a good idea, as it can lead to confusion. You can only use the first argument as a keyword argument if you don't have add-ons. Here's proof:
The first argument is a keyword argument, coffee_type="Cappuccino"
. But then you attempt to pass two positional arguments, chocolate sprinkles
and vanilla syrup
. This call raises an error:
File "...", line 25
)
^
SyntaxError: positional argument follows
keyword argument
You can't have positional arguments following keyword arguments.
Alex decides to remove this source of confusion by ensuring that the argument assigned to coffee_type
is always a positional argument. He only needs to make a small addition to the function's signature:
The rogue forward slash, /
, in place of a parameter is not a typo. It indicates that all parameters before the forward slash must be assigned positional arguments. Therefore, the object assigned to coffee_type
can no longer be a keyword argument:
The first argument is a keyword argument. But this call raises an error:
Traceback (most recent call last):
File "...", line 19, in <module>
brew_coffee(
~~~~~~~~~~~^
coffee_type="Cappuccino",
^^^^^^^^^^^^^^^^^^^^^^^^^
...<2 lines>...
cup_size="large cup",
^^^^^^^^^^^^^^^^^^^^^
)
^
TypeError: brew_coffee() missing 1 required
positional argument: 'coffee_type'
The function has a required positional argument, the one assigned to coffee_type
. The forward slash, /
, makes the first argument a positional-only argument. It can no longer be a keyword argument:
This version works fine since the first argument is positional:
Coffee type: Cappuccino
Strength: 3
Milk Amount: 2
Add-ons:
Instructions:
temperature: extra hot
cup size: large cup
Alex feels that this function's signature is neater and clearer now, avoiding ambiguity.
• • •
The R&D team at the startup that's developing the Espressix ProtoSip were keen to see how Alex was using the prototype and inspect the changes he made to suit his needs. They implemented many of Alex's changes.
However, they were planning to offer a more basic version of the Espressix that didn't have the option to include add-ons in the coffee.
The easiest option is to remove the *add-ons
parameter from the function's signature:
No *add_ons
parameter, no add-ons in the coffee.
Sorted? Sort of.
The *add_ons
parameter enabled you to pass optional positional arguments. However, *add_ons
served a second purpose in the earlier version. All parameters after the args parameter, which is *add_ons
in this example, must be assigned keyword arguments. The args parameter, *add_ons
, forces all remaining parameters to be assigned keyword-only arguments.
Removing the *add_ons
parameter changes the rules for the remaining arguments.
But you can still implement the same rules even when you're not using args. All you need to do is keep the leading asterisk but drop the parameter name:
Remember to remove the line printing out the add-ons, too. That’s the second of the highlighted lines in the code above.
Notice how there's a lone asterisk in one of the parameter slots in the function signature. You can confirm that strength
and milk_amount
still need to be assigned keyword arguments:
When you try to pass positional arguments to strength
and milk_amount
, the code raises an error:
Traceback (most recent call last):
brew_coffee(
~~~~~~~~~~~^
"Espresso",
^^^^^^^^^^^
3,
^^
0,
^^
)
^
TypeError: brew_coffee() takes 1 positional argument
but 3 were given
The error message tells you that brew_coffee()
only takes one positional argument. All the arguments after the *
are keyword-only. Therefore, only the arguments preceding it may be positional. And there's only one parameter before the rogue asterisk, *
.
A lone forward slash,
/
, among the function's parameters indicates that all parameters before the forward slash must be assigned positional-only arguments.A lone asterisk,
*
, among the function's parameters indicates that all parameters after the asterisk must be assigned keyword-only arguments.
If you re-read the statements above carefully, you'll conclude that when you use both /
and *
in a function definition, the /
must come before the *
. Recall that positional arguments must come before keyword arguments.
It's also possible to have parameters between the /
and *
:
You add a new parameter, another_param
, in between /
and *
in the function's signature. Since this parameter is sandwiched between /
and *
, you can choose to assign either a positional or a keyword argument to it.
Here's a function call with the second argument as a positional argument:
The second positional argument is assigned to another_param
.
But you can also use a keyword argument:
Both of these versions give the same output:
Coffee type: Espresso
another_param='testing another parameter'
Strength: 4
Milk Amount: 0
Instructions:
Any parameter between /
and *
in the function definition can have either positional or keyword arguments. So, in summary:
Arguments assigned to parameters before a forward slash,
/
, are positional-only.Arguments assigned to parameters between a forward slash,
/
, and an asterisk,*
, can be either positional or keyword.Arguments assigned to parameters after an asterisk,
*
, are keyword-only.
Remember that the *
serves a similar purpose as the asterisk in *args
since both *
and *args
force any parameters that come after them to require keyword-only arguments. Remember this similarity if you find yourself struggling to remember what /
and *
do!
Why use positional-only or keyword-only arguments? Positional-only arguments (using /
) ensure clarity and prevent misuse in APIs where parameter names are irrelevant to the user. Keyword-only arguments (using *
) improve readability and avoid errors in functions with many parameters, as names make the intent clear. For Alex, making coffee_type
positional-only and strength
and milk_amount
keyword-only simplifies the API by enforcing a consistent calling style, reducing confusion for his team.
Using positional-only arguments may also be beneficial in performance-critical code since the overhead to deal with keyword arguments is not negligible in these cases.
Do you want to join a forum to discuss Python further with other Pythonistas? Upgrade to a paid subscription here on The Python Coding Stack to get exclusive access to The Python Coding Place's members' forum. More Python. More discussions. More fun.
And you'll also be supporting this publication. I put plenty of time and effort into crafting each article. Your support will help me keep this content coming regularly and, importantly, will help keep it free for everyone.
Final Words
The reporter from The Herald did manage to chat to Alex eventually. She had become a regular at AI Coffee, and ever since Alex employed more staff, he's been able to chat to customers a bit more.
"There's a question I'm curious about", she asked. "How does the Artificial Intelligence software work to make the coffee just perfect for each customer?"
"I beg your pardon?" Alex looked confused.
"I get it. It's a trade secret, and you don't want to tell me. This Artificial Intelligence stuff is everywhere these days."
"What do you mean by Artificial Intelligence?" Alex asked, more perplexed.
"The machine uses AI to optimise the coffee it makes, right?"
"Er, no. It does not."
"But…But the name of the coffee shop, AI Coffee…?"
"Ah, that's silly, I know. I couldn't think of a name for the shop. So I just used my initials. I'm Alex Inverness."
• • •
Python functions offer lots of flexibility in how to define and use them. But function signatures can look cryptic with all the *args
and **kwargs
, rogue /
and *
, some parameters with default values and others without. And the rules on when and how to use arguments may not be intuitive at first.
Hopefully, Alex's story helped you grasp all the minutiae of the various types of parameters and arguments you can use in Python functions.
Now, I need to make myself a cup of coffee…
Photo by Viktoria Alipatova: https://www.pexels.com/photo/person-sitting-near-table-with-teacups-and-plates-2074130/
Code in this article uses Python 3.13
The code images used in this article are created using Snappify. [Affiliate link]
You can also support this publication by making a one-off contribution of any amount you wish.
For more Python resources, you can also visit Real Python—you may even stumble on one of my own articles or courses there!
Also, are you interested in technical writing? You’d like to make your own writing more narrative, more engaging, more memorable? Have a look at Breaking the Rules.
And you can find out more about me at stephengruppetta.com
Further reading related to this article’s topic:
Coffee features often when talking about programming. Here's another coffee-themed article, also about functions: What Can A Coffee Machine Teach You About Python's Functions?
Python Quirks? Party Tricks? Peculiarities Revealed… [in particular see section 2, The Teleportation Trick]
Appendix: Code Blocks
Code Block #1
brew_coffee("Cappuccino", 4, 2)
Code Block #2
brew_coffee(coffee_type: str, strength: int, milk_amount: int)
Code Block #3
brew_coffee("Americano", 1, 4)
Code Block #4
brew_coffee(coffee_type, strength, milk_amount)
Code Block #5
def brew_coffee(coffee_type: str, strength: int, milk_amount: int):
print(
f"Coffee type: {coffee_type}\n"
f"Strength: {strength}\n"
f"Milk Amount: {milk_amount}\n"
)
brew_coffee("Americano", 1, 4)
Code Block #6
brew_coffee("Americano", milk_amount=1, strength=4)
Code Block #7
brew_coffee("Americano", milk_amount=1, strength=4)
Code Block #8
brew_coffee("Americano", milk_amount=1, 4)
Code Block #9
def brew_coffee(coffee_type, strength, milk_amount, *args):
print(
f"Coffee type: {coffee_type}\n"
f"Strength: {strength}\n"
f"Milk Amount: {milk_amount}\n"
f"Add-ons: {', '.join(args)}\n"
)
Code Block #10
brew_coffee("Latte", 3, 2, "cinnamon", "hazelnut syrup")
Code Block #11
def brew_coffee(coffee_type, strength, milk_amount, *args):
print(f"{args=}")
print(type(args))
print(
f"Coffee type: {coffee_type}\n"
f"Strength: {strength}\n"
f"Milk Amount: {milk_amount}\n"
f"Add-ons: {', '.join(args)}\n"
)
brew_coffee("Latte", 3, 2, "cinnamon", "hazelnut syrup")
Code Block #12
def brew_coffee(coffee_type, strength, milk_amount, *add_ons):
print(f"{add_ons=}")
print(type(add_ons))
print(
f"Coffee type: {coffee_type}\n"
f"Strength: {strength}\n"
f"Milk Amount: {milk_amount}\n"
f"Add-ons: {', '.join(add_ons)}\n"
)
brew_coffee("Latte", 3, 2, "cinnamon", "hazelnut syrup")
Code Block #13
brew_coffee("Latte", 3, 2)
Code Block #14
def brew_coffee(coffee_type, strength, milk_amount, *add_ons):
print(
f"Coffee type: {coffee_type}\n"
f"Strength: {strength}\n"
f"Milk Amount: {milk_amount}\n"
f"Add-ons: {', '.join(add_ons)}\n"
)
brew_coffee("Latte", strength=3, milk_amount=2, "vanilla syrup")
Code Block #15
def brew_coffee(coffee_type, *add_ons, strength, milk_amount):
print(
f"Coffee type: {coffee_type}\n"
f"Strength: {strength}\n"
f"Milk Amount: {milk_amount}\n"
f"Add-ons: {', '.join(add_ons)}\n"
)
brew_coffee("Latte", "vanilla syrup", strength=3, milk_amount=2)
Code Block #16
brew_coffee("Latte", "vanilla syrup", 3, 2)
Code Block #17
def brew_coffee(
coffee_type,
*add_ons,
strength,
milk_amount,
**kwargs,
):
print(f"{kwargs=}")
print(type(kwargs))
print(
f"Coffee type: {coffee_type}\n"
f"Strength: {strength}\n"
f"Milk Amount: {milk_amount}\n"
f"Add-ons: {', '.join(add_ons)}\n"
f"Instructions:"
)
for key, value in kwargs.items():
print(f"\t{key.replace('_', ' ')}: {value}")
Code Block #18
brew_coffee(
"Latte",
"vanilla syrup",
strength=3,
milk_amount=2,
milk_type="oat",
temperature="extra hot",
)
Code Block #19
def brew_coffee(
coffee_type,
*add_ons,
strength,
milk_amount,
**instructions,
):
print(f"{instructions=}")
print(type(instructions))
print(
f"Coffee type: {coffee_type}\n"
f"Strength: {strength}\n"
f"Milk Amount: {milk_amount}\n"
f"Add-ons: {', '.join(add_ons)}\n"
f"Instructions:"
)
for key, value in instructions.items():
print(f"\t{key.replace('_', ' ')}: {value}")
Code Block #20
brew_coffee("Espresso", strength=4, milk_amount=0)
Code Block #21
brew_coffee(coffee_type="Espresso", strength=4, milk_amount=0)
Code Block #22
def brew_coffee(
coffee_type,
*add_ons,
strength=3,
milk_amount,
**instructions,
):
# ...
Code Block #23
brew_coffee("Espresso", milk_amount=0)
Code Block #24
brew_coffee("Latte", "caramel drizzle", milk_amount=2)
Code Block #25
def brew_coffee(
coffee_type,
*add_ons,
strength=3,
milk_amount,
**instructions,
):
# ...
Code Block #26
def brew_coffee_variant(
coffee_type,
# *add_ons,
strength=3,
milk_amount,
**instructions,
):
print(
f"Coffee type: {coffee_type}\n"
f"Strength: {strength}\n"
f"Milk Amount: {milk_amount}\n"
# f"Add-ons: {', '.join(add_ons)}\n"
f"Instructions:"
)
for key, value in instructions.items():
print(f"\t{key.replace('_', ' ')}: {value}")
brew_coffee_variant("Espresso", milk_amount=0)
Code Block #27
brew_coffee_variant("Espresso", 0)
Code Block #28
def brew_coffee(
coffee_type,
*add_ons,
strength=3,
milk_amount,
**instructions,
):
print(
f"Coffee type: {coffee_type}\n"
f"Strength: {strength}\n"
f"Milk Amount: {milk_amount}\n"
f"Add-ons: {', '.join(add_ons)}\n"
f"Instructions:"
)
for key, value in instructions.items():
print(f"\t{key.replace('_', ' ')}: {value}")
brew_coffee("Espresso", milk_amount=0)
Code Block #29
def brew_coffee(
coffee_type,
*add_ons,
strength=3,
milk_amount=0,
**instructions,
):
print(
f"Coffee type: {coffee_type}\n"
f"Strength: {strength}\n"
f"Milk Amount: {milk_amount}\n"
f"Add-ons: {', '.join(add_ons)}\n"
f"Instructions:"
)
for key, value in instructions.items():
print(f"\t{key.replace('_', ' ')}: {value}")
brew_coffee("Espresso")
Code Block #30
brew_coffee(
"Cappuccino",
"chocolate sprinkles",
"vanilla syrup",
milk_amount=2,
temperature="extra hot",
cup_size="large cup",
)
Code Block #31
def brew_coffee(
coffee_type,
*add_ons,
strength=3,
milk_amount=0,
**instructions,
):
# ...
Code Block #32
brew_coffee(
coffee_type="Cappuccino",
"chocolate sprinkles",
"vanilla syrup",
milk_amount=2,
temperature="extra hot",
cup_size="large cup",
)
Code Block #33
def brew_coffee(
coffee_type,
/,
*add_ons,
strength=3,
milk_amount=0,
**instructions,
):
# ...
Code Block #34
brew_coffee(
coffee_type="Cappuccino",
milk_amount=2,
temperature="extra hot",
cup_size="large cup",
)
Code Block #35
brew_coffee(
"Cappuccino",
milk_amount=2,
temperature="extra hot",
cup_size="large cup",
)
Code Block #36
def brew_coffee(
coffee_type,
/,
# *add_ons,
strength=3,
milk_amount=0,
**instructions,
):
Code Block #37
def brew_coffee(
coffee_type,
/,
*,
strength=3,
milk_amount=0,
**instructions,
):
print(
f"Coffee type: {coffee_type}\n"
f"Strength: {strength}\n"
f"Milk Amount: {milk_amount}\n"
# f"Add-ons: {', '.join(add_ons)}\n"
f"Instructions:"
)
for key, value in instructions.items():
print(f"\t{key.replace('_', ' ')}: {value}")
brew_coffee(
"Cappuccino",
milk_amount=2,
temperature="extra hot",
cup_size="large cup",
)
Code Block #38
brew_coffee(
"Espresso",
3,
0,
)
Code Block #39
def brew_coffee(
coffee_type,
/,
another_param,
*,
strength=3,
milk_amount=0,
**instructions,
):
print(
f"Coffee type: {coffee_type}\n"
f"{another_param=}\n"
f"Strength: {strength}\n"
f"Milk Amount: {milk_amount}\n"
# f"Add-ons: {', '.join(add_ons)}\n"
f"Instructions:"
)
for key, value in instructions.items():
print(f"\t{key.replace('_', ' ')}: {value}")
Code Block #40
brew_coffee(
"Espresso",
"testing another parameter",
strength=4,
)
Code Block #41
brew_coffee(
"Espresso",
another_param="testing another parameter",
strength=4,
)
Code Block #42
brew_coffee(
"Macchiato",
strength=4,
milk_amount=1,
cup="Stephen's espresso cup",
)
For more Python resources, you can also visit Real Python—you may even stumble on one of my own articles or courses there!
Also, are you interested in technical writing? You’d like to make your own writing more narrative, more engaging, more memorable? Have a look at Breaking the Rules.
And you can find out more about me at stephengruppetta.com