How many arguments should a function have in Python?
One of the clearest signs that a function is doing too much is that it has too many arguments. When there are many arguments, the code becomes hard to read, test, and document. What is the limit?
A simple rule that works well in practice:
- Ideal: 0 to 2 arguments.
- Acceptable: 3 or 4 (if well justified or using default values).
- Bad design: 5 to infinity.
But there are functions that need more arguments — for example, when creating a user, you need first name, last name, email, phone, address... There must be a solution that does not break the 4-argument rule.
Here are some tools to consider. You decide which one fits your case best.
Dataclasses
If you have several related arguments, group them into a dataclass. The function receives a single object with everything it needs.
from dataclasses import dataclass
@dataclass
class Address:
"""Full postal address of a user.
Attributes:
street: Street name and number.
city: City of residence.
zip_code: Postal code of the area.
"""
street: str
city: str
zip_code: str
def create_user(
first_name: str, last_name: str, email: str, phone: str, address: Address
) -> None:
"""Create a new user in the system.
Args:
first_name: User's first name.
last_name: Last name.
email: Email address.
phone: Contact phone number.
address: Full postal address.
"""
# Your logic here
Keyword-only arguments
The * in the signature forces everything that comes after it to be passed by name. Its main advantage is that it improves readability, since the code that calls the function is more explicit.
def configure_chart(
title: str,
*,
width: int = 800,
height: int = 600,
line_color: str = "blue",
thickness: int = 2,
) -> None:
"""Configure and render a chart.
Args:
title: Chart title.
width: Width in pixels, default 800.
height: Height in pixels, default 600.
line_color: Main line color, default "blue".
thickness: Line thickness in points, default 2.
"""
pass
configure_chart("My Chart", width=1024, line_color="red")
Validation with msgspec
Work with a data validation library, for example msgspec. If you have used pydantic, you will feel right at home. Think of msgspec as a modern validator and parser, lightweight (written in C) and very efficient.
import msgspec
class User(msgspec.Struct, strict=True):
"""User model with strict type validation.
Attributes:
name: Full name of the user.
age: Age in years.
"""
name: str
age: int
input_data = {"name": "Ana", "age": 28}
validated_user = msgspec.convert(input_data, type=User)
With strict=True, if a type does not match exactly, it raises an error. No surprises.
Final notes
I have not included *args and **kwargs because they only hide the arguments. They are useful in specific contexts (decorators, wrappers, very generic APIs), but they are not an excuse to avoid thinking about design.
Hopefully, before adding a fifth argument, you ask yourself whether it really belongs there or whether it is a sign that the function is taking on too many responsibilities.
This work is under a Attribution-NonCommercial-NoDerivatives 4.0 International license.
Support me on Ko-fi
Comments
There are no comments yet.