Movatterモバイル変換


[0]ホーム

URL:


— FREE Email Series —

🐍 Python Tricks 💌

Python Tricks Dictionary Merge

🔒 No spam. Unsubscribe any time.

Browse TopicsGuided Learning Paths
Basics Intermediate Advanced
apibest-practicescareercommunitydatabasesdata-sciencedata-structuresdata-vizdevopsdjangodockereditorsflaskfront-endgamedevguimachine-learningnumpyprojectspythontestingtoolsweb-devweb-scraping

Table of Contents

Recommended Video Course
Using Multiple Constructors in Your Python Classes

Providing Multiple Constructors in Your Python Classes

Providing Multiple Constructors in Your Python Classes

byLeodanis Pozo Ramos Feb 01, 2025intermediatepython

Table of Contents

Remove ads

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 Multiple Constructors in Your Python Classes

Python doesn’t support constructor overloading in the same way that Java or C++ do. However, you can simulate multiple constructors by defining default arguments in.__init__() and use@classmethod to define alternative constructors. Another option is to employ the@singledispatchmethod decorator for method overloading based on argument types. These techniques provide flexible ways to construct objects in Python.

By the end of this tutorial, you’ll understand that:

  • You cancreate multiple constructors in Python by usingoptional arguments and branching logic in the.__init__() method.
  • The built-in@classmethod decorator allows you todefine alternative constructors, using the class itself as the first argument.
  • @singledispatchmethod can simulate overloaded constructors based on thetype of the first argument.

Having the tools to provide multiple constructors will help you write flexible classes that can adapt to changing needs.

You’ll also get a peek under the hood at how Python internallyconstructs instances of a regular class and how somestandard-library classes provide multiple constructors.

To get the most out of this tutorial, you should have basic knowledge ofobject-oriented programming and understand how to defineclass methods with@classmethod. You should also have experience working withdecorators 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.

Instantiating Classes in Python

Python supportsobject-oriented programming withclasses that are straightforward to create and use. Python classes offer powerful features that can help you write better software. Classes are like blueprints forobjects, also known asinstances. In the same way that you can build several houses from a single blueprint, you can build several instances from a class.

To define a class in Python, you need to use theclass keyword followed by the class name:

Python
>>># Define a Person class>>>classPerson:...def__init__(self,name):...self.name=name...

Python has a rich set ofspecial methods that you can use in your classes. Python implicitly calls special methods to automatically execute a wide variety of operations on instances. There are special methods to make your objects iterable, provide a suitable string representation for your objects, initialize instance attributes, and a lot more.

A pretty common special method is.__init__(). This method provides what’s known as theinstance initializer in Python. This method’s job is to initializeinstance attributes with appropriate values when you instantiate a given class.

InPerson, the.__init__() method’s first argument is calledself. This argument holds the current object or instance, which is passed implicitly in the method call. This argument is common to everyinstance method in Python. The second argument to.__init__() is calledname and will hold the person’s name as astring.

Note: Usingself to name the current object is a pretty strong convention in Python but not a requirement. However, using another name will raise some eyebrows among your fellow Python developers.

Once you’ve defined a class, you can startinstantiating it. In other words, you can start creating objects of that class. To do this, you’ll use a familiar syntax. Just call the class using a pair of parentheses (()), which is the same syntax that you use to call any Pythonfunction:

Python
>>># Instantiating Person>>>john=Person("John Doe")>>>john.name'John Doe'

In Python, the class name provides what other languages, such asC++ andJava, call theclass constructor. Calling a class, like you did withPerson, triggers Python’s classinstantiation process, which internally runs in two steps:

  1. Create a new instance of the target class.
  2. Initialize the instance with suitable instance attribute values.

To continue with the above example, the value that you pass as an argument toPerson is internally passed to.__init__() and then assigned to the instance attribute.name. This way, you initialize your person instance,john, with valid data, which you can confirm by accessing.name. Success!John Doe is indeed his name.

Note: When you call the class to create a new instance, you need to provide as many arguments as.__init__() requires so that this method can initialize all the instance attributes that demand an initial value.

Now that you understand the object initialization mechanism, you’re ready to learn what Python does before it gets to this point in the instantiation process. It’s time to dig into another special method, called.__new__(). This method takes care of creating new instances in Python.

Note: The.__new__() special method is often called aclass constructor in Python. However, its job is actually to create new objects from the class blueprint, so you can more accurately call it aninstance creator orobject creator.

The.__new__() special method takes the underlying class as its first argument and returns a new object. This object is typically an instance of the input class, but in some cases, it can be an instance of a different class.

If the object that.__new__() returns is an instance of the current class, then this instance is immediately passed to.__init__() for initialization purposes. These two steps run when you call the class.

Python’sobject class provides the base or default implementation of.__new__() and.__init__(). Unlike with.__init__(), you rarely need to override.__new__() in your custom classes. Most of the time, you can safely rely on its default implementation.

To summarize what you’ve learned so far, Python’s instantiation process starts when you call a class with appropriate arguments. Then the process runs through two steps: object creation with the.__new__() method, and object initialization with the.__init__() method.

Now that you know about this internal behavior of Python, you’re ready to dive into providingmultiple constructors in your classes. In other words, you’ll provide multiple ways to construct objects of a givenPython class.

Defining Multiple Class Constructors

Sometimes you’d like to write a class that allows you to construct objects using arguments of different data types or even a different number of arguments. One way to achieve this is by providing multiple constructors in the class at hand. Each constructor will allow you to create instances of the class using a different set of arguments.

Some programming languages, such asC++,C#, andJava, support what is known asfunction or method overloading. This feature allows you to provide multiple class constructors because it enables you to create multiple functions or methods with the same name and different implementations.

Method overloading means that depending on how you call the method at hand, the language will select the appropriate implementation to run. So, your method can perform different tasks according to the context of the call.

Unfortunately, Python doesn’t support function overloading directly. Python classes keep method names in an internaldictionary called.__dict__, which holds the classnamespace. Like any Python dictionary,.__dict__ can’t have repeated keys, so you can’t have multiple methods with the same name in a given class. If you try to do so, then Python will only remember the last implementation of the method at hand:

Pythongreet.py
classGreeter:defsay_hello(self):print("Hello, World")defsay_hello(self):print("Hello, Pythonista")

In this example, you createGreeter as a Python class with two methods. Both methods have the same name, but they have slightly different implementations.

To learn what happens when two methods have the same name, save your class into agreet.py file in your working directory and run the following code in aninteractive session:

Python
>>>fromgreetimportGreeter>>>greeter=Greeter()>>>greeter.say_hello()Hello, Pythonista>>>Greeter.__dict__mappingproxy({..., 'say_hello': <function Greeter.say_hello at...>, ...})

In this example, you call.say_hello() ongreeter, which is an instance of theGreeter class. You getHello, Pythonista instead ofHello, World on your screen, which confirms that the second implementation of the method prevails over the first one.

The final line of code inspects the content of.__dict__, uncovering that the method’s name,say_hello, appears only once in the class namespace. This is consistent with how dictionaries work in Python.

Something similar occurs with functions in a Python module and in an interactive session. The last implementation of several functions with the same name prevails over the rest of the implementations:

Python
>>>defsay_hello():...print("Hello, World")...>>>defsay_hello():...print("Hello, Pythonista")...>>>say_hello()Hello Pythonista

You define two functions with the same name,say_hello(), in the same interpreter session. However, the second definition overwrites the first one. When you call the function, you getHello, Pythonista, which confirms that the last function definition prevails.

Another technique that some programming languages use to provide multiple ways to call a method or function ismultiple dispatch.

With this technique, you can write several different implementations of the same method or function and dynamically dispatch the desired implementation according to the type or other characteristics of the arguments that are used in the call. You can use a couple of tools from thestandard library to pull this technique into your Python code.

Python is a fairly flexible and feature-rich language and provides a couple of ways to implement multiple constructors and make your classes more flexible.

In the following section, you’ll simulate multiple constructors by passingoptional arguments and by checking the argument types to determine different behaviors in your instance initializers.

Simulating Multiple Constructors in Your Classes

A pretty useful technique for simulating multiple constructors in a Python class is to provide.__init__() withoptional arguments usingdefault argument values. This way, you can call the class constructor in different ways and get a different behavior each time.

Another strategy is to check thedata type of the arguments to.__init__() to provide different behaviors depending on the concrete data type that you pass in the call. This technique allows you to simulate multiple constructors in a class.

In this section, you’ll learn the basics of how to simulate multiple ways to construct objects by providing appropriate default values for the arguments of the.__init__() method and also by checking the data type of the arguments to this method. Both approaches require only one implementation of.__init__().

Using Optional Argument Values in.__init__()

An elegant and Pythonic way to simulate multiple constructors is to implement a.__init__() method withoptional arguments. You can do this by specifying appropriatedefault argument values.

Note: You can also provide optional arguments in your functions and methods using an undefined number ofpositional arguments or an undefined number ofkeyword arguments. Check outUsing Python Optional Arguments When Defining Functions for more details on these options.

To this end, say you need to code afactory class calledCumulativePowerFactory. This class will create callable objects that compute specific powers, using a stream of numbers as input. You also need your class to track the total sum of consecutive powers. Finally, your class should accept an argument holding an initial value for the sum of powers.

Go ahead and create apower.py file in your current directory. Then type in the following code to implementCumulativePowerFactory:

Pythonpower.py
classCumulativePowerFactory:def__init__(self,exponent=2,*,start=0):self._exponent=exponentself.total=startdef__call__(self,base):power=base**self._exponentself.total+=powerreturnpower

The initializer ofCumulativePowerFactory takes two optional arguments,exponent andstart. The first argument holds the exponent that you’ll use to compute a series of powers. It defaults to2, which is a commonly used value when it comes to computing powers.

The star or asterisk symbol (*) afterexponent means thatstart is akeyword-only argument. To pass a value to a keyword-only argument, you need to use the argument’s name explicitly. In other words, to setarg tovalue, you need to explicitly typearg=value.

Thestart argument holds the initial value to compute the cumulative sum of powers. It defaults to0, which is the appropriate value for those cases in which you don’t have a previously computed value to initialize the cumulative sum of powers.

The special method.__call__() turns the instances ofCumulativePowerFactory intocallable objects. In other words, you can call the instances ofCumulativePowerFactory like you call any regular function.

Inside.__call__(), you first compute the power ofbase raised toexponent. Then you add the resulting value to the current value of.total. Finally, youreturn the computed power.

To giveCumulativePowerFactory a try, open a Pythoninteractive session in the directory containingpower.py and run the following code:

Python
>>>frompowerimportCumulativePowerFactory>>>square=CumulativePowerFactory()>>>square(21)441>>>square(42)1764>>>square.total2205>>>cube=CumulativePowerFactory(exponent=3)>>>cube(21)9261>>>cube(42)74088>>>cube.total83349>>>initialized_cube=CumulativePowerFactory(3,start=2205)>>>initialized_cube(21)9261>>>initialized_cube(42)74088>>>initialized_cube.total85554

These examples show howCumulativePowerFactory simulates multiple constructors. For example, the first constructor doesn’t take arguments. It allows you to create class instances that compute powers of2, which is the default value of theexponent argument. The.total instance attribute holds the cumulative sum of computed powers as you go.

The second example shows a constructor that takesexponent as an argument and returns a callable instance that computes cubes. In this case,.total works the same as in the first example.

The third example shows howCumulativePowerFactory seems to have another constructor that allows you to create instances by providing theexponent andstart arguments. Now.total starts with a value of2205, which initializes the sum of powers.

Using optional arguments when you’re implementing.__init__() in your classes is a clean and Pythonic technique to create classes that simulate multiple constructors.

Checking Argument Types in.__init__()

Another approach to simulate multiple constructors is to write a.__init__() method that behaves differently depending on the argument type. To check the type of avariable in Python, you commonly rely on the built-inisinstance() function. This function returnsTrue if an object is an instance of a given class andFalse otherwise:

Python
>>>isinstance(42,int)True>>>isinstance(42,float)False>>>isinstance(42,(list,int))True>>>isinstance(42,list|int)# Python >= 3.10True

The first argument toisinstance() is the object that you want to type check. The second argument is the class or data type of reference. You can also pass atuple of types to this argument. If you’re runningPython 3.10 or later, then you can also use the newunion syntax with the pipe symbol (|).

Now say that you want to continue working on yourPerson class, and you need that class to also accept the person’s birth date. Your code will represent the birth date as adate object, but for convenience your users will also have the option of providing the birth date as a string with a given format. In this case, you can do something like the following:

Python
>>>fromdatetimeimportdate>>>classPerson:...def__init__(self,name,birth_date):...self.name=name...ifisinstance(birth_date,date):...self.birth_date=birth_date...elifisinstance(birth_date,str):...self.birth_date=date.fromisoformat(birth_date)...>>>jane=Person("Jane Doe","2000-11-29")>>>jane.birth_datedatetime.date(2000, 11, 29)>>>john=Person("John Doe",date(1998,5,15))>>>john.birth_datedatetime.date(1998, 5, 15)

Inside.__init__(), you first define the usual.name attribute. Theif clause of theconditional statement checks if the provided birth date is adate object. If so, then you define.birth_date to store the date at hand.

Theelif clause checks if thebirth_date argument is ofstr type. If so, then you set.birth_date to adate object built from the provided string. Note that thebirth_date argument should be a string with a date inISO format,YYYY-MM-DD.

That’s it! Now you have a.__init__() method that simulates a class with multiple constructors. One constructor takes arguments ofdate type. The other constructor takes arguments of string type.

Note: If you’re runningPython 3.10 or greater, then you can also use thestructural pattern matching syntax to implement the technique in this section.

The technique in the above example has the drawback that it doesn’t scale well. If you have multiple arguments that can take values of different data types, then your implementation can soon become a nightmare. So, this technique is considered an anti-pattern in Python.

Note:PEP 443 states that “…it is currently a common anti-pattern for Python code to inspect the types of received arguments, in order to decide what to do with the objects.” This coding pattern is “brittle and closed to extension,” according to the same document.

PEP 443 therefore introducedsingle-dispatchgeneric functions to help you avoid using this coding anti-pattern whenever possible. You’ll learn more about this feature in the sectionProviding Multiple Class Constructors With@singledispatchmethod.

For example, what would happen if your user input aUnix time value forbirth_date? Check out the following code snippet:

Python
>>>linda=Person("Linda Smith",1011222000)>>>linda.birth_dateTraceback (most recent call last):...AttributeError:'Person' object has no attribute 'birth_date'

When you access.birth_date, you get anAttributeError because your conditional statement doesn’t have a branch that considers a different date format.

To fix this issue, you can continue addingelif clauses to cover all the possible date formats that the user can pass. You can also add anelse clause to catchunsupported date formats:

Python
>>>fromdatetimeimportdate>>>classPerson:...def__init__(self,name,birth_date):...self.name=name...ifisinstance(birth_date,date):...self.birth_date=birth_date...elifisinstance(birth_date,str):...self.birth_date=date.fromisoformat(birth_date)...else:...raiseValueError(f"unsupported date format:{birth_date}")...>>>linda=Person("Linda Smith",1011222000)Traceback (most recent call last):...ValueError:unsupported date format: 1011222000

In this example, theelse clause runs if the value ofbirth_date isn’t adate object or a string holding a valid ISO date. This way, the exceptional situation doesn’tpass silently.

Providing Multiple Constructors With@classmethod in Python

A powerful technique for providing multiple constructors in Python is to use@classmethod. This decorator allows you to turn a regular method into aclass method.

Unlike regular methods, class methods don’t take the current instance,self, as an argument. Instead, they take the class itself, which is commonly passed in as thecls argument. Usingcls to name this argument is a popular convention in the Python community.

Here’s the basic syntax to define a class method:

Python
>>>classDemoClass:...@classmethod...defclass_method(cls):...print(f"A class method from{cls.__name__}!")...>>>DemoClass.class_method()A class method from DemoClass!>>>demo=DemoClass()>>>demo.class_method()A class method from DemoClass!

DemoClass defines a class method using Python’s built-in@classmethod decorator. The first argument of.class_method() holds the class itself. Through this argument, you can access the class from inside itself. In this example, you access the.__name__ attribute, which stores the name of the underlying class as astring.

It’s important to note that you can access a class method using either the class or a concrete instance of the class at hand. No matter how you invoke.class_method(), it’ll receiveDemoClass as its first argument. The ultimate reason why you can use class methods as constructors is that you don’t need an instance to call a class method.

Using@classmethod makes it possible to add as many explicit constructors as you need to a given class. It’s a Pythonic and popular way to implement multiple constructors. You can also call this type of constructor analternative constructor in Python, asRaymond Hettinger does in his PyCon talkPython’s Class Development Toolkit.

Now, how can you use a class method to customize Python’s instantiation process? Instead of fine-tuning.__init__() and the object initialization, you’ll control both steps: object creation and initialization. Through the following examples, you’ll learn how to do just that.

Constructing a Circle From Its Diameter

To create your first class constructor with@classmethod, say you’re coding a geometry-related application and need aCircle class. Initially, you define your class as follows:

Pythoncircle.py
importmathclassCircle:def__init__(self,radius):self.radius=radiusdefarea(self):returnmath.pi*self.radius**2defperimeter(self):return2*math.pi*self.radiusdef__repr__(self):returnf"{self.__class__.__name__}(radius={self.radius})"

The initializer ofCircle takes a radius value as an argument and stores it in an instance attribute called.radius. Then the class implements methods to compute the circle’s area and perimeter using Python’smath module. The special method.__repr__() returns a suitablestring representation for your class.

Go ahead and create thecircle.py file in your working directory. Then open the Python interpreter and run the following code to try outCircle:

Python
>>>fromcircleimportCircle>>>circle=Circle(42)>>>circleCircle(radius=42)>>>circle.area()5541.769440932395>>>circle.perimeter()263.89378290154264

Cool! Your class works correctly! Now say that you also want to instantiateCircle using the diameter. You can do something likeCircle(diameter / 2), but that’s not quite Pythonic or intuitive. It’d be better to have an alternative constructor to create circles by using their diameter directly.

Go ahead and add the following class method toCircle right after.__init__():

Pythoncircle.py
importmathclassCircle:def__init__(self,radius):self.radius=radius@classmethoddeffrom_diameter(cls,diameter):returncls(radius=diameter/2)# ...

Here, you define.from_diameter() as a class method. Its first argument receives a reference to the containing class,Circle.

The second argument holds the diameter of the specific circle that you want to create. Inside the method, you first calculate the radius using the input value ofdiameter. Then you instantiateCircle by callingcls with the radius that results from thediameter argument.

This way, you’re in full control of creating and initializing the instances ofCircle using the diameter as an argument.

Note: In the example above, you can seemingly achieve the same result by callingCircle itself instead ofcls. However, this can potentially lead to bugs if your class is subclassed. Those subclasses will then callCircle instead of themselves when they’re initialized with.from_diameter().

The call to thecls argument automatically runs the object creation and initialization steps that Python needs to instantiate a class. Finally,.from_diameter() returns the new instance to the caller.

Note: A popular convention within the Python community is to use thefrom preposition to name constructors that you create as class methods.

Here’s how you can use your brand-new constructor to create circles by using the diameter:

Python
>>>fromcircleimportCircle>>>circle=Circle.from_diameter(84)>>>circleCircle(radius=42.0)>>>circle.area()5541.769440932395>>>circle.perimeter()263.89378290154264

The call to.from_diameter() onCircle returns a new instance of the class. To construct that instance, the method uses the diameter instead of the radius. Note that the rest of the functionality ofCircle works the same as before.

Using@classmethod as you did in the example above is the most common way to provide explicit multiple constructors in your classes. With this technique, you have the option to select the right name for each alternative constructor that you provide, which can make your code more readable and maintainable.

Building a Polar Point From Cartesian Coordinates

For a more elaborate example of providing multiple constructors using class methods, say you have a class representing apolar point in a math-related application. You need a way to make your class more flexible so that you can construct new instances usingCartesian coordinates as well.

Here’s how you can write a constructor to meet this requirement:

Pythonpoint.py
importmathclassPolarPoint:def__init__(self,distance,angle):self.distance=distanceself.angle=angle@classmethoddeffrom_cartesian(cls,x,y):distance=math.dist((0,0),(x,y))angle=math.degrees(math.atan2(y,x))returncls(distance,angle)def__repr__(self):return(f"{self.__class__.__name__}"f"(distance={self.distance:.1f}, angle={self.angle:.1f})")

In this example,.from_cartesian() takes two arguments representing a given point’sx andy Cartesian coordinates. Then the method calculates the requireddistance andangle to construct the correspondingPolarPoint object. Finally,.from_cartesian() returns a new instance of the class.

Here’s how the class works, using both coordinate systems:

Python
>>>frompointimportPolarPoint>>># With polar coordinates>>>PolarPoint(13,22.6)PolarPoint(distance=13.0, angle=22.6)>>># With cartesian coordinates>>>PolarPoint.from_cartesian(x=12,y=5)PolarPoint(distance=13.0, angle=22.6)

In these examples, you use the standard instantiation process and your alternative constructor,.from_cartesian(), to createPolarPoint instances using conceptually different initialization arguments.

Exploring Multiple Constructors in Existing Python Classes

Using the@classmethod decorator to provide multiple constructors in a class is a fairly popular technique in Python. There are several examples of built-in and standard-library classes that use this technique to provide multiple alternative constructors.

In this section, you’ll learn about three of the most notable examples of these classes:dict,datetime.date, andpathlib.Path.

Constructing Dictionaries From Keys

Dictionaries are a fundamental data type in Python. They’re present in every piece of Python code, either explicitly or implicitly. They’re also a cornerstone of the language itself because important parts of theCPython implementation rely on them.

You have several ways todefine dictionary instances in Python. You can use dictionary literals, which consist of key-value pairs in curly brackets ({}). You can also calldict() explicitly with keyword arguments or with a sequence of two-item tuples, for example.

This popular class also implements an alternative constructor called.fromkeys(). This class method takes aniterable of keys and an optionalvalue. Thevalue argument defaults toNone and works as the value for all the keys in the resulting dictionary.

Now, how can.fromkeys() be useful in your code? Say you’re running an animal shelter, and you need to build a small application to track how many animals currently live in your shelter. Your app uses a dictionary to store the inventory of animals.

Because you already know which species you’re capable of housing at the shelter, you can create the initial inventory dictionary dynamically, like in the following code snippet:

Python
>>>allowed_animals=["dog","cat","python","turtle"]>>>animal_inventory=dict.fromkeys(allowed_animals,0)>>>animal_inventory{'dog': 0, 'cat': 0, 'python': 0, 'turtle': 0}

In this example, you build an initial dictionary using.fromkeys(), which takes the keys fromallowed_animals. You set the initial inventory of each animal to0 by providing this value as the second argument to.fromkeys().

As you already learned,value defaults toNone, which can be a suitable initial value for your dictionary’s keys in some situations. However, in the example above,0 is a convenient value because you’re working with the number of individuals that you have from each species.

Note: Most of the time, theCounter class from thecollections module is a more appropriate tool for tackling inventory problems like the one above. However,Counter doesn’t provide a suitable implementation of.fromkeys() to prevent ambiguities likeCounter.fromkeys("mississippi", 0).

Othermappings in the standard library also have a constructor called.fromkeys(). This is the case withOrderedDict,defaultdict, andUserDict. For example, thesource code ofUserDict provides the following implementation of.fromkeys():

Python
@classmethoddeffromkeys(cls,iterable,value=None):d=cls()forkeyiniterable:d[key]=valuereturnd

Here,.fromkeys() takes aniterable and avalue as arguments. The method creates a new dictionary by callingcls. Then it iterates over the keys initerable and sets each value tovalue, which defaults toNone, as usual. Finally, the method returns the newly created dictionary.

Creatingdatetime.date Objects

Thedatetime.date class from the standard library is another class that takes advantage of multiple constructors. This class provides several alternative constructors, such as.today(),.fromtimestamp(),.fromordinal(), and.fromisoformat(). All of them allow you to constructdatetime.date objects using conceptually different arguments.

Here are a few examples of how to use some of those constructors to createdatetime.date objects:

Python
>>>fromdatetimeimportdate>>>fromtimeimporttime>>># Standard constructor>>>date(2022,1,13)datetime.date(2022, 1, 13)>>>date.today()datetime.date(2022, 1, 13)>>>date.fromtimestamp(1642110000)datetime.date(2022, 1, 13)>>>date.fromtimestamp(time())datetime.date(2022, 1, 13)>>>date.fromordinal(738168)datetime.date(2022, 1, 13)>>>date.fromisoformat("2022-01-13")datetime.date(2022, 1, 13)

The first example uses the standard class constructor as a reference. The second example shows how you can use.today() to build adate object from the current day’s date.

The rest of the examples show howdatetime.date uses several class methods to provide multiple constructors. This variety of constructors makes the instantiation process pretty versatile and powerful, covering a wide range of use cases. It also improves the readability of your code with descriptive method names.

Finding Your Path to Home

Python’spathlib module from the standard library provides convenient and modern tools for gracefully handling system paths in your code. If you’ve never used this module, then check outPython’s pathlib Module: Taming the File System.

The handiest tool inpathlib is itsPath class. This class allows you to handle your system path in a cross-platform way.Path is another standard-library class that provides multiple constructors. For example, you’ll findPath.home(), which creates a path object from your home directory:

Python
>>>frompathlibimportPath>>>Path.home()WindowsPath('C:/Users/username')
Python
>>>frompathlibimportPath>>>Path.home()PosixPath('/home/username')

The.home() constructor returns a new path object representing the user’s home directory. This alternative constructor can be useful when you’re handlingconfiguration files in your Python applications and projects.

Finally,Path also provides a constructor called.cwd(). This method creates a path object from your current working directory. Go ahead and give it a try!

Providing Multiple Constructors With@singledispatchmethod

The final technique that you’ll learn is known assingle-dispatchgeneric functions. With this technique, you can add multiple constructors to your classes and run them selectively, according to the type of their first argument.

A single-dispatch generic function consists of multiple functions implementing the same operation for different data types. The underlyingdispatch algorithm determines which implementation to run based on the type of a single argument. That’s where the termsingle dispatch comes from.

You can use the@singledispatch or@singledispatchmethod decorator to turn a function or a method, respectively, into a single-dispatch generic function.PEP 443 explains that you can find these decorators in thefunctools module.

In a regular function, Python selects the implementation to dispatch according to the type of the function’s first argument. In a method, the target argument is the first one immediately afterself.

A Demo Example of a Single-Dispatch Method

To apply the single-dispatch method technique to a given class, you need to define a base method implementation and decorate it with@singledispatchmethod. Then you can write alternative implementations and decorate them using the name of the base method plus.register.

Here’s an example that shows the basic syntax:

Pythondemo.py
fromfunctoolsimportsingledispatchmethodclassDemoClass:@singledispatchmethoddefgeneric_method(self,arg):print(f"Do something with argument of type:{type(arg).__name__}")@generic_method.registerdef_(self,arg:int):print("Implementation for an int argument...")@generic_method.register(str)def_(self,arg):print("Implementation for a str argument...")

InDemoClass, you first define a base method calledgeneric_method() and decorate it with@singledispatchmethod. Then you define two alternative implementations ofgeneric_method() and decorate them with@generic_method.register.

In this example, you name the alternative implementations using a singleunderscore (_) as a placeholder name. In real code, you should use descriptive names, provided that they’re different from the base method name,generic_method(). When using descriptive names, consider adding a leading underscore to mark the alternative methods asnon-public and prevent direct calls from your end users.

You can usetype annotations to define the type of the target argument. You can also explicitly pass the type of the target argument to the.register() decorator. If you need to define a method to process several types, then you can stack multiple calls to.register(), with the required type for each.

Here’s how your class works:

Python
>>>fromdemoimportDemoClass>>>demo=DemoClass()>>>demo.generic_method(42)Implementation for an int argument...>>>demo.generic_method("Hello, World!")Implementation for a str argument...>>>demo.generic_method([1,2,3])Do something with argument of type: list

If you call.generic_method() with an integer number as an argument, then Python runs the implementation corresponding to theint type. Similarly, when you call the method with a string, Python dispatches the string implementation. Finally, if you call.generic_method() with an unregistered data type, such as a list, then Python runs the base implementation of the method.

You can also use this technique to overload.__init__(), which will allow you to provide multiple implementations for this method, and therefore, your class will have multiple constructors.

A Real-World Example of a Single-Dispatch Method

As a more realistic example of using@singledispatchmethod, say you need to continue adding features to yourPerson class. This time, you need to provide a way to compute the approximate age of a person based on their birth date. To add this feature toPerson, you can use a helper class that handles all the information related to the birth date and age.

Go ahead and create a file calledperson.py in your working directory. Then add the following code to it:

Pythonperson.py
 1fromdatetimeimportdate 2fromfunctoolsimportsingledispatchmethod 3 4classBirthInfo: 5@singledispatchmethod 6def__init__(self,birth_date): 7raiseValueError(f"unsupported date format:{birth_date}") 8 9@__init__.register(date)10def_from_date(self,birth_date):11self.date=birth_date1213@__init__.register(str)14def_from_isoformat(self,birth_date):15self.date=date.fromisoformat(birth_date)1617@__init__.register(int)18@__init__.register(float)19def_from_timestamp(self,birth_date):20self.date=date.fromtimestamp(birth_date)2122defage(self):23returndate.today().year-self.date.year

Here’s a breakdown of how this code works:

  • Line 1 importsdate fromdatetime so that you can later convert any input date to adate object.

  • Line 2 imports@singledispatchmethod to define the overloaded method.

  • Line 4 definesBirthInfo as a regular Python class.

  • Lines 5 to 7 define the class initializer as a single-dispatch generic method using@singledispatchmethod. This is the method’s base implementation, and it raises aValueError for unsupported date formats.

  • Lines 9 to 11 register an implementation of.__init__() that processesdate objects directly.

  • Lines 13 to 15 define the implementation of.__init__() that processes dates that come as strings with an ISO format.

  • Lines 17 to 20 register an implementation that processes dates that come as Unix time in seconds since theepoch. This time, you register two instances of the overloaded method by stacking the.register decorator with theint andfloat types.

  • Lines 22 to 23 provide a regular method to compute the age of a given person. Note that the implementation ofage() isn’t totally accurate because it doesn’t consider the month and day of the year when calculating the age. Theage() method is just a bonus feature to enrich the example.

Now you can usecomposition in yourPerson class to take advantage of the newBirthInfo class. Go ahead and updatePerson with the following code:

Pythonperson.py
# ...classPerson:def__init__(self,name,birth_date):self.name=nameself._birth_info=BirthInfo(birth_date)@propertydefage(self):returnself._birth_info.age()@propertydefbirth_date(self):returnself._birth_info.date

In this update,Person has a new non-public attribute called._birth_info, which is an instance ofBirthInfo. This instance is initialized with the input argumentbirth_date. The overloaded initializer ofBirthInfo will initialize._birth_info according to the user’s birth date.

Then you defineage() as aproperty to provide a computed attribute that returns the person’s current approximate age. The final addition toPerson is thebirth_date() property, which returns the person’s birth date as adate object.

To try out yourPerson andBirthInfo classes, open an interactive session and run the following code:

Python
>>>frompersonimportPerson>>>john=Person("John Doe",date(1998,5,15))>>>john.age24>>>john.birth_datedatetime.date(1998, 5, 15)>>>jane=Person("Jane Doe","2000-11-29")>>>jane.age22>>>jane.birth_datedatetime.date(2000, 11, 29)>>>linda=Person("Linda Smith",1011222000)>>>linda.age20>>>linda.birth_datedatetime.date(2002, 1, 17)>>>david=Person("David Smith",{"year":2000,"month":7,"day":25})Traceback (most recent call last):...ValueError:unsupported date format: {'year': 2000, 'month': 7, 'day': 25}

You can instantiatePerson using different date formats. The internal instance ofBirthDate automatically converts the input date into a date object. If you instantiatePerson with an unsupported date format, such as a dictionary, then you get aValueError.

Note thatBirthDate.__init__() takes care of processing the input birth date for you. There’s no need to use explicit alternative constructors to process different types of input. You can just instantiate the class using the standard constructor.

The main limitation of the single-dispatch method technique is that it relies on a single argument, the first argument afterself. If you need to use multiple arguments for dispatching appropriate implementations, then check out some existing third-party libraries, such asmultipledispatch andmultimethod.

Conclusion

Writing Python classes withmultiple constructors can make your code more versatile and flexible, covering a wide range of use cases. Multiple constructors are a powerful feature that allows you to build instances of the underlying class using arguments of different types, a different number of arguments, or both, depending on your needs.

In this tutorial, you learned how to:

  • Simulate multiple constructors usingoptional arguments andtype checking
  • Write multiple constructors using the built-in@classmethod decorator
  • Overload your class constructors using the@singledispatchmethod decorator

You also learned how Python internallyconstructs instances of a given class and how somestandard-library classes provide multiple constructors.

With this knowledge, now you can spice up your classes with multiple constructors, equipping them with several ways to tackle the instantiation process 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.

Frequently Asked Questions

Now that you have some experience with multiple constructors in Python, 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.

Python doesn’t support traditional method overloading directly, but you can simulate overloaded constructors using techniques like optional arguments with branching logic in a single.__init__() method, or by utilizing class methods with different parameter sets.

You can create multiple constructors in Python by using techniques like optional arguments, class methods with@classmethod, and single-dispatch methods with@singledispatchmethod.

Because Python allows you to define default values for parameters, you can accept different combinations of arguments in a single.__init__() method. By checking which arguments were provided, you can branch your object initialization logic accordingly.

You can use@classmethod to define alternative constructors in a class. Alternative constructors, or factory methods, are methods that return an instance of the class—like.__init__() does—but do so in a way that can be more flexible or more descriptive than the normal constructor.

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 Multiple Constructors in Your Python Classes

🐍 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.

Python Tricks Dictionary Merge

AboutLeodanis Pozo Ramos

Leodanis is a self-taught Python developer, educator, and technical writer with over 10 years of experience.

» More about Leodanis

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

MasterReal-World Python Skills With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

MasterReal-World Python Skills
With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Rate this article:

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.


Looking for a real-time conversation? Visit theReal Python Community Chat or join the next“Office Hours” Live Q&A Session. Happy Pythoning!

Keep Learning

Related Topics:intermediatepython

Recommended Video Course:Using Multiple Constructors in Your Python Classes

Related Tutorials:

Keep reading Real Python by creating a free account or signing in:

Already have an account?Sign-In

Almost there! Complete this form and click the button below to gain instant access:

Python OOP Cheat Sheet

Object-Oriented Programming in Python: The 7 Best Resources (A Free PDF Cheat Sheet)

🔒 No spam. We take your privacy seriously.


[8]ページ先頭

©2009-2025 Movatter.jp