Back to Blog
Python

Python Constructor Arguments: __init__ Parameters Explained

python constructor arguments: Learn how Python constructor arguments work in __init__: positional vs keyword, defaults, *args/**kwargs, validation, and common pitfalls.

Python OOP__init__function argumentsdefault valueskeyword argumentsmutable defaults
A Python class diagram showing the __init__ method receiving constructor arguments and assigning them to instance attributes.

When you define a class in Python, the __init__ method determines how python constructor arguments are accepted and processed. This method runs immediately after the instance is created, and its parameters control what data the caller must supply and how that data becomes part of the object's state. Getting the signature right matters because it shapes the public API of your class and affects how easily the class can be extended, tested, and maintained.

The Role of init in Python Constructors

In Python, __init__ is not a true constructor in the C++ or Java sense; the actual allocation happens in __new__. But for almost all practical purposes, __init__ is where you initialize instance attributes from the arguments passed to the class. When you call MyClass(arg1, arg2), Python creates a new instance and then invokes __init__ with those same arguments.

The simplest constructor takes no arguments except self, but that is rare. Typically you define parameters that map to instance attributes:

class Point: def __init__(self, x, y): self.x = x self.y = y

The parameters x and y are required. Calling Point(3, 4) works, but Point(3) raises TypeError because y is missing. This behavior is the same as for any Python function, which means all the argument-passing rules you already know apply directly to constructors.

Passing Positional and Keyword Arguments

Constructor arguments can be passed positionally or by keyword, just like regular function calls. Both of these create an identical Point object:

p1 = Point(3, 4) p2 = Point(x=3, y=4)

Mixing positional and keyword arguments is allowed as long as positional arguments come first. This flexibility is useful when a class has many parameters and callers want to specify only some of them by name. However, it also means that changing the parameter order later can break existing positional calls, so think carefully about the order you choose.

If you want to force callers to use keyword arguments for certain parameters, you can use the * separator in the parameter list. This is covered later in the keyword-only section.

Default Values and the Mutable Default Pitfall

Parameters can have default values, making them optional. This is common for configuration options or optional dependencies:

class Circle: def __init__(self, radius, color="red"): self.radius = radius self.color = color

Here color is optional, and Circle(5) and Circle(5, "blue") both work. Defaults are evaluated once when the function is defined, not each time the constructor is called. That behavior is usually harmless for immutable values like numbers, strings, or tuples, but it causes a classic bug when the default is a mutable object such as a list or dictionary.

Consider this class:

class ShoppingCart: def __init__(self, items=[]): self.items = items

Every instance that does not pass items will share the same list object. Adding an item to one cart changes the default for all future carts. The correct pattern is to use None as the default and create a new list inside the method:

class ShoppingCart: def __init__(self, items=None): self.items = items if items is not None else []

This avoids the shared-state bug and is the standard way to handle mutable defaults in Python.

Using *args and **kwargs for Flexible Constructors

Sometimes a constructor needs to accept an arbitrary number of positional arguments or keyword arguments. The *args parameter collects extra positional arguments into a tuple, and **kwargs collects extra keyword arguments into a dictionary. This is useful for wrappers, decorators, or classes that forward arguments to another object.

class Logger: def __init__(self, *args, **kwargs): self.messages = list(args) self.options = kwargs

This constructor accepts any call signature, but it also makes the API less explicit. Callers cannot see from the signature which arguments are valid, and typos in keyword names will not raise errors immediately. Use *args and **kwargs when the set of arguments is genuinely dynamic, such as when building a generic event dispatcher or a proxy that forwards to an underlying implementation. For most classes, explicit parameters are better because they document the expected inputs and let the interpreter catch mistakes early.

Keyword-Only Arguments for Clearer APIs

Python allows you to force certain parameters to be passed only by keyword. Place a bare * in the parameter list before those parameters:

class Server: def __init__(self, host, port, *, timeout=30, retries=3): self.host = host self.port = port self.timeout = timeout self.retries = retries

Now timeout and retries must be passed as keyword arguments. This prevents callers from accidentally passing timeout as a third positional argument, which would be easy to mix up if the class later adds another positional parameter. Keyword-only arguments are especially valuable when a class has many optional settings that are rarely supplied positionally.

Validation and Normalization Inside init

The constructor is the right place to validate input and normalize it into a consistent internal representation. If you accept a string for a field that will be used as an integer, convert it early so the rest of the class can rely on the type. If a value must be within a certain range, check it before assigning it to an attribute.

class Temperature: def __init__(self, celsius): if not isinstance(celsius, (int, float)): raise TypeError("celsius must be a number") if celsius < -273.15: raise ValueError("temperature below absolute zero") self.celsius = celsius

Raising exceptions early prevents invalid objects from existing, which simplifies every method that uses the object later. However, avoid over-validating: if the class is internal and the inputs are always trusted, extra checks add noise. Use validation when the constructor is part of a public API or when invalid values would cause subtle failures far from the point of creation.

Performance and Maintainability Considerations

Constructor arguments affect both runtime behavior and code maintainability. On the performance side, the cost of argument processing is usually negligible compared to the work done inside __init__, but there are a few patterns that can matter in hot paths. For example, using **kwargs to forward a large dictionary repeatedly can add overhead because the dictionary is rebuilt and unpacked on each call. If you know the exact set of arguments, explicit parameters are faster and more readable.

Maintainability is where constructor signatures have the biggest impact. A class with dozens of parameters is hard to use and test. If you find yourself adding many optional parameters, consider grouping related ones into a configuration object or using keyword-only arguments with sensible defaults. Also remember that changing the parameter order or removing a parameter is a breaking change for positional callers, so design the signature with future evolution in mind.

One common maintainability trap is using *args and **kwargs to pass arguments through several layers of wrapper classes. This makes it difficult to trace where a value comes from and what types are expected. Prefer explicit forwarding when the number of parameters is bounded, and reserve **kwargs for cases where the set of options is genuinely open-ended, such as a plugin system that must remain compatible with unknown future options.

Another consideration is the interaction between constructor arguments and inheritance. When a subclass calls super().__init__(), it must pass the arguments the parent expects. If the parent signature changes, all subclasses must be updated. Using keyword-only parameters in the parent can reduce this risk because subclasses can pass them by name without depending on positional order.

Finally, remember that __init__ is just a method. You can call it explicitly, but that is rarely useful. The normal flow is to let the class call it for you. If you need alternative ways to create an instance, consider class methods like from_dict or from_file that construct the object with a specific set of constructor arguments. This keeps the primary constructor simple and moves complex parsing logic to separate, well-named methods.

python constructor arguments: Practical Usage and Code Examp | RYUSLOG DEV