avatarYanick Andrade

Summary

The provided text explains the purpose and functionality of slash (/) and asterisk (*) in Python function definitions, detailing their roles in distinguishing between positional and keyword arguments to improve code clarity, maintainability, and performance.

Abstract

Python's function definition syntax allows for special parameters denoted by slashes (/) and asterisks (*). The asterisk is used to define keyword-only arguments, ensuring that certain parameters can only be provided by name, thus preventing errors and ambiguity in argument order. Conversely, the slash is used to specify positional-only parameters, which can only be passed by position, not by name. This feature enhances code optimization by reducing the overhead associated with handling empty keyword arguments and aligns with the design of many built-in Python functions that accept positional-only arguments for performance reasons. The article emphasizes that understanding these concepts not only improves coding practices but also leverages Python's capabilities to write more efficient and maintainable code.

Opinions

  • The author views the use of asterisks for keyword-only arguments as a way to avoid the confusion that arises from the incorrect ordering of positional arguments, thereby making the code more robust.
  • The introduction of the slash for defining positional-only parameters is seen as a beneficial feature that aligns with Python's design philosophy of explicit is better than implicit.
  • The article suggests that the ability to restrict argument types (positional or keyword) can lead to performance improvements, referencing Python's built-in functions as examples.
  • The author advocates for the use of positional arguments when the parameter names are subject to change, as it allows for flexibility without breaking user code.
  • The text implies that the adoption of these syntax features can lead to better coding habits and a deeper understanding of Python's function call mechanics.
  • The article encourages readers to embrace these features as they can contribute to writing code that is easier to maintain over time.

What Slash (/) And Asterisk (*) Are In A Function Definition — Python

Python Special Parameters

Photo by Dušan veverkolog on Unsplash

Not a member yet? You can still this for free here

Have you ever seen a Python code that looks like this?

def function(name, age, *, job, salary):
    ...

Or one that looks like this:

def function(name, age, /, job, salary):
    ...

I saw the first one I believe one year ago or more, and it was so intriguing for me that I had to find out more about it and what is its purpose.

The second one, on the other hand, I only recently came across while reading things about Python3.12.

No, it’s not a feature new to Python 3.12.

By the end of this article, you will learn the following:

  • What does slash mean in function definition
  • What does the asterisk mean in a function definition

Understanding these two concepts can help you improve your code, improve performance, and make your code easier to maintain over time.

Without further ado, let’s get into them.

Slash and asterisk as operators

As you probably know, slash — / and asterisk — * are operators in Python. They are used to perform mathematical operations — division and multiplication respectively.

def divide_by_n[T: (int, float)](n: T) -> T:
    return n / n # divide n by n

def multiply_by_n[T: (int, float)](n: T) -> T:
    return n * n

Our functions receive a generic type that either be an int or float and returns the same type as the one received.

Asterisk (*) In Function Definition

There are three ways to use the asterisk in a function definition. a) We use it to represent a tupleargs — of arguments b) to represent a dictionary — kwargs — of arguments and c) to define a function that receives only keyword arguments.

def func(*args, **kwargs): # represents a) and b)
    print(args, kwargs)

print(func(1, 2, first_name="Jane", last_name="Doe"))
# (1, 2), {'first_name': 'Jane', 'last_name': 'Doe'}

But what we are interested in, is the option c). Using an asterisk to define keyword-only arguments.

But first, let’s define keyword arguments to ensure we are all on the same page.

By keyword arguments, we mean calling a function and sending arguments by the parameters’ name.

def func(first_name: str, last_name: str):
    print(f'Hello, {first_name} {last_name}')

Our function func has two parameters — first_name and last_name. So calling by sending arguments by keyword, means that we explicitly specify which value we are assigning to which parameter:

func(last_name='Doe', first_name='Jane')
# Hello, Jane Doe

As you can see, using keyword arguments we can send values in a different order from the way we defined our function with.

But since our function accepts both keyword arguments and positional arguments (we will define in the next section), if we call our function using positional arguments we might run into problems:

func('Doe', 'Jane')
# Hello, Doe Jane

This is not the output we expected from our function.

So how can we avoid situations like this when we want to make sure arguments are sent properly?

The solution here is to define our function in a way that it only accepts keyword arguments.

That’s where an asterisk in the function definition can be very handy as well.

def func(*, first_name: str, last_name: str): # asterisk in parameters
    print(f'Hello, {first_name} {last_name}')

By defining our function with an asterisk in the parameters definition, we are saying that everything that comes after it — on the right side — are keyword arguments only.

func('Jane', last_name='Doe') ❌
# TypeError: func() takes 0 positional arguments 
# but 1 positional argument (and 1 keyword-only argument) were given

func(first_name='Jane', last_name='Doe') ✅
# Hello, Jane Doe

So any attempt to call func with positional arguments in this case results in a TypeError.

— Mixing positional and keyword arguments

Using an asterisk in the function definition means that everything after it will be keyword only.

This means that everything before it can be either positional or keyword.

def func(message: str, *, first_name: str, last_name: str):
    print(f'{message}, {first_name} {last_name}')


func('Welcome back', first_name='Jane', last_name='Doe') # positional message
# Welcome back, Jane Doe

func(message='Hi', first_name='Jane', last_name='Doe') # keyword message
# Hi, Jane Doe

Slash (/) In Function Definition

Back in the day, Python only supported positional arguments by default. It was not possible to choose whether you call a function and use the parameter(s) name or send by position:

def divide_by_n[T: (int, float)](n: T) -> T:
    return n / n # divide n by n
 
divide_by_n(10) # call and send argument by position

It was only in Python 1.0 that it became possible to call a function and use the parameters’ name or position:

divide_by_n(n=10) # call using name in arguments

If you’re wondering where T in function divide_by_n comes from, it is a new feature from Python 3.12 to create generic types. You can read more about it here

Introduced in PEP 570, slash gives the ability to say that our function only accepts positional arguments.

# using slash in a function definition
def divide_by_n[T: (int, float)](n: T, /) -> T:
    return n / n # divide n by n

This means that everything on the left side of our slash can only be positional arguments. It’s the opposite of the asterisk that we saw earlier.

“how about using ‘/’ ? It’s kind of the opposite of ‘*’ which means “keyword argument”, and ‘/’ is not a new character.” — Guido van Rossum, PEP 570

def func(first_name: str, last_name: str, /):
    print(f'Welcome, {first_name} {last_name}')

In this case, we are explicitly saying that our function should be called with positional arguments only. So the user calling it should be aware of the outcome in case the arguments are sent in the wrong position.

func('Jane', 'Doe') ✅
# f'Welcome, Jane Doe

func('Jane', last_name='Doe') ❌
# TypeError: func() got some positional-only arguments passed as keyword arguments: 'last_name'

— Why should we restrict to positional arguments only?

One of the questions you might ask is why we should stick to positional arguments only.

  • Optimization

There’s a method called convention name METH_FASTCALL, which is responsible for supporting positional arguments.

This method, according to PEP 570, has been specialized for a function that accepts positional-only arguments. This improvement means a reduction of the cost that Python has to handle empty keywords.

One of the reasons we see so many built-in functions accepting positional-only arguments (like int, float, and str), is because of the performance improvement they can give.

  • Maintainability

Using positional arguments gives us the flexibility to change the name of our arguments without the fear of breaking things.

def func(f_name: str, l_name: str, /):
    print(f'Welcome, {f_name} {l_name}')

Like in this example, where we previously had as parameters first_name and last_name.

Changing the parameters’ name does not affect the users’ code when interacting with our function.

func('Jane', 'Doe')
# f'Welcome, Jane Doe

More good reasons on why and when to use it can be found in this well-documented PEP.

Final Thoughts

One of the things we love about Python is the flexibility to write our code and mix things. The ability to focus on getting things done without worrying too much about things that ‘do not matter’.

Knowing things like these can improve the way we code and make us a better Python programmer.

When deciding to use one of these, understand what you want to accomplish and how your code will evolve.

Resources used in this article:

Hi! Thank you for your time reading my article. If you enjoyed this article and would like to receive similar content directly to your inbox

In Plain English 🚀

Thank you for being a part of the In Plain English community! Before you go:

Python
Coding
Programming
Python3
Python Programming
Recommended from ReadMedium