Recommended Video Course
Using Python Class Constructors
Table of Contents
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding:Using Python Class Constructors
Creating a class constructor in Python involves understanding the instantiation process, which consists of two steps: instance creation and instance initialization. You start this process by calling the class like a function, which triggers the.__new__()
method to create an instance and the.__init__()
method to initialize it. Mastering these methods allows you to customize how Python constructs and initializes objects of your classes.
By the end of this tutorial, you’ll understand that:
.__new__()
for creation and.__init__()
for initialization..__init__()
method in your class..__new__()
and.__init__()
is that.__new__()
creates the instance, while.__init__()
initializes it..__new__()
include subclassing immutable types or implementing singletons.To better understand the examples and concepts in this tutorial, you should be familiar withobject-oriented programming andspecial methods in Python.
Free Bonus:Click here to get access to a free Python OOP Cheat Sheet that points you to the best tutorials, videos, and books to learn more about Object-Oriented Programming with Python.
Take the Quiz: Test your knowledge with our interactive “Python Class Constructors: Control Your Object Instantiation” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Python Class Constructors: Control Your Object InstantiationIn this quiz, you'll test your understanding of class constructors in Python. By working through this quiz, you'll revisit the internal instantiation process, object initialization, and fine-tuning object creation.
Like many other programming languages, Python supportsobject-oriented programming. At the heart of Python’s object-oriented capabilities, you’ll find theclass
keyword, which allows you to define customclasses that can haveattributes for storing data andmethods for providing behaviors.
Once you have a class to work with, then you can start creating newinstances orobjects of that class, which is an efficient way to reuse functionality in your code.
Creating and initializing objects of a given class is a fundamental step in object-oriented programming. This step is often referred to asobject construction orinstantiation. The tool responsible for running this instantiation process is commonly known as aclass constructor.
In Python, to construct an object of a given class, you just need to call the class with appropriate arguments, as you would call anyfunction:
>>>classSomeClass:...pass...>>># Call the class to construct an object>>>SomeClass()<__main__.SomeClass object at 0x7fecf442a140>
In this example, you defineSomeClass
using theclass
keyword. This class is currently empty because it doesn’t have attributes or methods. Instead, the class’s body only contains apass
statement as a placeholder statement that does nothing.
Then you create a new instance ofSomeClass
by calling the class with a pair of parentheses. In this example, you don’t need to pass any argument in the call because your class doesn’t take arguments yet.
In Python, when you call a class as you did in the above example, you’re calling the class constructor, which creates, initializes, and returns a new object by triggering Python’s internal instantiation process.
A final point to note is that calling a class isn’t the same as calling aninstance of a class. These are two different and unrelated topics. Tomake a class’s instance callable, you need to implement a.__call__()
special method, which has nothing to do with Python’s instantiation process.
You trigger Python’sinstantiation process whenever you call a Python class to create a new instance. This process runs through two separate steps, which you can describe as follows:
To run the first step, Python classes have a special method called.__new__()
, which is responsible for creating and returning a new empty object. Then another special method,.__init__()
, takes the resulting object, along with the class constructor’s arguments.
The.__init__()
method takes the new object as its first argument,self
. Then it sets any required instance attribute to a valid state using the arguments that the class constructor passed to it.
In short, Python’s instantiation process starts with a call to the class constructor, which triggers theinstance creator,.__new__()
, to create a new empty object. The process continues with theinstance initializer,.__init__()
, which takes the constructor’s arguments to initialize the newly created object.
To explore how Python’s instantiation process works internally, consider the following example of aPoint
class that implements a custom version of both methods,.__new__()
and.__init__()
, for demonstration purposes:
point.py
1classPoint: 2def__new__(cls,*args,**kwargs): 3print("1. Create a new instance of Point.") 4returnsuper().__new__(cls) 5 6def__init__(self,x,y): 7print("2. Initialize the new instance of Point.") 8self.x=x 9self.y=y1011def__repr__(self)->str:12returnf"{type(self).__name__}(x={self.x}, y={self.y})"
Here’s a breakdown of what this code does:
Line 1 defines thePoint
class using theclass
keyword followed by the class name.
Line 2 defines the.__new__()
method, which takes the class as its first argument. Note that usingcls
as the name of this argument is a strong convention in Python, just like usingself
to name the current instance is. The method also takes*args
and**kwargs
, which allow for passing an undefined number of initialization arguments to the underlying instance.
Line 3prints a message when.__new__()
runs the object creation step.
Line 4 creates a newPoint
instance by calling the parent class’s.__new__()
method withcls
as an argument. In this example,object
is the parent class, and the call tosuper()
gives you access to it. Then the instance is returned. This instance will be the first argument to.__init__()
.
Line 6 defines.__init__()
, which is responsible for the initialization step. This method takes a first argument calledself
, which holds a reference to the current instance. The method also takes two additional arguments,x
andy
. These arguments hold initial values for the instance attributes.x
and.y
. You need to pass suitable values for these arguments in the call toPoint()
, as you’ll learn in a moment.
Line 7 prints a message when.__init__()
runs the object initialization step.
Lines 8 and 9 initialize.x
and.y
, respectively. To do this, they use the provided input argumentsx
andy
.
Lines 11 and 12 implement the.__repr__()
special method, which provides a proper string representation for yourPoint
class.
WithPoint
in place, you can uncover how the instantiation process works in practice. Save your code to a file calledpoint.py
andstart your Python interpreter in a command-line window. Then run the following code:
>>>frompointimportPoint>>>point=Point(21,42)1. Create a new instance of Point.2. Initialize the new instance of Point.>>>pointPoint(x=21, y=42)
Calling thePoint()
class constructor creates, initializes, and returns a new instance of the class. This instance is then assigned to thepoint
variable.
In this example, the call to the constructor also lets you know the steps that Python internally runs to construct the instance. First, Python calls.__new__()
and then.__init__()
, resulting in a new and fully initialized instance ofPoint
, as you confirmed at the end of the example.
To continue learning about class instantiation in Python, you can try running both steps manually:
>>>frompointimportPoint>>>point=Point.__new__(Point)1. Create a new instance of Point.>>># The point object is not initialized>>>point.xTraceback (most recent call last):...AttributeError:'Point' object has no attribute 'x'>>>point.yTraceback (most recent call last):...AttributeError:'Point' object has no attribute 'y'>>>point.__init__(21,42)2. Initialize the new instance of Point.>>># Now point is properly initialized>>>pointPoint(x=21, y=42)
In this example, you first call.__new__()
on yourPoint
class, passing the class itself as the first argument to the method. This call only runs the first step of the instantiation process, creating a new and empty object. Note that creating an instance this way bypasses the call to.__init__()
.
Note: The code snippet above is intended to be a demonstrative example of how the instantiation process works internally. It’s not something that you would typically do in real code.
Once you have the new object, then you can initialize it by calling.__init__()
with an appropriate set of arguments. After this call, yourPoint
object is properly initialized, with all its attributes set up.
A subtle and important detail to note about.__new__()
is that it can also return an instance of a class different from the class that implements the method itself. When that happens, Python doesn’t call.__init__()
in the current class, because there’s no way to unambiguously know how to initialize an object of a different class.
Consider the following example, in which the.__new__()
method of theB
class returns an instance of theA
class:
ab_classes.py
classA:def__init__(self,a_value):print("Initialize the new instance of A.")self.a_value=a_valueclassB:def__new__(cls,*args,**kwargs):returnA(42)def__init__(self,b_value):print("Initialize the new instance of B.")self.b_value=b_value
BecauseB.__new__()
returns an instance of a different class, Python doesn’t runB.__init__()
. To confirm this behavior, save the code into a file calledab_classes.py
and then run the following code in an interactive Python session:
>>>fromab_classesimportB>>>b=B(21)Initialize the new instance of A.>>>b.b_valueTraceback (most recent call last):...AttributeError:'A' object has no attribute 'b_value'>>>isinstance(b,B)False>>>isinstance(b,A)True>>>b.a_value42
The call to theB()
class constructor runsB.__new__()
, which returns an instance ofA
instead ofB
. That’s whyB.__init__()
never runs. Note thatb
doesn’t have a.b_value
attribute. In contrast,b
does have an.a_value
attribute with a value of42
.
Now that you know the steps that Python internally takes to create instances of a given class, you’re ready to dig a little deeper into other characteristics of.__init__()
,.__new__()
, and the steps that they run.
.__init__()
In Python, the.__init__()
method is probably the most common special method that you’ll ever override in your custom classes. Almost all your classes will need a custom implementation of.__init__()
. Overriding this method will allow you to initialize your objects properly.
The purpose of this initialization step is to leave your new objects in a valid state so that you can start using them right away in your code. In this section, you’ll learn the basics of writing your own.__init__()
methods and how they can help you customize your classes.
The most bare-bones implementation of.__init__()
that you can write will just take care of assigning input arguments to matching instance attributes. For example, say you’re writing aRectangle
class that requires.width
and.height
attributes. In that case, you can do something like this:
>>>classRectangle:...def__init__(self,width,height):...self.width=width...self.height=height...>>>rectangle=Rectangle(21,42)>>>rectangle.width21>>>rectangle.height42
As you learned before,.__init__()
runs the second step of the object instantiation process in Python. Its first argument,self
, holds the new instance that results from calling.__new__()
. The rest of the arguments to.__init__()
are normally used to initialize instance attributes. In the above example, you initialized the rectangle’s.width
and.height
using thewidth
andheight
arguments to.__init__()
.
It’s important to note that, without countingself
, the arguments to.__init__()
are the same ones that you passed in the call to the class constructor. So, in a way, the.__init__()
signature defines the signature of the class constructor.
Additionally, keep in mind that.__init__()
must notexplicitly return anything different fromNone
, or you’ll get aTypeError
exception:
>>>classRectangle:...def__init__(self,width,height):...self.width=width...self.height=height...return42...>>>rectangle=Rectangle(21,42)Traceback (most recent call last):...TypeError:__init__() should return None, not 'int'
In this example, the.__init__()
method attempts to return an integernumber, which ends up raising aTypeError
exception at run time.
The error message in the above example says that.__init__()
should returnNone
. However, you don’t need to returnNone
explicitly, because methods and functions without an explicitreturn
statement just returnNone
implicitly in Python.
With the above implementation of.__init__()
, you ensure that.width
and.height
get initialized to a valid state when you call the class constructor with appropriate arguments. That way, your rectangles will be ready for use right after the construction process finishes.
In.__init__()
, you can also run any transformation over the input arguments to properly initialize the instance attributes. For example, if your users will useRectangle
directly, then you might want to validate the suppliedwidth
andheight
and make sure that they’re correct before initializing the corresponding attributes:
>>>classRectangle:...def__init__(self,width,height):...ifnot(isinstance(width,(int,float))andwidth>0):...raiseValueError(f"positive width expected, got{width}")...self.width=width...ifnot(isinstance(height,(int,float))andheight>0):...raiseValueError(f"positive height expected, got{height}")...self.height=height...>>>rectangle=Rectangle(-21,42)Traceback (most recent call last):...ValueError:positive width expected, got -21
In this updated implementation of.__init__()
, you make sure that the inputwidth
andheight
arguments are positive numbers before initializing the corresponding.width
and.height
attributes. If either validation fails, then you get aValueError
.
Note: A more Pythonic technique to tackle attribute validation is to turn attributes intoproperties. To learn more about properties, check outPython’s property(): Add Managed Attributes to Your Classes.
Now say that you’re usinginheritance to create a custom class hierarchy and reuse some functionality in your code. If your subclasses provide a.__init__()
method, then this method must explicitly call the base class’s.__init__()
method with appropriate arguments to ensure the correct initialization of instances. To do this, you should use the built-insuper()
function like in the following example:
>>>classPerson:...def__init__(self,name,birth_date):...self.name=name...self.birth_date=birth_date...>>>classEmployee(Person):...def__init__(self,name,birth_date,position):...super().__init__(name,birth_date)...self.position=position...>>>john=Employee("John Doe","2001-02-07","Python Developer")>>>john.name'John Doe'>>>john.birth_date'2001-02-07'>>>john.position'Python Developer'
The first line in the.__init__()
method ofEmployee
callssuper().__init__()
withname
andbirth_date
as arguments. This call ensures the initialization of.name
and.birth_date
in the parent class,Person
. This technique allows you toextend the base class with new attributes and functionality.
To wrap up this section, you should know that the base implementation of.__init__()
comes from the built-inobject
class. This implementation is automatically called when you don’t provide an explicit.__init__()
method in your classes.
You can make your objects’ initialization step flexible and versatile by tweaking the.__init__()
method. To this end, one of the most popular techniques is to useoptional arguments. This technique allows you to write classes in which the constructor accepts different sets of input arguments at instantiation time. Which arguments to use at a given time will depend on your specific needs and context.
As a quick example, check out the followingGreeter
class:
greet.py
classGreeter:def__init__(self,name,formal=False):self.name=nameself.formal=formaldefgreet(self):ifself.formal:print(f"Good morning,{self.name}!")else:print(f"Hello,{self.name}!")
In this example,.__init__()
takes a regular argument calledname
. It also takes anoptional argument calledformal
, which defaults toFalse
. Becauseformal
has a default value, you can construct objects by relying on this value or by providing your own.
The class’s final behavior will depend on the value offormal
. If this argument isFalse
, then you’ll get an informal greeting when you call.greet()
. Otherwise, you’ll get a more formal greeting.
To tryGreeter
out, go ahead and save the code into agreet.py
file. Then open an interactive session in your working directory and run the following code:
>>>fromgreetimportGreeter>>>informal_greeter=Greeter("Pythonista")>>>informal_greeter.greet()Hello, Pythonista!>>>formal_greeter=Greeter("Pythonista",formal=True)>>>formal_greeter.greet()Good morning, Pythonista!
In the first example, you create aninformal_greeter
object by passing a value to thename
argument and relying on the default value offormal
. You get an informal greeting on your screen when you call.greet()
on theinformal_greeter
object.
In the second example, you use aname
and aformal
argument to instantiateGreeter
. Becauseformal
isTrue
, the result of calling.greet()
is a formal greeting.
Even though this is a toy example, it showcases how default argument values are a powerful Python feature that you can use to write flexible initializers for your classes. These initializers will allow you to instantiate your classes using different sets of arguments depending on your needs.
Okay! Now that you know the basics of.__init__()
and the object initialization step, it’s time to change gears and start diving deeper into.__new__()
and the object creation step.
.__new__()
When writing Python classes, you typically don’t need to provide your own implementation of the.__new__()
special method. Most of the time, the base implementation from the built-inobject
class is sufficient to build an empty object of your current class.
However, there are a few interesting use cases for this method. For example, you can use.__new__()
to create subclasses ofimmutable types, such asint
,float
,tuple
, andstr
.
In the following sections, you’ll learn how to write custom implementations of.__new__()
in your classes. To do this, you’ll code a few examples that’ll give you an idea of when you might need to override this method.
Typically, you’ll write a custom implementation of.__new__()
only when you need to control the creation of a new instance at a low level. Now, if you need a custom implementation of this method, then you should follow a few steps:
super().__new__()
with appropriate arguments.With these three succinct steps, you’ll be able to customize the instance creation step in the Python instantiation process. Here’s an example of how you can translate these steps into Python code:
classSomeClass:def__new__(cls,*args,**kwargs):instance=super().__new__(cls)# Customize your instance here...returninstance
This example provides a sort of template implementation of.__new__()
. As usual,.__new__()
takes the current class as an argument that’s typically calledcls
.
Note that you’re using*args
and**kwargs
to make the method more flexible and maintainable by accepting any number of arguments. You should always define.__new__()
with*args
and**kwargs
, unless you have a good reason to follow a different pattern.
In the first line of.__new__()
, you call the parent class’s.__new__()
method to create a new instance and allocate memory for it. To access the parent class’s.__new__()
method, you use thesuper()
function. This chain of calls takes you up toobject.__new__()
, which is the base implementation of.__new__()
for all Python classes.
Note: The built-inobject
class is the default base class of all Python classes.
The next step is to customize your newly created instance. You can do whatever you need to do to customize the instance at hand. Finally, in the third step, you need to return the new instance to continue the instantiation process with the initialization step.
It’s important to note thatobject.__new__()
itself only accepts asingle argument, the class to instantiate. If you callobject.__new__()
with more arguments, then you get aTypeError
:
>>>classSomeClass:...def__new__(cls,*args,**kwargs):...returnsuper().__new__(cls,*args,**kwargs)...def__init__(self,value):...self.value=value...>>>SomeClass(42)Traceback (most recent call last):...TypeError:object.__new__() takes exactly one argument (the type to instantiate)
In this example, you hand over*args
and**kwargs
as additional arguments in the call tosuper().__new__()
. The underlyingobject.__new__()
accepts only the class as an argument, so you get aTypeError
when you instantiate the class.
However,object.__new__()
still accepts and passes over extra arguments to.__init__()
if your class doesn’t override.__new__()
, as in the following variation ofSomeClass
:
>>>classSomeClass:...def__init__(self,value):...self.value=value...>>>some_obj=SomeClass(42)>>>some_obj<__main__.SomeClass object at 0x7f67db8d0ac0>>>>some_obj.value42
In this implementation ofSomeClass
, you don’t override.__new__()
. The object creation is then delegated toobject.__new__()
, which now acceptsvalue
and passes it over toSomeClass.__init__()
to finalize the instantiation. Now you can create new and fully initialized instances ofSomeClass
, just likesome_obj
in the example.
Cool! Now that you know the basics of writing your own implementations of.__new__()
, you’re ready to dive into a few practical examples that feature some of the most common use cases of this method in Python programming.
To kick things off, you’ll start with a use case of.__new__()
that consists of subclassing an immutable built-in type. As an example, say you need to write aDistance
class as a subclass of Python’sfloat
type. Your class will have an additional attribute to store the unit that’s used to measure the distance.
Here’s a first approach to this problem, using the.__init__()
method:
>>>classDistance(float):...def__init__(self,value,unit):...super().__init__(value)...self.unit=unit...>>>in_miles=Distance(42.0,"Miles")Traceback (most recent call last):...TypeError:float expected at most 1 argument, got 2
When you subclass an immutable built-in data type, you get an error. Part of the problem is that the value is set during creation, and it’s too late to change it during initialization. Additionally,float.__new__()
is called under the hood, and it doesn’t deal with extra arguments in the same way asobject.__new__()
. This is what raises the error in your example.
To work around this issue, you can initialize the object at creation time with.__new__()
instead of overriding.__init__()
. Here’s how you can do this in practice:
>>>classDistance(float):...def__new__(cls,value,unit):...instance=super().__new__(cls,value)...instance.unit=unit...returninstance...>>>in_miles=Distance(42.0,"Miles")>>>in_miles42.0>>>in_miles.unit'Miles'>>>in_miles+42.084.0>>>dir(in_miles)['__abs__', '__add__', ..., 'real', 'unit']
In this example,.__new__()
runs the three steps that you learned in the previous section. First, the method creates a new instance of the current class,cls
, by callingsuper().__new__()
. This time, the call rolls back tofloat.__new__()
, which creates a new instance and initializes it usingvalue
as an argument. Then the method customizes the new instance by adding a.unit
attribute to it. Finally, the new instance gets returned.
Note: TheDistance
class in the example above doesn’t provide a proper unit conversion mechanism. This means that something likeDistance(10, "km") + Distance(20, "miles")
won’t attempt at converting units before adding the values. If you’re interested in converting units, then check out thePint project onPyPI.
That’s it! Now yourDistance
class works as expected, allowing you to use an instance attribute for storing the unit in which you’re measuring the distance. Unlike the floating-point value stored in a given instance ofDistance
, the.unit
attribute is mutable, so you can change its value any time you like. Finally, note how a call to thedir()
function reveals that your class inherits features and methods fromfloat
.
Returning an object of a different class is a requirement that can raise the need for a custom implementation of.__new__()
. However, you should be careful because in this case, Python skips the initialization step entirely. So, you’ll have the responsibility of taking the newly created object into a valid state before using it in your code.
Check out the following example, in which thePet
class uses.__new__()
to return instances of randomly selected classes:
pets.py
fromrandomimportchoiceclassPet:def__new__(cls):other=choice([Dog,Cat,Python])instance=super().__new__(other)print(f"I'm a{type(instance).__name__}!")returninstancedef__init__(self):print("Never runs!")classDog:defcommunicate(self):print("woof! woof!")classCat:defcommunicate(self):print("meow! meow!")classPython:defcommunicate(self):print("hiss! hiss!")
In this example,Pet
provides a.__new__()
method that creates a new instance by randomly selecting a class from a list of existing classes.
Here’s how you can use thisPet
class as a factory of pet objects:
>>>frompetsimportPet>>>pet=Pet()I'm a Dog!>>>pet.communicate()woof! woof!>>>isinstance(pet,Pet)False>>>isinstance(pet,Dog)True>>>another_pet=Pet()I'm a Python!>>>another_pet.communicate()hiss! hiss!
Every time you instantiatePet
, you get a random object from a different class. This result is possible because there’s no restriction on the object that.__new__()
can return. Using.__new__()
in such a way transforms a class into a flexible and powerful factory of objects, not limited to instances of itself.
Finally, note how the.__init__()
method ofPet
never runs. That’s becausePet.__new__()
always returns objects of a different class rather than ofPet
itself.
Sometimes you need to implement a class that allows the creation of a single instance only. This type of class is commonly known as asingleton class. In this situation, the.__new__()
method comes in handy because it can help you restrict the number of instances that a given class can have.
Note: Most experienced Python developers would argue that you don’t need to implement the singletondesign pattern in Python unless you already have a working class and need to add the pattern’s functionality on top of it.
The rest of the time, you can use a module-levelconstant to get the same singleton functionality without having to write a relatively complex class.
Here’s an example of coding aSingleton
class with a.__new__()
method that allows the creation of only one instance at a time. To do this,.__new__()
checks the existence of previous instances cached on a class attribute:
>>>classSingleton(object):..._instance=None...def__new__(cls,*args,**kwargs):...ifcls._instanceisNone:...cls._instance=super().__new__(cls)...returncls._instance...>>>first=Singleton()>>>second=Singleton()>>>firstissecondTrue
TheSingleton
class in this example has aclass attribute called._instance
that defaults toNone
and works as acache. The.__new__()
method checks if no previous instance exists by testing the conditioncls._instance is None
.
Note: In the example above,Singleton
doesn’t provide an implementation of.__init__()
. If you ever need a class like this with a.__init__()
method, then keep in mind that this method will run every time you call theSingleton()
constructor. This behavior can cause weird initialization effects and bugs.
If this condition is true, then theif
code block creates a new instance ofSingleton
and stores it tocls._instance
. Finally, the method returns the new or the existing instance to the caller.
Then you instantiateSingleton
twice to try to construct two different objects,first
andsecond
. If you compare the identity of these objects with theis
operator, then you’ll note that both objects are the same object. The namesfirst
andsecond
just hold references to the sameSingleton
object.
collections.namedtuple
As a final example of how to take advantage of.__new__()
in your code, you can push your Python skills and write a factory function that partially emulatescollections.namedtuple()
. Thenamedtuple()
function allows you to create subclasses oftuple
with the additional feature of having named fields for accessing the items in the tuple.
The code below implements anamed_tuple_factory()
function that partially emulates this functionality by overriding the.__new__()
method of a nested class calledNamedTuple
:
named_tuple.py
1fromoperatorimportitemgetter 2 3defnamed_tuple_factory(type_name,*fields): 4num_fields=len(fields) 5 6classNamedTuple(tuple): 7__slots__=() 8 9def__new__(cls,*args):10iflen(args)!=num_fields:11raiseTypeError(12f"{type_name} expected exactly{num_fields} arguments,"13f" got{len(args)}"14)15cls.__name__=type_name16forindex,fieldinenumerate(fields):17setattr(cls,field,property(itemgetter(index)))18returnsuper().__new__(cls,args)1920def__repr__(self):21returnf"""{type_name}({", ".join(repr(arg)forarginself)})"""2223returnNamedTuple
Here’s how this factory function works line by line:
Line 1 importsitemgetter()
from theoperator
module. This function allows you to retrieve items using their index in the containing sequence.
Line 3 definesnamed_tuple_factory()
. This function takes a first argument calledtype_name
, which will hold the name of the tuple subclass that you want to create. The*fields
argument allows you to pass an undefined number of field names asstrings.
Line 4 defines a localvariable to hold the number of named fields provided by the user.
Line 6 defines a nested class calledNamedTuple
, which inherits from the built-intuple
class.
Line 7 provides a.__slots__
class attribute. This attribute defines a tuple for holding instance attributes. This tuple saves memory by acting as a substitute for the instance’s dictionary,.__dict__
, which would otherwise play a similar role.
Line 9 implements.__new__()
withcls
as its first argument. This implementation also takes the*args
argument to accept an undefined number of field values.
Lines 10 to 14 define aconditional statement that checks if the number of items to store in the final tuple differs from the number of named fields. If that’s the case, then the conditional raises aTypeError
with an error message.
Line 15 sets the.__name__
attribute of the current class to the value provided bytype_name
.
Lines 16 and 17 define afor
loop that turns every named field into a property that usesitemgetter()
to return the item at the targetindex
. The loop uses the built-insetattr()
function to perform this action. Note that the built-inenumerate()
function provides the appropriateindex
value.
Line 18 returns a new instance of the current class by callingsuper().__new__()
as usual.
Lines 20 and 21 define a.__repr__()
method for your tuple subclass.
Line 23 returns the newly createdNamedTuple
class.
To try yournamed_tuple_factory()
out, fire up an interactive session in the directory containing thenamed_tuple.py
file and run the following code:
>>>fromnamed_tupleimportnamed_tuple_factory>>>Point=named_tuple_factory("Point","x","y")>>>point=Point(21,42)>>>pointPoint(21, 42)>>>point.x21>>>point.y42>>>point[0]21>>>point[1]42>>>point.x=84Traceback (most recent call last):...AttributeError:can't set attribute>>>dir(point)['__add__', '__class__', ..., 'count', 'index', 'x', 'y']
In this code snippet, you create a newPoint
class by callingnamed_tuple_factory()
. The first argument in this call represents the name that the resulting class object will use. The second and third arguments are the named fields available in the resulting class.
Then you create aPoint
object by calling the class constructor with appropriate values for the.x
and.y
fields. To access the value of each named field, you can use the dot notation. You can also use indices to retrieve the values because your class is a tuple subclass.
Because tuples are immutable data types in Python, you can’t assign new values to the point’s coordinatesin place. If you try to do that, then you get anAttributeError
.
Finally, callingdir()
with yourpoint
instance as an argument reveals that your object inherits all the attributes and methods that regular tuples have in Python.
Now you know how Python class constructors allow you to instantiate classes, so you can create concrete and ready-to-use objects in your code. In Python, class constructors internally trigger the instantiation or construction process, which goes throughinstance creation andinstance initialization. These steps are driven by the.__new__()
and.__init__()
special methods.
By learning about Python’s class constructors, the instantiation process, and the.__new__()
and.__init__()
methods, you can now manage how your custom classes construct new instances.
In this tutorial, you learned:
.__init__()
methods help you customize object initialization.__new__()
method allows for custom object creationNow you’re ready to take advantage of this knowledge to fine-tune your class constructors and take full control over instance creation and initialization in your object-oriented programming adventure with Python.
Free Bonus:Click here to get access to a free Python OOP Cheat Sheet that points you to the best tutorials, videos, and books to learn more about Object-Oriented Programming with Python.
Now that you have some experience with Python class constructors, you can use the questions and answers below to check your understanding and recap what you’ve learned.
These FAQs are related to the most important concepts you’ve covered in this tutorial. Click theShow/Hide toggle beside each question to reveal the answer.
A class constructor in Python is a mechanism that creates and initializes a new instance of a class. It involves calling the class itself like a function, which triggers the instantiation process, including the execution of the.__new__()
and.__init__()
methods.
You can customize object initialization in Python by overriding the.__init__()
method in your class. This method allows you to set up the initial state of an object by assigning values to instance attributes using the arguments passed to the class constructor.
The.__new__()
method is responsible for creating a new instance of a class, while the.__init__()
method initializes the newly created instance..__new__()
returns the new instance, and.__init__()
sets up the initial state of the instance.
Common use cases for overriding.__new__()
include creating instances of immutable built-in types likeint
ortuple
, controlling the instantiation of singleton classes, and returning instances of a different class to customize object creation.
If you don’t override.__new__()
or.__init__()
in your class, Python will use the default implementations from the built-inobject
class. They’re typically sufficient for most use cases unless you need custom behavior during object creation or initialization.
Take the Quiz: Test your knowledge with our interactive “Python Class Constructors: Control Your Object Instantiation” quiz. You’ll receive a score upon completion to help you track your learning progress:
Interactive Quiz
Python Class Constructors: Control Your Object InstantiationIn this quiz, you'll test your understanding of class constructors in Python. By working through this quiz, you'll revisit the internal instantiation process, object initialization, and fine-tuning object creation.
Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding:Using Python Class Constructors
🐍 Python Tricks 💌
Get a short & sweetPython Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.
AboutLeodanis Pozo Ramos
Leodanis is an industrial engineer who loves Python and software development. He's a self-taught Python developer with 6+ years of experience. He's an avid technical writer with a growing number of articles published on Real Python and other sites.
» More about LeodanisMasterReal-World Python Skills With Unlimited Access to Real Python
Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:
MasterReal-World Python Skills
With Unlimited Access to Real Python
Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:
What Do You Think?
What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.
Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students.Get tips for asking good questions andget answers to common questions in our support portal.
Keep Learning
Related Topics:intermediatepython
Recommended Video Course:Using Python Class Constructors
Related Tutorials:
Already have an account?Sign-In
Almost there! Complete this form and click the button below to gain instant access:
Object-Oriented Programming in Python: The 7 Best Resources (A Free PDF Cheat Sheet)