| ATTRIBUTES
Real Python
Python Classes: The Power of Object-Oriented
Programming
by Leodanis Pozo Ramos © Apr26,2023 ® 26 Comments ® (lateamedate) (phen)
Markas Completed
Table of Contents
* Getting Started With Python Classes
© Defining a Class in Python
© Greating Objects From a Class in Python
© Accessing Attributes and Methods
‘+ Naming Conventions in Python Classes
© Publicvs Non-Public Members
© Name Mangling
‘+ Understanding the Benefits of Using Classes in Python
Data to Classes and Instances
© Class Attributes
© Instance Atabutes
© The._dict_ Attribute
© ynamic Class and Instance Attributes
© Property and Descriptor-Based Attributes
© Lightweight Classes With. slots_
+ Providing Behavior Wih Methods
« Instance Methods With
© Special Methods and Protocols
© Class Methods With @classmethod
© Static Methods With @staticmethod
© Getter and Setter Methods vs Properties
‘© Summarizing Class Syntax and Usage: A Complete Example‘Debugging Python Classes
‘Exploring Specialized Classes From the Standard Library
© Data Classes,
© Enumerations
‘© Using Inheritance and Building Class Hierarchies
© Simple inheritance
© Class Hierarchies
© Extended vs Overridden Methods
© Multiple inheritance
© Method Resolution Order (MRO)
© Mixin Classes
© Benefits of Using Inheritance
+ Using alternatives to Inheritance
© Composition
© Delegation
© Dependency Injection
Creating Abstract Base Classes (ABCs) and interfaces
+ Unlocking Polymorehism With Common Interfaces
+ Conclusion
(© Removeads
(Gai This tutorial hae a related video course created by the Rel Python team, Watch ittogether with the ween
tutorial to deepen your understanding: Inheritance and Internals: Object-Oriented Programming in Python
Python supports the object-oriented programming paradigm through classes. They provide an elegant way to define reusable
pleces of code that encapsulate data and behavior in a single entity. With lasses, you can quickly and intuitively model real-
world objects and solve complex problems.
you're new to classes, need to refresh your knowledge, or want to dive deeper into them, then this tutorial is for you!
int
tutorial, you'l learn how to:
Define Python classes with the class keyword
* Add state to your classes with class and instance attributes
Provide behavior to your classes with methods
Use inheritance to build hierarchies of classes
Provide interfaces with abstract classes
To get the most out of this tutorial, you should know about Python variables, datatypes, and functions. Some experience with
‘object-oriented programming (OOP) i also a plus. Don't worry f you're not an OOP expert yet. In this tutorial, you'll lea the
key concepts that you need to get started and more, Youll also write several practical examples to help reinforce your
knowledge of Python classes.
Free Bonus: Click here to download your sample code for building powerful object blueprints with classes in Python.
@ Take the Q
Programming” quiz. Upon completion you will receive a score so you can track your learning progress over time:
"
Test your knowledge with our interactive “Python Classes - The Power of Object-OrientedGetting Started With Python Classes
Python is @ multiparadigm programming language that supports object-oriented programming (OOP) through classes that you
can define with the class keyword. You can think of a class as a piece of code that specifies the data and behavior that
represent and model a particular type of object
What isa class in Python? A common analogy’s that a class is like the blueprint for a house. You can use the blueprint to create
several houses and even a complete neighborhood. Each concrete house is an object or instance that’s derived from the
blueprint
Each instance can have its own properties, such as color, owner, and interior design. These properties carry what's commonly
known as the object's state, Instances can also have different behaviors, such as locking the doors and windows, opening the
garage door, turning the lights on and off, watering the garden, and more.
In 00°, you commonly use the term attributes to refer to the properties or data associated with a specific object ofa given
class. In Python, attributes are variables defined inside a class with the purpose of storing all the required data for the class to
work
Similarly, you'll use the term metheds to refer to the different behaviors that objects will show. Methods are functions that you
define within a class. These functions typically operate on or with the attributes of the underlying instance or class. Attributes
and methods are collectively referred to as members of a class or object.
You can write fully functional classes to model the real world, These classes will help you better organize your code and solve
complex programming problems.
For example, you can use classes to create objects that emulate people, animals, vehicles, books, buildings, cars, or other
objects. You can also model virtual objects, such as a web server, directory tee, chatbot, file manager, and more,
Finally, you can use classes to build elass hierarchies. This way, you'll promote code reuse and remove repetition throughout
your codebase,
In this tutorial, you'll eam a lot about classes and all the cool things that you can do with them. To kick things off, you'll start,
by defining your first class in Python, Then you'll dive into ather topics related to instances, attributes, and methods.
© Removeads
Defining a Class in Python
To define a class, you need to use the class keyword followed by the class name and a colon, just like you'd do for other
‘compound statements in Python, Then you must define the class body, which will start atthe next indentation level:
Python
class Clasehane:
‘Class bosy
In a class body, you can define attributes and methods as needed. As you already learned, attributes are variables that hold the
class data, while methods are functions that provide behavior and typically act on the class data,
Note: In Python, the body ofa given class works as a namespace where attributes and methods live. You can only access
those attributes and methods through the class or its objects.
‘As an example of how to define attributes and methods, say that you need a cirele class to model diferent circles in a drawing
application, Initially, your class will have a single attribute to hold the radius. It'll also have a method to calculate the cirle’s
Python# carcle.py
Anport math
class Circle:
ef _init_(self, radius):
Tet radius - radius
ef caleulate_area(sei*):
return round(aath.pi * self.radius ** 2, 2)
In this code snippet, you define circte using the class keyword. Inside the class, you write two methods. The .__inst_()
‘method has a special meaning in Python classes. This method is known as the object initializer because it defines and sets the
initial values for your attributes. You'll learn more about this method in the Instance Attributes section.
The second method of circle is conveniently named .calculste_area() and will compute the area of a specific circle by using
its radius. ts common for method names to contain a verb, such as calculate, to describe an action the method performs. In
this example, you've used the math module to access the pt constant as it’s defined in that module.
Note: In Python, the fist argument of most methods is e1¥. This argument holds a reference to the current object so that
‘you can use it inside the class, You'll learn more about this argument in the section on instance methods with self.
Cool! You've written your fist class. Now, how can you use this class in your code to represent several concrete circles? Well,
you need to instantiate your class to create specific circle abjects from I
Creating Objects From a Class in Python
The action of creating concrete objects from an existing class 's known as instantiation, With every instantiation, you create a
new object of the target class. To get your hands dirty, go ahead and make a couple of instances of circle by running the
following code in a Python REPL session:
Python
b9> from efrele import Circle
o> ekrele_a = cirele(e2)
o> ehrele_2 = Cirele(7)
o> edrclea
<_nain_.Circle object at 61162083540
ov circle?
To create an object ofa Python class like cércte, you must cal the circle() class constructor with a pair of parentheses and a
set of appropriate arguments. What arguments? In Python, the class constructor accepts the same arguments as the
-__init_() method. In this example, the cincie class expects the radius argument.
Calling the class constructor with different argument values will allow you to create different objects or instances of the target
class. In the above example, circle_1 and circle_2 are separate instances of circle. In other words, they're two different and
concrete circles, as you can conclude from the code's output.
Great! You already know how to create objects of an existing class by calling the class constructor with the required arguments,
Now, how can you access the attributes and methods of a given class? That's what you'll learn in the next section,
Accessing Attributes and Methods
In Python, you can access the attributes and methods of an object by using det notation with the dat operator. The following
snippet of code shows the required syntax:
Pythonobj. attribute rane
oj method_nare()
Note that the dot .) inthis syntax basically means give me the following attribute or method from this object. The first line
returns the value stored in the target attribute, while the second line accesses the target method so that you can callit
Note: Remember that to calla function or method, you need to use a pair of parentheses and a series of arguments, if
applicable.
Now get back to your circle objects and run the following code:
Python a
>9> from etrele import Circle
o> chrele_a = Cirele(e2)
bop elrele_? = cirele(7)
o> cdrele_a.radive
a
>>> edrele_t-caleutate_areat)
o> irele_2. radius
o> ehnele_2.caleulate_area()
153.96
In the first couple of lines after the instantiation, you access the .radtus attribute on your eircle_1 object. Then you call the
circle a.radivs ~ 100
o> elnelet.radius
o> circle a.calevlate_area()
3uaas.93
Now the radius of eirele_ is entirely different. When you call .calculate_area(), the result immediately reflects this change.
You've changed the object's internal state or data, which typically impacts its behaviors or methods.
© Removeads
Naming Conventions in Python Classes
Before continuing diving into classes, you'll need to be aware of some important naming conventions that Python uses in the
context of classes, Python is a flexible language that loves freedom and doesn’t like to have explicit restrictions. Because of
that, the language and the community rely on conventions rather than restrictions.
Note: Most Python programmers follow the snake_case naming convention, which involves using underscores () to
separate multiple words, However, the recommended naming convention for Python classes is the PascalCase, where
each word is capitalized.
In the following two sections, youl learn about two important naming conventions that apply to class attributes.Public vs Non-Public Members
The frst naming convention that you need te know about is related to the fact that Python doesn’t distinguish between
private, protected, and public attributes like Java and other languages do. In Python, all attributes are accessible in one way
or another. However, Python has a well-established naming convention that you should use to communicate that an attribute
ded for use from outside its containing class or object.
or method isn't
The naming convention consists of adding 2 leading underscore to the member's name. So, in a Python class, you'll have the
following convention:
Member Naming Examples
Publie Use the normal naming patter. radius, caleutate_area()
Non-public Include a leading underscore in names radius, caleulate_area()
Public members are part of theofficial interface or APL of your classes, while non-public members aren't intended to be part of
that API. This means that you shouldn't use non-public members outside the'r defining class.
Its important to note that the second naming convention only indicates that the attribute isnt intended to be used directly
from outside the containing class. It doesn't prevent direct access, though. For example, you can run obj._nane, and you'll
access the content of ._nane. However, this 's bad practice, and you should avoid it
Non-public members exist only to support the internal implementation ofa given class and may be removed at any time, so
you shouldn’t rely on them. The existence of these members depends on how the class is implemented. So, you shouldn't use
them directly in client code. Ifyou do, then your code could break at any moment,
When writing classes, sometimes it's hard to decide ifan attribute should be public or non-public. This decision will depend on
how you want your users to use your classes. In most cases, attributes should be non-public to guarantee the safe use of your
classes. A good approach will be to start with all your attributes as non-public and only make them public f real use cases
appear.
Name Mangling
‘Another naming conwentian that you can see and use in Python classes isto add two leading underscores to attribute and
method names. This naming convention triggers what's known as name mangling.
Name mangling is an automatic name transformation that prepends the class's name to the member's name, like in
_£lasstiane_attrsbute oF _Classhare_nethod. This results in name hiding. In other words, mangled names aren't available for
direct access. They're not part ofa class's public APL
For example, consider the following sample class:
Python ao> class Sampleciass:
(self, valve)}
Teur,_Svatue value
def _nethad(sel#)
print(self._valve)
de? in
o> sonple_instance = SanpleClass(“Hel10!")
>> vars(sample_instance)
('_Sampleclass__value': ‘HelJel"“}
>>> vars(Sampleclass)
rappingorony
"_init_': function sanpleclass._init__ at oxesafdaeo>,
"SanpleCiass_ethoe’: «function SanpleClass._wethod at @x108d"4760>,
Taict_': ,
»
>> sanple_instance = SanpleClass(“Hel 101")
>>> sanple_instance.__value
Traceback (ost secent call last):
Actribuvetrror: “Sanpleclass* object has no attribute “value
9» sanple_instance.__ethos()
‘raceback (most recent call last):
ctributetrror: ‘Sampleclass* object has no attribute *_ethed
In this class, ._value and ._method() have two leading underscores, so their names are mangled to ._sarpleciass_valve and
._SanpleClass_nethod(), aS you can see in the highlighted lines. Python has automatically added the prefix_sanpleClass to
both names. Because of ths internal renaming, you can't access the attributes from outside the class using their original
names. Ifyou try to doit, then you get an attributetrror.
Not
with the given abject. This dictionary plays an important role in Python classes. You'll learn more about itn the ._-dict,
attribute section
Inthe above example, you use the built-in vare() function, which returns a dictionary ofall the members associated
This internal behavior hides the names, creating the illusion of a private attribute or method. However, they're not strictly
private, You can access them through their mangled names:
Python a
o> sanple_instance.,Sanpleclass_value
oo» sonple_instance._SanpleClass_nethod()
Helio!
ts still possible to access named-mangled attributes or methods using their mangled names, although this is bad practice,
‘and you should avoid it in your code, fyou see a name that uses this convention in someone's code, then don’t try to force the
code to use the name from outside its containing class.
Name mangling is particularly useful when you want to ensure that a given attribute or method won't get accidentally
overwritten. It's a way to avoid naming conflicts between classes or subclasses. It’s also useful to prevent subclasses from
overriding methods that have been optimized for better performance.
Wow! Up to this point, you've learned the basics of Python classes and a bit more. You'll dive deeper into how Python classes
work in a moment. But first, t's time to jump into some reasons why you should learn about classes and use them in your
Python projects.Understanding the Benefits of Using Classes in Python
Is it worth using classes in Python? Absolutely! Classes are the building blocks of object-oriented programming in Python. They
allow you to leverage the power of Python while writing and organizing your code. By learning about classes, you'l be able to
take advantage ofall the benefi
at they provide. With classes, you can:
+ Model and solve complex real-world problems: You'l ind many situations where the objects in your code map to real-
world objects. This can help you think about complex problems, which will resultin better solutions to your programming
problems.
+ Reuse code and avoid repetition: You can define hierarchies of related classes. The base classes at the top of a hierarchy
provide common functionality that you can reuse later in the subclasses down the hierarchy. This allows you to reduce
‘code duplication and promote code reuse.
+ Encapsulate related data and behaviors in a single entity: You can use Python classes to bundle together related
attributes and methods in a single entity, the object. This helps you better organize your code using modular and
autonomous entities that you can even reuse actoss multiple projects.
+ Abstract away the implementation details of concepts and objects: You can use classes to abstract away the
implementation details of core concepts and objects. This will help you provide your users with intuitive interfaces (APIs)
to process complex data and behaviors.
* Unlock polymorphism with common interfaces: You can implement a particular interface in several slightly different
classes and use them interchangeably in your code. This will make your code more flexible and adaptable.
In short, Python classes can help you write more organized, structured, maintainable, reusable, flexible, and user-friendly code,
They're a great tool to have under your belt. However, don’t be tempted to use classes for everything in Python. In some
situations, they'll overcomplicate your solutions.
Note: n Python, the public attributes and methods ofa class make up what you'll know as the class’s interface or
application programming interface (API). Youll learn more about interfaces throughout tis tutorial. Stay tuned!
Inthe following section, you'll explore some situations where you should avoid using classes in your code.
Deciding When to Avoid Using Classes
Python classes are prety cool and powerful tools that you can use in multiple scenarios. Because of ths, some people tend to
overuse classes and solve all their coding problems using them. However, sometimes using a class isn’t the best solution.
Sometimes a couple of functions are enough
In practice, you'l encounter a few situations in which you should avoid classes. For example, you shouldn't use regular classes
when you need to:
* Store only dat ole instead.
*+ Provide a single method. Use a function instead.
Use a data class ora named t
Data classes, enumerations, and named tuples are specially designed to store data, So, they might be the best solution if your
class doesn’t have any behavior attached,
your class has a single method in its API, then you may not require a class, Instead, use a function unless you need to retain a
certain state between calls. f more methods appear later, then you can always create a class. Remember the Python principle:
‘Simple is better than complex. (Source)
‘Additionally, you should avoid creating custom classes to wrap up functionality that’s available through built-in types or third-
party classes. Use the type or third-party class directly.
You'l ind many other general situations where you may not need to use classes in your Python code. For example, classes
aren't necessary when you're working with:+ A.small and simple program or seript that doesn’t require complex data structures or logic. In this case, using classes
may be overkill
+ Apperformance-critical program. Classes add overhead to your program, especially when you need to create many
objects. This may affect your code's general performance.
+ Alegacy codebase. fan existing codebase doesn't use classes, then you shouldn'tintroduce them. This will break the
‘current coding style and disrupt the code's consistency.
+ Ateam with a different coding style. f your current team doesn't use classes, then stick with their coding style. This will
ensure consistency across the project.
+ Acodebase that uses functional programming. Ifa given codebase is currently written with a functional approach, then
you shouldn't introduce classes, This will break the underlying coding paradigm.
You may find yourselfin many other situations where using classes will be overkill Classes are great, but don’t turn them into a
cone-size-fits-all type of tool. Start your code as simply as possible. Ifthe need for a class appears, then go for it.
Attaching Data to Classes and Instances
‘As you've leammed, classes are great when you must bundle data and behavior together in a single entity. The data will come in
the form of attributes, while the behavior will come as methods. You already have an idea of what an attribute is. Now it's time
to dive deeper into how you can add, access, and modify attributes in your custom classes.
First, you need to know that your classes can have
yo types of attributes in Python:
1. Class attributes: A class attribute is a variable that you define inthe class body directly Class attributes belong to their
containing class. Their data is common to the class and all its instances.
2. Instance attributes: An instance is a variable that you define inside a method. Instance attributes belong to a concrete
instance of a given class. Their data is only available to that instance and defines its state
Both types of attributes have their specific use cases. Instance attributes are, by far, the most common type of attribute that
you'll use in your day-to-day coding, but class attributes also come in handy.
© Removeads
Class Attributes
Class attributes are variables that you define directly in the class body but outside of any method. These attributes are tied to
the class itself rather than to particular objects ofthat class.
Al the objects that you create from a particular class share the same class attributes with the same original values. Because of
this, fyou change a class attribute, then that change affects all the derived objects
‘As an example, say that you want to create a class that keeps an internal count of the instances you've created. In that case,
you can use a class attribute:
Python ao> class objectCounter:
rum snstances = 0
def _inis_(self)
‘bjectCounter.num_instances += 2
95 Objectcounter()
<_nain_.objectcounter object at oxi03624810>
>>> objactcounter)
253 Objectcounter()
<_nain_.objectcounter object at @xu0395b759>
29> Obsectcounter()
9> Objectcounter.num_instances
o> counter = objectcounter()
>>> counter mum instances
5
ObjectCounter Keeps a -nun_instances class attribute that works as a counter of instances, When Python parses this class, it
initializes the counter to zero and leaves it alone. Creating instances of this class means automatically calling the ._inst_()
‘method and incremementing .oun_instances by one.
Note: In the above example, you've used the class name to access .num_instances inside ._Antt_(), However, using the
built-in type0) function is best because it| make your class more flexible:
Python
o> class objectcounter:
ru_snstances ~
def _tnit_(self)
ype(sel*).nun_instances += 1
The built-in type() function returns the class or type of self, which is objectcounter inthis example. This subtle change
makes your class more robust and rel'able by avoiding hard-coding the class that provides the attribute.
Its important to note that you can access class attributes using either the class or one of its instances. That's why you can use
the counter abject to retrieve the value of .nun_ instances. However, if ou need to modify a class attribute, then you must use
the class itself rather than one of ts instances.
For example, ifyou use seif to modify .nun_instances, then you'll be overriding the original class attribute by creating a new
instance attribute:
Python
9p class objectCounter:
num instances ~ 0
def _tnit_(sel#)
Telf.num_anstances == 3
o> Objectcounter()
<_nain_.objectcounter object at 91303087558
25> Objactcounter()
<_nain__objectCounter object at 0¥1038¢58905
S53 objectcounter()
<_nain__.objectcounter object at exlaa96a89@>
>>> objactcounter)
<_nain_.Objectcounter object at exte36fatte>
29> Objectcounter.num_instances
°You can’t modify class attributes through instances of the containing class. Doing that will create new instance attributes with
the same name as the original class attributes. That's why objectcounter.nus_instances returns @in this example. You've
overridden the class attribute in the highlighted line.
In general, you should use class attributes for sharing data between instances ofa class. Any changes on a class attribute will,
be visible to all the instances ofthat class.
Instance Attributes
Instance attributes are variables tied to a particular object ofa given class, The value of an instance attribute is attached to the
object itself So, the attribute's value is specific to its containing instance.
Python lets you dynamically attach attributes to existing objects that you've already created. However, you most often define
instance attributes inside instance methods, which are those methods that receive sel as their first argument.
Note: Even though you can define instance attributes inside any instance method, is best to define all of them in the
-_init_() method, which is the instance initializer. This ensures that all of the attributes have the correct values when
you create a new instance. Additionally, it makes the code more organized and easier to debug,
Consider the following car class, which defines a bunch of instance attributes:
Python
# carpy
class Cart
Gof _init_(self, make, model, year, color)
Self make = make
self-rodel = modet
self.year = year
self color = color
self-started =
self. speed
self.#ax_speed = 200
In this class, you define a total of seven instance attributes inside .__inis_(). The attributes .nake, .nodel, .year, and .coler
‘ake values from the arguments to .__init_(), which are the arguments that you must pass to the class constructor, car(), to
create concrete objects.
Then, you explicitly initialize the attributes .startec, -spees, and .max_speed with sensible values that don't come from the user.
Note: Inside a class, you must access all instance attributes through the self argument. This argument holds a reference
to the current instance, which is where the attributes belong and live. The sei argument plays a fundamental role in
Python classes. You'll learn more about sei¥ in the section Instance Methods With self
Here’s how your car class works in practice:
Pythono> from ear import Car
29> toyota.canry = Car("Toyota", "camry", 2022, "Re
o> toyota_canry.make
Toyota’
>> Royota_canry. model
“canry"
>> toyota_canry. color
fed
>>> toyota_carry. speed
29> Ford_mustang = Car(*Fore
>>> ford_pustang. make
>>> ford_nustang. made
>>> ford sustang. year
>>> ford_mustang.max_speed
208
Mustang", 2022, "Black"
In these examples, you create two different instances of car. Each instance takes specific input arguments at instantiation time
to initialize part ofits attributes. Note how the values of the associated attributes are different and specific t the concrete
instance,
Unlike class a
ibutes, you can’t access instance
containing instance:
butes through the class. You need to access them through their
Python a
Traceback (most secent all last):
Actributeérror: type object “Car* has no attribute “make
Instance attributes are specific to a concrete instance ofa given class. So, you can't access them through the class object. If you
try to do that, then you get an attributetrror exception.
© Removeads
The .__dict__ Attribute
In Python, both classes and instances have a special attribute called .__aics__. This attribute holds a dictionary containing the
writable members of the underlying class or instance. Remember, these members can be attributes or methods. Each key in
-_dict__represents an attribute name. The value associated with a given key represents the value of the corresponding
attribute,
In a class, .__aict__will contain class attributes and methods. n an instance, .__dict_will hold instance attributes.
When you access a class member through the class object, Python automatically searches for the member's name in the class
_dict_. the name isn't there, then you get an attributetrror
Similarly, when you access an instance member through a concrete instance of a class, Python looks for the member's name in
the instance ._eict_. Ifthe name doesn't appears there, then Python looks in the class .__dict__. the name isn’t found,
then you get a Naretrcor.
Here's a toy class that illustrates how this mechanism works:
Python# sanple_dict.py
class Sanpleclass
class_attr = 100
ef _init_(s0lf, instance att):
Tels Anstance attr = dnstance_attr
ef nethod(sei"):
print(#"Class attribute: {self.class_atte}")
print(#*rnstance attnibute: {self instance atte}")
In this class, you define a class attribute with a value of 1a In the .__init_() method, you define an instance attribute that
‘takes its value from the user’ input. Finally, you define a method to print both attributes.
Now its time to check the content of .__eict__ in the class object. Go ahead and run the following code:
Python a
o> from sample dict inport Sanpleclass
>>> Sanpleclass..class_atte
108
>>> Sanplectass.__éiet_
appingprony
module: *_matn_',
lass_attr': 100,
"_init_': function SampleCiass._init__ at ext8366220>,
"Rethod"® ,
atet_'s cattrdbute ‘ict’ of ‘Sampleclass' ebjects>,
“weakref_': cattrsbute "_weakref_’ of “SanpleClass" objects»,
Titec": Hone
»
>>> Sanpleclass.__éict_
108
lass_attr"]
The highlighted lines show that both the class attribute and the method are in the class .__diet__ dictionary. Note how you can
Use .__ict__to access the value of class attributes by specifying the attribute's name in square brackets, as you usually access
keys in a dictionary,
Note: You can access the same dictionary by calling the built-in vars¢) function on your class or instance, as you did
before
Ininstances, the .__dict__ dictionary will contain instance attributes only:
Python
o> Instance = Sanplecass("Hello!")
o> dnstance. instance attr
“Hello!
o> instance. ethod()
Class attribute: 108
Instance attribute: Hello!
o> instance. diet
(anstance attr’: *HelZet")
>>> Anstance.__diet_{~instance atte")
swetlot
o> instancs
_dict_{instance attr"] = "hello, Pythonistal”™
>>> instance. instance_ atte
“Hello, Pythontstat*The instance ._dict__ dictionary in this example holds .instance_attr and its specific value for the abject at hand. Again, you
can access any existing instance attribute using .__¢ict__and the attribute name in square brackets.
You can modify the instance .__eiet__ dynamically. This means that you can change the value of existing instance attributes
through ._dict_,as you did in the final example above. You can even add new attributes to an instance usingits ._eict_
dictionary.
Using ._aict_ to change the value of instance attributes will allow you to avoid aecursionérrer exceptions when you're wiring
descriptors in Python. You'll learn more about descriptors in the Property and Descriptor-Based Altributes section.
Dynamic Class and Instance Attributes
In Python, you can add new attributes to your classes and instances dynamically. This possibility allows you to attach new data
‘and behavior to your classes and abjects in response to changing requirements or contexts. It aso allows you to adapt existing
classes to specific and dynamic needs.
For example, you can take advantage of this Python feature when you don’t know the required attributes of a given class at the
time when you're defining that class itself.
Consider the following class, which aims to store a row of data from a database table or a CSV file:
Python
o> class Record:
“o'Hpld a record of data."**
In this class, you haven't defined any attributes or methods because you don’t know what data the class will store. Fortunately,
you can add attributes and even methods to this class dynamically
For example, say that you've read a row of data from an enployees.csv file using csv. 0sctReader, Ths class reads the data and
returns it in a dictionary-like object. Now suppose that you have the following dictionary of data:
Python
o> john = ¢
name"! “ohn Doe,
position": "Python Developer",
“departnent™: “Engineering”,
salary’: B0aee,
“hire date": "2020-01-01",
“is manager": False,
Next, you want to add this data to an instance of your Recor¢ class, and you need to represent each data field as an instance
attribute. Here's how you can do it:
Python ao> John_racord = Record()
9p for field, value in john.ttens()
setattr(jehn_record, flelé, valve)
29> Joha_recond.nane
ohn Doe”
95> John_record. departrent
Engineering!
>> John_record.__dict_
position’: “Python Developer’,
department": "Engineering’,
is manager’: False
In this code snippet, you first create an instance of Record called John_record, Then you run a for loop to iterate over the items
of your dictionary of data, john. Inside the loop, you use the built-in setattr() function to sequentially add each field as an
attribute to your john record object. Ifyou inspect john_recore, then you'll note that it stores al the original data as attributes.
You can also use dot notation and an assignment to add new attributes and methods to a class dynamically:
Python
o> class User
pass
bo» # Add instance attributes dynanically
29> Jane = User()
>>> Jane.nane = "Jane voe™
>>> Jane.job = “bata Engineer”
o> # Add methods dynamically
>>> Gof _Intt_(self, nane, Job)
Telr.nane
self-jeb
95 User. tne
rappingprony(¢
"_init_': function _init__ at extessccaea>
»
>>> Linda = User("Linda saith", “Teaw Lead")
o> Hinds._dict_
Cane": "Linda Smita’, "Job": "Team Lead")
Here, you first create a minimal user class with no custom attributes or methods. To define the class's body, you've just used a
pass statement as a placeholder, which is Python's way of doing nothing,
Then you create an object called jane, Note how you can use dot notation and an assignment to add new attributes to the
instance. In this example, you add .nane and . job attributes with appropriate values.
Then you provide the user class with an initializer or .__tnit__() method. n this method, you take the nare and joo arguments,
which you turn into instance attributes in the method's body. Then you add the method to user dynamically. After this addition,
you can create user objects by passing the name and job to the class constructor.‘As you can conclude from the above example, you can construct an entire Python class dynamically, Even though this
capability of Python may seem neat, you must use it carefully because it can make your code dificult to understand and reason
about.
© Removeads
Property and Descriptor-Based Attributes
Python allows you to add function-like behavior on top of existing instance attributes and turn them into managed attributes.
This type of attribute prevents you from introducing breaking changes into your APIs.
In other words, with managed attributes, you can have function-like behavior and attribute-like access at the same time. You
don't need to change your APIs by replacing attributes with method calls, which can potentially break your users’ code.
To create a managed attribute with function- like behavior in Python, you can use either a property or a descriptor, depending
‘on your specific needs.
Note: To cive deeper into Python properties, check out Python's nroperty(): Add Managed Attributes to Your Classes.
‘As an example, get back to your circle class and say that you need to validate the radius to ensure that it only stores positive
‘numbers. How would you do that without changing your class interface? The quickest approach to this problem is to use a
property and implement the validation logic in the setter method.
Here's what your new version of circle can look like:
Python
# carete.py
Snport math
clses circle:
ef init__(self, radius):
Self radios = radius
prop
ef radius(sel"):
return self. radius
Gradius. setter
GeF radius(selt, value)
4 not tsinstance(value, int | float) or value
raise Valuetrror("positive ausber expected")
_radius ~ value
sel
Gof caleulate.areatsei*):
return round(aath.pl * self.radiust=2, 2)
To tum an existing attribute like .radius into a property, you typically use the @arcaerty decorator to write the getter method.
The getter method must return the value of the attribute. In this example, the getter returns the circles radius, which is stored
inthe non-public ._radtus attribu
To define the setter method of a property-based attribute, you need to use the decorator Gate ter. In the example,
Yyou use gradivs. setter. Then you need to define the method itself. Note that property setters need to take an argument
providing the value that you want to store in the underlying attribute,
Inside the setter method, you use a conditional to check whether the input value is an integer or a floating-point number. You
also check ifthe value is less than or equal to, Ifeither's true, then you raise a valuetrror with a descriptive message about
the actual issue. Finally, you assign value to ._ragius, and that's it. Now, .radius is a property-based attribute.
Here's an example of this new version of cérete in action:
Python>> from eirele inport Circle
o> ednele_a = chrele(26a)
o> elnelera. adie
108
o> eirele_a radius = 500
o> edele_t. radius = @
Traceback (nost recent call last):
Voluetrrort positive number expected
o> cdrele_2 » Cirele(-108)
Traceback (most recent 911 Last):
\eluztrror: positive number expected
o> ehrede.3 = Cirede("320")
Traceback (most secent e211 last):
Voluctrror: positive number expected
The first instance of circle in this example takes a valid value for ts radius, Note how you can continue working with .radtus as
a regular attribute rather than as a method. If you try to assign an invalid value to .radius, then you get a valuetrror:
Note: Remember to reload the circle. py module if you're working on the same REPL session as before. This
recommendation will also be valid forall the examples in this tutorial where you change modules that you defined in
previous examples.
Its important to note that the validation also runs at instantiation time when you call the class constructor to create new
Instances of circle, This behavior is consistent with your validation strategy.
Using a descriptor to create managed attributes is another powerful way to add function-Like behavior to your instance
attributes without changing your APIs. Like properties, descriptors can also have getter, setter, and other types of methods.
Note: To learn more about descriptors and how to use them, check out Python Descriptors: An Introduction,
‘To explore how descriptors work, say that you've decided to continue creating classes for your drawing application, and now
‘you have the following Square class:
Python
1 square.py
class Square:
Gof _init_(self, side):
Teltside = side
@property
GeF stde(seif)
return self._side
side. setter
ef side(self, value):
Af not isinstance(value, int | float) or value
raise ValucError("positive aurber expected”)
side » value
self,
ef caleulate_area(sel*):
return round(self.side'2, 2)
This class uses the same pattern as your Circie class, Instead of using radius, the Square class takes a side argument and
‘computes the area using the appropriate expression for a square.This class is pretty similar to circle, and the repetition starts ooking odd, Then you think of using a descriptor to abstract away
the validation process. Here's what you come up with:
Python
# shapes.py
Amport math
loss Posstivenunos
def _set_nane_(self, owner, name)
mu
ef _get_(sit, Anstance, ouner)
Feturn dnstance.__éict_[self._nare]
Ger _set_(soif, instance, value):
FF not Ssinstance(value, int | float) or value
positive nurber expected")
instance.__dict_[seif._nane] = value
raise Valuetrror
class Cirele:
radius = Positivewumber()
Gof _init_(soif, radius):
Ter radius = raatus
ef caleulate_area(sel*):
return round(nath-pi * self.radius**2, 2)
lass square:
side ~ Positivetonber()
ef _intt_(self, side):
Tet sige ~ side
Gof caleulate_areatsei*):
return round(self-side**2, 2)
The frst thing to notice in this example is that you moved all the classes to a shapes.py file. n that file, you define a descriptor
class called Posstivewunver by implementing the ._get_().and.._set__() special methods, which are part of the descriptor
protocol.
Next, you remove the .radius property from cireie and the .si¢e property from square. In Circle, you add a -radius class
attribute, which holds an instance of pos:tivenunber. You do something similar in square, but the class attribute is appropriately
named «side
Here are a few examples of how your classes work now:
Python a>9> from shapes import Circle, square
o> edeele = cirete(aee)
o> ekrele. radius
108
o> efrele.radius = 500
bo» edrele. radius
sea
o> cirele.radius = @
Traceback (most secent 911 Last):
\elustrror: positive nunben expected
>>> square = Square(206)
bo» square. side
>>> square.side = 302
side
>>> square.side = -100
Traceback (most recent call last):
Velucerror: positive nunber expected
Python descr
you remove repeti
ors provide a powerful tool for adding function-like behavior on top of your instance attributes. They can help
n from your code, making it cleaner and more maintainable. They also promote code reuse.
emoveads
Lightweight Classes With .__slots__
In.a Python class, using the .__stets__attribute can help you reduce the memory footprint of the corresponding instances. This
attribute prevents the automatic creation of an instance .__dict__ Using .__stots__is particularly handy when you have a
class with a fixed set of attributes, and you'll use that class to create a large number of objects.
In the example below, you have a Point class with a ,__stots__ attribute that consists of a tuple of allowed attributes. Each
attribute will represent a Cartesian coordinate:
Python a
o> clase point:
slots = ("x") °°)
def _init_(self. x y)
selfy
5> point = Potnt(s, 8)
9> point. dict
‘Traceback (ost recent call last):
int’ object has no attribute *_aict_*
This Point class defines .__stots__as a tuple with two items. Each item represents the name of an instance attribute. So, they
‘must be strings holding valid Python identifiers.
Note: Although .__stots__can hold a list object, you should use a tupte object instead. Even if changing the list in
«__slots__after processing the class body had no effect, it'd be misleading to use a mutable sequence there.
Instances of your ont class don’t have a .__oict_ as the code shows. This feature makes them memory-effcient. To
illustrate this eficiency, you can measure the memory consumption of an instance of Peint. To do this, you can use the Pympler
library, which you can install from PyP!.using the pip instatl pynpler command,
Once you've installed Pympler with pip, then you can run the following code in your REPL:Python
>>> from pympler Inport asizeot
o> asizeof.asizeof(Point(t, 8))
The asizeor() function from Pympler says that the object Point(4, 8) occupies 112 bytes in your computer's memory. Now get
back to your REPL session and redefine Point without providing ._siots_ attribute. With this update in place, go ahead and
run the memory check again:
Python
o> class Point:
>>> asizeof astzeof (Point(4, 8))
8), now consumes 528 bytes of memory. This number is over four times greater than what you got
with the original implementation of Point. Imagine how much memory ._slots__would save ifyou had to create a million
points in your code.
The .__slots_ attribute adds a second interesting behavior to your custom classes. It prevents you from adding new instance
attributes dynamically:
Python 3
slots = ("x") 7)
def init__(self, % ¥)
selfy
o> point = point(s, 8)
o> point.z = 16
‘iraceback (most recent call last):
actributetrror: ‘Point* object has no attribute *
‘Adding a .__stots__to your classes allows you to provide a series of allowed attributes. This means that you won't be able to
{add new attributes to your instances dynamically. Ifyou try to do it, then you'll get an attributetrror exception,
‘Aword of caution is in order, as many of Python's built-in mechanisms implicitly assume that objects have the ._«
attribute. When you use .
not work as expected anymore.
slots_(), then you waive that assumption, which means that some of those mechanisms might
Providing Behavior With Methods
Python classes allow you to bundle data and behavior together in a single entity through attributes and methods, respectively.
You'll use the data to define the objects current state and the methods to operate on that data or state
‘A method is just a function that you define inside a class, By defining it there, you make the relationship between the class and
the method explicit and clear.
Because they're just functions, methods can take arguments and return values as functions do. However, the syntax for calling
‘a method is a bit different from the syntax for calling 2 function. To call a method, you need to specify the class or instances in
which that method is defined, To do this, you need to use dot notation. Remember that classes are namespaces, and their
‘members aren't directly accessible from the outside.
In a Python class, you can define three different types of methods:1. Instance methods, which take the current instance, self, as thei fist argument
2.€lass methods, which take the current class, e1s, as their first argument
3. Static methods, which take neither the class nor the instance
Every type of method has its own characteristics and specific use cases. Instance methods are, by far, the most common
‘methods that you'll use in your custom Python classes,
Note: To learn more about instance, class, and static methods, check out Python’s Instance, Class, and Static Methods
Demystified
In the following sections, you'l dive into how each of these methods works and how to create them in your classes. To get
started, you'll begin with instance methods, which are the most common methods that you'll define in your classes.
© Removead
Instance Methods With self
In a class, an instance method isa function that takes the current instance as its first argument. In Python, this fist argument is
called se1# by convention.
Note: Naming the current instance self isa strong convention in Python. It's so strong that it may look like seit is one of
the Python keywords. However, you could use any other name instead of self.
Even though it's possible to use any name for the first argument of an instance method, using self Is definitely the right
choice because itll make your code look like Python code in the eyes of other developers
‘The self argument holds a reference to the current instance, allowing you to access that instance from within methods. More
importantly, through se1f, you can access and modify the instance attributes and call other methods within the class.
To define an instance method, you just need to write a regular function that accepts sel as its fist argument. Up to this point,
you've already written some instance methods. To continue learning about them, turn back to your car class.
Now say that you want to add methods to start, stop, accelerate, and brake the car. To kick things off, you'll begin by writing the
stare() and -stop() methods:
Python
# cary
class car:
GeF _init_(self, make, model, year, color):
Selfomake = make
solf-rodel = odet
self-year = year
self.color ~ color
self-started » False
self. speed
self-#ax_speed = 200
ef stare(se4):
prine(*stanting the ear..."
self started
ef stop(sei):
prine("stopping the ear...
self.started =
‘The .start() and .stop() methods are pretty straightforward. They take the current instance, self, as their fist argument.
Inside the methods, you use se1f to access the .started attribute on the current instance using dot notation. Then you change
the current value ofthis attribute to Truein .start() and to False in .stop(), Both methods print informative messages to
illustrate what your caris doing,Note: Instance methods should act on instance attributes by either accessing them or changing their values. Ifyou find
yourself writing an instance method that doesn't use seif in its body, then that may not be an instance method. In this,
case, you should probably use a class method ora static method, depending on your specific needs,
Now you can add the .accelerste() and .brake() methods, which will be a bit more complex:
Python
# cary
clases Car
Ger init__(self, make, model, year, color):
Selfsmake = make
self-#odel = odet
self.year = year
self-color = color
self-started = False
self-speed = 6
self.#ax_speed = 200
accelerate(self, value):
Sf not self started:
print(*Car is not started!")
Sf self.speed + value «> self .nax speed:
self. speed += value
Self speed = self.max_speed
prine(#*Accelerating to (self.speed) kn/h..
ef brake(sel, value)
Af selt.speed - value > 0:
self.speed -~ value
ease:
self speed
prine(#*Braking to {self-speed) ke/h.
‘The .accelerste() method takes an argument that represents the increment of speed that occurs when you call the method.
ity, you haven't set any validation for the input increment of speed, so using negative values can cause issues. Inside
the method, you first check whether the car's engine is started, returning immediately ifit's not,
Then you check ifthe incremented speed is less than or equal to the allowed maximum speed for your car. Ifthis condition is
true, then you increment the speed, Otherwise, you set the speed to the allowed limit.
The -brake() method works similarly. This time, you compare the decremented speed to @ because cars can’t have a negative
speed, Ifthe condition is true, then you decrement the speed according to the input argument, Otherwise, you set the speed to
its lower limit, 6. Again, you have no validation on the input decrement of speed, so be careful with negative values.
Your class now has four methods that operate on and with its attributes. It’s time for a drive:
Pythono> from ear import Car
2022, "slack™)
mustang = Can("Fore", "mustan
ustang.start()
Starting the car...
o> ford _sustang. accelerate(1e0)
Accelerating to 108 n/n.
>>> ford mustang. brake(5@)
raking to 50 ka/n
>>> ford _eustang. brake(@9)
Braking to @ n/n
>>> ford _sustang. stop)
o> ford mustang. accelerate(100)
Great! Your car class works nicely! You can start your car's engine, increment the speed, brake, and stop the car's engine. You're
also confident that ne one can increment the car's speed if the car's engine is stopped, How does that sound for minimal
‘modeling of a car?
It's important to note that when you call an instance method on a concrete instance like ford_qustang using dot notation, you
don't have to provide a value for the set argument. Python takes care ofthat step for you. It automatically passes the target
instance to sei. So, you only have to provide the rest of the arguments,
However, you can manually provide the desired instance if you want. To do this, you need to call the method on the class:
Python a
29> ford_austang = Can("Fora, "Mustang", 2022, "slack")
>>> Car. start(ford_pustang)
Starting the car.
>>> Can. accelerate( ford mustang, 180)
Accelerating to 108 ka/h.
95> Car.brake(ford_nustang, 100)
Brakeing to @ ka/h
>e> Car. stop(ford_qustang)
Stopping the car...
o> an. start()
Traceback (most recent all last):
‘yperrron: Car.start) missing 1 required positional argusent: “self?
In this example, you call instance methods directly on the class. For this type of call to work, you need to explicitly provide an
appropriate value to the se1¢ argument. In this example, that value is the Ford_nustang instance. Note that ifyou don't provide a
suitable instance, then the call falls with a typeError. The error message is pretty clear. There's a missing positional argument,
self
© kemoveads
Special Methods and Protocols
Python supports what it calls special methods, which are also known as dunder or magic methods. These methods are
‘ypically instance methods, and
feature in common: Python calls them automatically in response to specific operations,
y're a fundamental part of Python's internal class mechanism, They have an important
Python uses these methods for many different tasks. They provide a great set of tools that will allow you to unlock the power
of classes in Python.
You'll recognize special methods because their names start and end with a double underscore, which isthe origin oftheir other
‘name, dunder methods (double underscore). Arguably, ._init_() is the most common special method in Python classes. AS
you already know, this method works as the instance initializer. Python automatically calls it when you call a class constructor.You've already written a couple of .__inst__() methods. So, you're ready to learn about other common and useful special
‘methods. For example, the ._str_() and ._repr_(), methods provide string representations for your objects.
Go ahead and update your car class to add these two methods:
Python
2 cary
class Cart
*
cer sn
(sel):
return #*(se1f make), (self.model), (self.color}: ({self.year})"
0 _repr_(sel#):
return (
#°(type(se1®).__name_)"
ke="(self make)",
*(selfmodel)", *
self.year), *
self. calor}")*
The .__str_() method provides what's known as the informal string representation of an object. This method must return a
string that represents the abject in a user-friendly manner. You can access an object’ informal string representation using
either ste() orprint(),
The .__repr_() method is similar, but it must return a string that allows you to re-create the object if possible. So, this method
returns what's known as the formal string representation of an object. Ths string representation is mostly targeted at Python
programmers, and its pretty useful when you're working in an interactive REPL session.
In interactive mode, Python falls back to calling ._repr_() when you access an object or evaluate an expression, issuing the
formal string representation ofthe resulting object. In sctipt mode, you can access an object's formal string representation
using the built-in repe¢) function,
Run the following code to give your new methods a try. Remember that you need to restart your REPL or reload car.py
Python
>>> from ear inport Car
>9> toyota.canry = Car("Toyota", "cawry", 2622, *Ree")
>>> str(toyota_camry)
‘Toyota, camry, Red: (2022)
>>> print toyeta_canry)
Toyota, Canry, Red: (2022)
>>> toyota_canry
Ccan(nake="Toyeta", model="canry”, yes
o> repr toyota_canry)
‘Car(nake="Toyota", odel="Canry", year=2022, color="Red")
1022, color="ted")
When you use an instance of car as an argument to str() or print(), you get a user-friendly string representation of the car at
hand. This informal representation comes in handy when you need your programs to present your users with information
about specific objects,
you access an instance of car directly n a REPL session, then you get a formal string representation of the object. You can
copy and paste this representation to re-create the object in an appropriate environment. That's why this string representation
is intended to be useful for developers, who can take advantage of t while debugging and testing their code.
Python protocols are another fundamental topic that's closely related to special methods. Protocols consist of one or more
special methods that support a given feature or functionality. Common examples of protocols include:Protocol Provided Feature Special Methods
Iterator Allows you to create iterator objects ._iter_() and ._next_0)
terable Makes your objects iterable -_iter_0.
Descriptor Lets you write managed attributes __get_( and optionally ._set_(), ._elete_(),and
set nane_()
Content Enables an object to work on with center and ._exit_0.
manager statements
Of course, Python has many other protocols that support cool features of the language. You already coded an example of using
the descriptor protocol in the Property and Descriptor. Based Attributes section.
Here's an example of a minimal threeopoint class that implements the iterable protocol:
Python a
99> class ThreeoPosnt
def _inis_(self, % yy 2):
pelfxe x
selfy = y
det _tter_(self)
yield fron (self.x, self.y, self.2)
bop List(thresoroine(4, &, 46))
4, 8, 18]
This class takes three arguments representing the space coordinates of a given point. The .__iter__) method is a generator
function that returns an iterator, The resulting iterator yields the coordinates of mreeoPoint on demand,
The call to 1ist() iterates over the attributes .x,.y, and .2, returning a List object. You don't need to call ,_ tter_() directly.
Python calls it automatically when you use an instance of threeoPoint in an iteration.
@ Removeads
Class Methods With @classmethod
You can also add elass methods to your custom Python classes. A class method is a method that takes the class abject as its
fist argument
Python. So, you should stick to it.
stead of taking se1f. In this case, the argument should be called eis, which is also a strong convention in
You can create class methods using the gclzssnethad decorator. Providing your classes with multiole constructors is one of the
‘most common use cases of class methods in Python.
For example, say you want to add an alternative constructor to your threebPoint so that you can quickly create points from
tuples or lists of coordinates:
Python# point.py
class ThreeoPoint
ef _init_(self, %) yy 2):
welf.x x
selfy =
self
ef _tter_(s019:
Yield Fron (self.x, self.y, sel#.2)
@classnethod
ef from_sequence(cls, sequence):
return cis(*sequence)
ef _repr_(sel#):
return #{type(set?)
pame_)({se1 x}, {self.y}, (selF.2))"
In the -#ron_sequence() class method, you take a sequence of coordinates as an argument, create a ThreedPoint object from it,
and return the object tothe caller. To create the new object, you use the cls argument, which holds an implicit reference to the
current class, which Python injects into your method automaticaly.
Here's how this class method works:
Python
>>> from point inport ThreebPoint
>>> ThreeDPoint.fron_sequencel(4, 8, 16))
TrreeoPoint(4, 8, 36)
o> point = TheeeDpoint(7, 34, 21)
>>> point. from_sequence((3, 6, 9)
ThreeDPoint(3, 6, 9)
In this example, you use the ThreeoPoint class directly to access the class method .fron_sequence(). Note that you can also
access the method using a concrete instance, like point in the example, In each of the calls to -from_sequence(), you'll get a
completely new instance of threeb?oint. However, class methods should be accessed through the corresponding class name
for better clarity and to avoid confusion.
Static Methods With @staticmethod
Your Python classes can also have static methods. These methods don't take the instance or the class as an argument. So,
they’re regular functions defined within a class. You could've also defined them outside the class as stand-alone function.
You'll typically define a static method instead of a regular function outside the class when that function is closely related to
your class, and you want to bundle it together for convenience or for consistency with your codes API. Remember that calling a
function i a bit different from calling a method. To call a method, you need to specify a class or object that provides that
method
Iyou want to write a static method in one of your custom classes, then you need to use the gstat iceethod decorator. Check out
the .show_intro_eessage() method below:
Python# point.py
class ThreeoPoint
ef _init_(self, x) yy 2):
selfy=y
self
ef _iter_(s019:
Yield Fron (self.x, self.y, sel#.2)
@classnethod
ef From _sequence(cls, sequence):
return cls(*sequence)
@staticnethod
dof show_intro_message(nane)
prist(#*Hey {nane)! This 1s yous 30 Point!")
ef _repr_(seif):
return #*Ctype(ser*)._name_)C(self.x), (self.y}, (self.2))"
The -show_sntro_nessage() static method takes a nane as an argument and prints a message on the screen, Note that this is
only a toy example of how to write static methods in your classes,
Static methods like .show_intre_nessage() don’t operate on the current instance, self, or the current class, cls. They work as
independent fun
don't necessarily affect the class orits instances.
ns enclosed in a class. You'l typically put them inside a class when they're closely related to that class but
Here's how the method works:
Python a
9> from point import ThreebPoint
29> ThreeoPoint .show_intro_messaget"ythonists")
Hey Pythonistal This &s your 30 Point!
>>> point = threepPoint(2, 4, 6)
95> point. show_sntra_nessage("?Python developer")
Ney Python developer! TAs 15 your 30 Point!
‘As you already know, the .show_intro_message() method takes a name as an argument and prints a message to your screen.
Note that you can call the method using the class or any of ts instances. As with class methods, you should generally cal
static methods through the corresponding class instead of one ofits instances.
Getter and Setter Methods vs Properties
Programming languages like Java and C=+ don't expose attributes as part oftheir classes’ public APIs. Instead, these
programming languages make extensive use of getter and setter methods to give you access to attributes.
Note: To cive deeper into the getter and setter pattem and how Python approaches it, check out Getters and Setters:
‘Manage Attributes in Python.
Using methods to access and update attributes promotes encapsulation. Encapsulation isa fundamental OOP principle that
recommends protecting an object’s state or data from the outside world, preventing direct access. The object's state should
only be accessible through a public interface consisting of getter and setter methods.
For example, say that you have a Person lass with a .nane instance attribute. You can make .nane a non-public attribute and
provide getter and setter methods to access and change that attribute:
Python# person.py
claes Person:
ef _init_(self, ane):
Self set_rane(nane)
Gof get_name(sel4):
return self._nane
ef set_nane(seif, value)
selF._nane = value
In this example, .get_nane() is the getter method and allows you to access the underlying ._
-set_nane() is the setter method and allows you to change the current value of ._sare. The
where the actual data is stored.
1am attribute, Similarly,
ane attribute is non-public and is
Here's how you can use your Person class:
Python 3
>>> from parson Sport Person
29> Jane = Person("Jane")
>>> Jane. get_nane()
99> Jane. set_nane(ane 900")
get_nane()
Here, you create an instance of Person using the class constructor and “Jane” as the required name. That means you can use
the .get_nane() method to access Jane's name and the .set_nare() method to update it.
The getter and setter pattern is common in languages like Java and C++. Besides promoting encapsulation and APIs centered
‘on method calls, this patter also allows you to quickly add function-like behavior to your attributes without introducing
breaking changes in your APIs.
However, this pattern is less popular in the Python community. In Python, is completely normal to expose attributes as part of
‘an object's public API. Ifyou ever need to add function-like behavior on top of a public attribute, then you can turn it into a
property instead of breaking the API by replacing the attribute with a method.
Here's how mast Python developers would write the Person class:
Python
1 person.py
class Person:
def _init_(solf, ame):
‘This class doesn’t have getter and setter methods for the .nane attribute. Instead, it exposes the attribute as part of ts API. So,
you can use it directly:
Python a
>>> from person smport Person
9> Jane = Persan("ane")
>>> Jane.name
o> Jane.mane = “Jane Doe”
29> Jane naeIn this example, instead of using a setter method to change the value of .nane, you use the attribute directly in an assignment
statement. This is common practice in Python code. If your Person class evolves to a point where you need to add function-like
behavior on top of .nare, then you can turn the attribute into a property.
For example, say that you need to store the attribute in uppercase letters. Then you can do something like the following:
Python
# person.py
class Person:
Gof _init_(self, ane):
Tef.nane = nae
property
et nane(seif):
return self._nane
Grane. setter
ef name(sel, value):
self_pane ~ value. upper)
This class defines .nane as a property with appropriate getter and setter methods. Python will automatically call these
‘methods, respectively, when you access or update the attribute's value, The setter method takes care of uppercasing the input
value before assigning it back to ._nane:
Python a
o> from parson smport Person
29> Jane = Persan("Jane")
>>> Jane.nane
>>> Jane.name = “ane doe™
>>> Jane name
Python properties allow you to add function-like behavior to your attributes while you continue to use them as normal
attributes instead of as methods. Note how you can still assign new values to .nane using an assignment instead of a method
call, Running the assignment triggers the setter method, which uppercases the Input value,
Summarizing Class Syntax and Usage: A Complete Example
Up to this point, you've leamed a lot about Python classes: how to create them, when and how to use them in your code, and
‘more. In this section, you'll review that knowledge by writing a class that integrates most of the syntax and features you've
leamed so far.
Your class will represent an employee ofa given company and will implement attributes and methods to manage some related
tasks like keeping track of personal information and computing the employee's age. To kick things off, go ahead and fre up
your favorite cade editor or IDE and create a file called exployee.py. Then add the following code to it
Python
# enpioyee. py
class Employee:
conpany = "Example, Ine.
ef _init_(self, nane, birth date):
Tef.nane = nane
self. birth date = birth date
In this teployee class, you define a class attribute called .conpany. This attribute will hold the company’s name, which is,
common to all employees on the payrollThen you define the initializer, ._inst_(, which takes the employee's name and birth date as arguments. Remember that you
‘must pass appropriate values for both arguments when you call the class constructor, employee()
Inside ._antt_(), you define two public instance attributes to store the employee's name and birth date. These attributes will
be part ofthe class API because they're public attributes.
Now say that you want to turn .birth_date into a property to automatically convert the input date in|SO format to adatetine
object:
Python
1 enpioyee.py
Fron datetime ‘aport datetine
class Employee:
prop
ef barch_gate( sel")
return self. birth gate
birth date.setter
Gof birth date(seir, value):
self. pérth_gate = datetine.fromisoformat(valve)
Here, you define the .birth_date property through the @oroperty decorator, The getter method returns the content of
_birth date, This non-public attribute will hold the concrete data,
To define the setter method, you use the ebirth_éate.setter decorator. In this method, you assign a datetine.datetine object
to ._birth_date. In this example, you don’t run any validation on the input data, which should be a string holding the date in
180 format. You can implement the validation as an exercise,
Next, say you want to write a regular instance method to compute the employee's age from their birth date:
fron datetine inport datetine
class Employee:
et conpute_age(sei#):
‘today = datetine.toéay()
age = today.year - solf.birth date.year
birthday ~ datetine(
today. year,
self.pirthgate.nonth,
self. birthdate. day
>
Af today < birtnéay:
age = 2
return age
Here, .conpute_age() is an instance method because it takes the current instance, self, as its first argument, Inside the method,
you compute the employee's age using the -birth_date property asa starting point.
Now say that you'll often build instances of raployee from dictionaries containing the data of your employees. You can add a
convenient class method to quickly build objects that way:
Python# enptoyee.py
fron datetime tagort datetine
class Employee:
Bctassnethod
ef From dict(cis, data_dict))
return cis(**date_dict)
In this code snippet, you define a class method using the fclassnethod decorator. The method takes a dictionary object
containing the data of a given employee. Then it builds an instance of Exployee using the cls argument and unpacking the
dictionary.
Finally, you'll add suitable
respectively
str_() and .__repr_() special methods to make your class friendly to users and developers,
Python
1 enpioyee.py
fron datetine Inport datetine
loss Employee:
ef _str_(seif):
return f°{self.nane) is (self.compute_age()} years old”
nse)
return ¢
+#°{Rype(se14)._mame_}("
‘anes {self -nane}", ”
-#bsoth_date= (self. bimth_ date. steftine(2Y-%0-Rd'))")"
The .__str_() method returns a string describing the current employee in a user-fiendly manner. Similarly, the .__repr_()
‘method returns a string that will allow you to re-create the current abject, which is great from a developer's perspective.
Here's how you can use Eroteyee in your code:
Python a
29> from employee ingort Employee
25> John = Emplayee( "Jorn Ove", *1998-12-04")
95 John. company
>9> Joha.nane
25> John. compute_age()
99> print John)
John Doe is 24 years old
o> John
Enployea(nane="Jomn Doe’, birth date='2998-12-24")
>> Jane_data = (nane™: “Jane Doe", “birth date": “2061-05-35")
9> Jane” = Employee. from dict (Jane. data)
>>> print( ane)
Jane Doe is 21 years old
Cool! Your Employee class works great so far! It allows you to represent employees, access their atributes, and compute their
ages. It also provides neat string representations that will make your class look polished and reliable. Great job! Do you have
any ideas of cool features that you could add to teployee?Debugging Python Classes
Debugging often represents a large portion of your coding time. You'll probably spend long hours tracking errors in the code
that you're working on and trying to fix them to make the code more robust and reliable. When you start working with classes
and objects in Python, you're likely to encounter some new exceptions,
For example, ifyou try to access an attribute or method that doesn't exist, then you'll get an attributetror:
Python
o> class Point:
o> point = point(s, 8)
Traceback (most recent all last):
cteibutetrror: ‘Point” object has no attribute "2°
iby
The Point class doesn’t define a .z instance attribute, so you get an a ror if you try to access that attribute,
You'll find a few exceptions that can occur when working with Python classes. These are some of the most common ones:
+ An attrsbutetrror occurs when the specified object doesn't define the attribute or method that you're trying to access.
Take, for example, accessing -2 on the Point class defined in the above example,
* A ryoeécroc occurs when you apply an operation or function to an object that doesn’t support that operation, For
‘example, consider calling the builtin 1en() function with a number as an argument.
1oc occurs When an abstract method isn’t implemented in a concrete subclass. You'll learn more about
on in the section Creating Abstract Base Classes (ABC) and Interfaces.
These are just afew examples of exceptions that can occur when you're working with Python classes. You'll also find some
common mistakes that people sometimes make when they start to write thelr own classes:
* Forgetting to include the sei argument in instance methods
* Forgetting to instantiate the class by calling its constructor with appropriate arguments
* Confusing and misusing class and instance attributes
* Not following or respecting naming conventions for members
+ Accessing non-public members from outsid
1 containing class
+ Overusing and misusing inheritance
These are just a few common mistakes that people might make when they’re getting started with Python classes. From this list,
you haven't learned about inheritance yet. Don't worry about it for now. Inheritance is an advanced topic that you'll study later
inthis tutorial
Exploring Specialized Classes From the Standard Library
In the Python standard library, you'l ind many tools that solve different problems and deal with different challenges. Among
all these tools, you'll find afew that will make you more productive when writing custom classes.
For example, ifyou want a tool that saves you from writing a lot of class-related boilerplate code, then you can take advantage
of data classes and the dataclasses module.
Similarly, if you're looking for a tool that allows you to quickly create class-based enumerations of constants, then you can turn
your eye to the enun module and its different types of enumeration classes.
In the following sections, you'll learn the basics of using data classes and enumerations to efficiently write robust, reliable, and
specialized classes in Python.Data Classes
Python's data classes specialize in storing data. However, they're also code generators that produce a latof class-related
boilerplate code for you behind the scenes.
For example, ifyou use the data class infrastructure to write a custom class, then you won't have to implement special
methods like ._init_(), ._repr_(), «_e4_(),and .__hash_(). The data class will write them for you. More importantly, the
data class will write these methods applying best practices and avoiding potential errors
Note: To learn more about data classes in Python, check out Data Classes in Python 3.7+ (Guide)
‘As you already know, special methods support important functionalities in Python classes. n the case of data classes, you'll
have accurate string representation, comparison capabilities, hashability, and more.
Even though the name data class may suggest that this type of class is limited to containing data, it also offers methods. So,
data classes are like regular classes but with superpowers.
To create a data class, go ahead and import the géataclass decorator from the dataclasses module. You'll use this decorator in
the definition of your class. This time, you won't write an .__init_() method. You'l just define data fields as class attributes
with type hints.
For example, here's how you can write the threeoPoint class as a data class:
Python
# point.py
‘fron dataclasses Inport dataclass
esataclass
class ThreeDPoint
x: int | fo
ys int | float
2 int | Flo:
@classnethod
ef From_sequence(cls, sequence):
return cis(*sequence)
@staticnethod
ef show_intro_nessage(name):
prine(#*Hey {nane)! This 4s your 30 Potnt!")
‘This new implementation of ThreeoPoint uses Python's @datactass decorator to turn the regular class into @ data class. Instead
of defining an ._snit_() method, you list the instance attributes with their corresponding types. The date class will ake care
of writing a proper initializer for you. Note that you don't define .__tter_() or ._repr_() either.
Note: Data classes are pretty flexible when it comes to defining thei fields or attributes. You can declare them with the
type annotation syntax. You can initialize them with a sensible default value. You can also combine both approaches
depending on your needs:
Python
‘fron dataclasses inport dataclass
@eatactass
class ThreebPoint
x: dnt | float
y= ee
2 int | float = @.e
In this code snippet, you declare the fist attribute using the type annotation syntax. The second attribute has 2 default
value with no type annotation. Finally, the third attribute has both type annotation and a default value, However, when
you don't specify a type hint for an attribute, then Python won't automatically generate the corresponding code for thatattribute,
Once you've defined the data fields or attributes, you can start adding the methods that you need. In this example, you keep
the -from_sequence() class method and the .show_intre_nessage() static method.
Go ahead and run the following code to check the additional functionality that édataelass has added to this version of
Python
o> from dataclasses inport astuple
>>> from point Snport ThreeoPoint
o> point = ThreebPoine(1.2, 2.9, 2.8)
>> point
TareeoPoint(x-1.0, y-2.8, 293.0)
>>> astuple(point_2)
(ae, 2.8, 3.8)
>9> point 2 = TheeebPosnt(2, 3, 4)
>>> point A == point_2
>>> point_3 = TheeebPosne(1, 2, 3)
5> point 1 == point_3
Your TareebPoint class works pretty well! It provides a suitable string representation with an automatically generated
«__repr_() method. You can iterate over the fields using the astupie() function from the dataclasses module. Finally, you can
compare two instances of the class for equality (-). As you can conclude, this new version of ThreebPoint has saved you from
writing several lines of tricky boilerplate code.
Enumerations
‘An enumeration, or just enum, is a data type that you'll find in several programming languages. Enums allow you to create sets
fof named constants, which are known as members and can be accessed through the enumeration itself.
Python doesn't have a built-in enum data type. Fortunately, Python 34 introduced the enun module to provide the Enun class for
supporting general-purpose enumerations.
Days of the week, months and seasons of the year, HTTP status codes, colors ina traffic light, and pricing plans of a web service
are all great examples of constants that you can group in an enum. In short, you can use enums to represent variables that can
take one of a limited set of possible values.
The Enun class, among other similar classes in the enux module, allows you to quickly and efficiently create custom
cenumerations or groups of similar constants with neat features that you don't have to code yourself Apart from member
constants, enums can also have methods to operate with those constants.
Note: To lear more about how to create and use enumerations in your Python code, check out Build Enumerations of
‘Constants With Python's Enum,
To define a custom enumeration, you can subclass the enum class. Here's an example of an enumeration that groups the days of
the week:
Pythono> from enun import Enum
o> clase Weekoay(Enua)
Tuesoay ~ 2
WEONESDAY = 3
FRIOAY = 5
SATURDAY =
suNos
In tis code example, you define weekoay by subclassing Enus from the enun module. This specific enum groups seven constants
representing the days of the week. These constants are the enum members. Because they’re constants, you should follow the
convention for naming any constant in Python: uppercase letters and, if applicable, underscores between words.
Enumerations have a few cool features that you can take advantage of For example, their members are strict constants, so you
can't change their values, They're also iterable by default:
Python
>>> MoekDay MONDAY = @
‘raceback (most recent call last):
Acteibuteteror: cannot seassign sesber “MORORY"
o> Lise (weekDay)
i
‘tleekoay.MONDAY: 2>,
‘tleekDay. TUESDAY: 23,
‘leekDay WEDNESDAY: 3>,
‘eekoay. THURSDAY: @>,
‘leekoay. FRIDAY: 59,
‘eekDay.SATUROAY: 6>,
‘teekDay. SUNDAY: 7>
you try to change the value of an enum member, then you get an Attributetrror. So, enum members are strictly constants,
You can iterate over the members directly because enumerations support iteration by default.
You can directly access their members using different syntax:
Python a
>>> MeekDay MONDAY
‘MeokDay MONDAY: 15
bee # call sotation
>>> weakoay(2)
‘MleekDay TUESDAY: 2>
93 1 Dictionary notation
>>> Meekbay{ WEDNESDAY”)
Inthe first example, you access an enum member using dot notation, which is pretty intuitive and readable. In the second
example, you access a member by calling the enumeration with that member's value as an argument. Finally, you use a
dictionary like syntax to access anott
er member by name,
you want fine-grain access to a member's components, then you can use the .nae and value attributes, which are pretty
handy in the context of iteration
Python a>5> MeekDay THURSDAY. rane
“Tauasoay
29> MeekDay THURSDAY. value
o> for day in Weekbay:
prine(day-nane,
|, day.value)
woway => a
FRIDAY => 5
In these examples, you access the .nane and . value attributes of specific members of weekday. These attributes provide access
to each member's component,
Finally, you can also add custom behavior to your enumerations. To do that, you can use methods as you'd do with regular
classes:
Python
1 week.py
‘ron enun Inport Frum
class weekoay(Enun):
Wowoay = 3
Tuesoay = 2
‘Tuasoay = 4
FRIORY =
SsaTUROAY
6
@classnethod
ef favorive_eay(cls):
return cle. FREDAY
def _str_(seit)
Feturn #current day: (self.nane)”
‘After saving your code to week.py, you add a class method called .avorite_day() to your Weekday enumeration, This method
will just return your favorite day of the week, which is Friday of coursel Then you add a ._str__) method to provide a user-
friendly string representation for the current day.
Here's how you can use these methods in your code:
Python a
>9> from week Inport Weekbay
>> Weekday favorite day()
o> prant(WeekDay. FRIDAY)
You've added new functionality to your enumeration through class and instance methods. Isn't that cool?Using Inheritance and Building Class Hierarchies
Inheritance isa powerful feature of abject-oriented programming, It consists of creating hierarchical relationships between
classes, where child classes inherit attributes and methods from their parent class. In Python, one class can have multiple
parents or, more broadly, ancestors.
This is called implementation inheritance, which allows you to reduce duplication and repetition by code reuse. Itcan also
make your code more modular, better organized, and more scalable. However, classes also inherit the interface by becoming.
‘more specialized kinds of their ancestors. In some cases, you'll be able to use a child instance where an ancestor is expected.
In the following sections, you'l learn how to use inheritance in Python. You start with simple inheritance and continue with
‘more complex concepts. So, get ready! This is going to be fun!
Simple Inheritance
When you havea class that inherits from a single parent class, then you're using single-base inheritance or just simple
inheritance. To make a Python class inherit from another, you need to lst the parent class's name in parentheses after the
child class's name in the definition
To make this clearer, here's the syntax that you must use:
Python
class Parent
4 Parents definition goes here.
class chita(Parent)
4 Child definitions goes here.
In this code snippet, Parent isthe class you want to inherit from. Parent classes typically provide generic and common
functionality that you can reuse throughout multiple child classes. chit isthe class that inherits features and code from
Parent. The highlighted line shows the required syntax.
Note: In this tutorial, you'll use the terms parent class, superclass, and base class interchangeably to refer to the class
that you inherit from,
Similarly, you'l use the terms child class, derived class, and subelass to refer to classes that inherit from other classes.
Here's a practical example to get started with simple inheritance and how it works. Suppose you're building an app to track
vehicles and routes. At fist, the app will track cars and motorcycles. You think of creating a venscle class and deriving two
subclasses from it. One subclass will represent a car, and the other will represent a motorcycle.
The venicle class will provide common attrioutes, such as .nake, adel, and .year. It'll also provide the .start() and .stop()
‘methods to start and stop the vehicle engine, respectively
Python# venicles. py
class Venicle
ef init__(self, make, model, year):
Self make ~ make
self-rodel = odet
self.year = year
self._started = False
ef start(sel4):
prine(*starting engine..."
self,_started = true
ef stop(seiF)
prine("stopping engine...")
self._started = False
In this code, you define the vehscte class with attributes and methods that are common to all your current vehicles. You can say
that vehicle provides a common interface for your vehicles. You'll inherit from this class to reuse this interface and its
functionality in your subclasses.
Now you can define the car and motorcycle classes, Both of them will have some unique attributes and methods specific to the
vehicle type, For example, the car will have a .nun_seats attribute and a .drive() method:
Python
# venscles.py
class Can(vehiete):
ef init__(self, make, wodel, year, nun_seats)
Tiper()o_init_(nake, mogel, year)
self.num_seats ~ munseats
Get drtve(sel4):
print("briving my "(self.make) - {se1f.nodel)" on the road’)
def _str_(seit):
Feturn f° "(self make) ~ (Self.eodel}” has (SeLf.num seats} seats"
Your car lass uses Venicle as its parent class. This means that car will automatically inherit the .2ake, .nodel, and .year
attributes, as well as the non-public ._starte attribute. It also inherit the .start() and .stop() methods.
Note: Like inheritance in nature, inheritance in OOP goes ina single direction, from the parents to the children. In other
words, children inherit from their parents and not the other way around.
The class defines a .nun_seats attribute. As you already know, you should define and initialize instance attributes in
+_init_(). This requires you to provide a custom ._init_() method in car, which will shadow the superclass initializer.
How can you write an .__init_() method in car and still guarantee that you initialize the .nake, model, and .year attributes?
That's where the built-in super() function comes on the scene. This function allows you to access members in the superclass,
asits name suggests.
Note: To learn more about using super() in your classes, check out Supercharge Your Classes With Python super).
In car, you use super) to call the .__init__() method on Vehicie. Note that you pass the input values for .nake, .nodel, and
year so that vehicle can initialize these attributes correctly. After this call to super(), you add and initialize the .nan_seats
attributes, which is specific to the car class.
Finally, you write the .arive() method, which i also specific to car, This method is just a demonstrative example, so it only
prints a message to your screen,Now its time to define the Hotoreycte class, which will inherit from vehicle too, This class will have a .nun_sheels attribute and
a .ride() method:
Python
# venscles.py
*
loss Motoreyele(vehicle)
ef _init_(self, make, model, year, nus_sheels):
Super()-_init_(nake, model, year)
self.nua_wheets = nu_vhesls
ef wide(seie):
print(F*Ricing my "{self.make) - (selF.nodel)" on the road’)
ef _str_(seit)
Feturn "(5
mnake) ~ {(Self.rodel}” has (seLf.num wheels) wheels?
‘Again, you call supar() to initialize .rake, .sege1, and .year. After that, you define and initialize the .nun_wheels attribute. Finally,
you write the .ride() method. Again, this method is just 2 demonstrative example.
With this code in place, you can start using Car and Wotoreycle right away:
Python a
>9> from vehicles inport Car, Motorcycle
o> tesla = Car(*Tesla", “Model 5", 2822, 5)
bop tesla. stare()
starting engine...
o> tesla. drivel)
o> tesla. ston)
Stopping engine.
>>> print testa)
‘Tesla - Model S* has 5 seats
>> harley = Wotorcyele("Hariey-Uavidson", "Iron 883", 2021, 2)
>> nanley.start()
Starting engine.
>9> harley.ride()
Aiding my "Harley-Davidson ~ Iron 83" on the road.
>>> harley. stop()
Stopping engine.
>>> print{harley)
Harley-Davidson - Iron 883" has 2 wheels
Cool! Your Tesla and your Harley-Davidson work nicely. You can start their engines, drive or ride them, and so on. Note how you
can use both the inherited and specific attributes and methods in both classes.
You'll typically use single inheritance or inheritance in general when you have classes that share common attributes and
behaviors and want to reuse them in derived classes. So, inheritance isa great tool for code reuse. Subclasses will inherit and
reuse functionality from their pare
‘Subclasses will frequently extend their parents’ interface with new attributes and methods. You can use them as a new starting
point to create another level of inheritance. This practice will lead to the creation of class hierarchies.
Class Hierarchies
Using inheritance, you can design and build class hierarchies, also known as inheritance trees. A class hierarchy isa set of
closely related classes that are connected through inheritance and arranged in atree-lke structure.
The class or classes at the top of the hierarchy are the base classes, while the classes below are derived classes or subclasses,
Inheritance-based hierarchies express an is-a-type-of relationship between subclasses and their base classes.Each level in the hierarchy will inherit attributes and behaviors from the above levels. Therefore, classes at the top of the
hierarchy are generic classes with common functionality, while classes down the hierarchy are more specialized. They'll inherit
attributes and behaviors from their superclasses and will also add their own.
Taxonomic classification of animals is a commonly used example to explain class hierarchies. In this hierarchy, you'll have a
generic Aninai class at the top. Below this class, you can have subclasses like Maneal, Bird, Fish, and so on. These subclasses
‘are more specific classes than ninal and inherit the attributes and methods from it. They can also have their own attributes
and methods.
To continue with the hierarchy, you can subclass Mannal, Bird, and Fish and create derived classes with even more specific
characteristics, Here's a short toy example:
Python
# aninals.py
class Animal:
ef _init_(self, mane, sex, habitat)
Self.nane = mare
self abseat
abitat
class Maal (Arina}):
unique feature =
Mamary glands”
lass Bird(aninal)
‘unique_feature = "Feathers"
class Fish(Aninal)
vnique_feature = "Gills"
lass Dog(tanra1)
ef walk(seif):
print("The dog is wateing”)
class cat(haneal)
et walk(seif):
prine("the cat is wateing”)
lass Eagle(Bird)
ef fy(self)
print("the eagle is flying”)
class Penguin(Bird):
ef seim(sei):
print ("The penguin is swinning”)
class Salmon(Fish):
Ger sxim(se2):
print("the satnon is swinning")
class Shark(ish)
ef sxim(sei):
print("The shark £5 swisming")
[At the top of the hierarchy, you have the aninai class. This isthe base class of your hierarchy, Ithas the .nane, sex, and -nabitat
attributes, which will be string objects. These attributes are common to all animals,
Then you define the Hama, 8ird, and Fish classes by inheriting from aninat. These classes have a .unique_feature class
attribute that holds the distinguishing characteristic of each group of animals.
Then you create concrete mammals like bog and cat. These classes have specific methods that are common to all dags and
cats, respectively. Similarly, you define two classes that inherit from Bird and two more that inherit from Fish
Here's a tree-like class diagram that will help you see the hierarchical relationship between classes:Animal
++ name: st
+ 0x: str
+ habitat: st
Mammal Bird Fish
+ unique feature: str + unique feature: str + unique feature: str
Dog cat Eagle Penguin Salmon Shark
+ walk): None + waik(): None +f: None + swim): None + swim): None + swim): None
ach level in the hierarchy can—and typically willadd new attributes and functionality on top of those that its parents already
provide. if you walk through the diagram from top to button, then you'll move from generic to specialized classes.
‘These latter classes implement new methods that are specific to the class at hand. In this example, the methods just print
some information to the screen and automatically return None, which is the null value in Pythan.
Note: You can create class diagrams to represent class hierarchies that are based on inheritance, However, that's not the
only relationship that can appear between your classes.
With class diagrams, you can also represent other types of relationships, including:
+ composi
‘existing, then the arm stops existing too.
Xn, which expresses a strong has-a relationship. For example, a robot has an arm. Ifthe robot stops
+ Aggregation, which expresses a softer has-a relationship. For example, @ university has an instructor. fthe
University stops existing, the instructor doesn't stop existing,
+ Association, which expresses a uses-a relationship. For example, a student may be associated with a course. They
will use the course, This relationship is common in database systems where you have one-to-one, one-to-many, and
many-to-many associations,
You'll learn more about some of these types of relationships in the section called Using Alternatives to Inheritance,
‘That's how you design and create class hierarchies to reuse code and functionality. Such hierarchies also allow you to give your
code a modular organization, making it more maintainable and scalable.
Extended vs Overridden Methods
When you're using inheritance, you can face an interesting and challenging issue. In some situations, a parent class may
provide a given functionality only ata basic evel, and you may want to extend that functionality in your subclasses. In other
situations, the feature in the parent class isn't appropriate forthe subclass.
In these situations, you can use one of the following strategies, depending on your specific case:
+ Extending an inherited method in a subclass, which means that you'll reuse the functionality provided by the superclass
and add new functionality on top
‘+ Overriding an inherited method in a subclass, which means that you'll completely discard the functionality rom the
‘superclass and provide new functionality in the subclassHere's an example of a small class hierarchy that applies the fist strategy to provide extended functionality based on the
inherited one:
Python
# sirerafts.py
class Aircraft
ef _init_(self, thrust, 1ift, max_speed):
Set thrust = thrust
selfs Lift = Leet
self .max_speed - max speed
def show technsea_spees(set*))
print(#*Thrust: (self.thrust) i")
print(Fuift: (sel?..4#t) ke")
print FRx speed: {selfomax_speed) kn/h")
class Helicopter (Aircraft)
GeF _init_(self, thrust, Lift, max_speed, nua_rotors):
Super()._init_(thrust, 14, max_speed)
ef show_technical_specs (sei:
super) -show_Sechnteal_specs()
print(#Nonber of rotors: {selF.num_rotors)”)
In this example, you define aircraét as the base class. In .__init__(), you create a few instance attributes. Then you define the
show_technical_specs() method, which prints information about the aircraft’s technical specifications.
Next, you define Helicopter, inheriting from aircraft. The .__init__() method of Helicopter extends the corresponding method
of airerart by calling super() to initialize the thrust, .21ft, and .nax_speed attributes. You already saw something like this in
the previous section.
Helicopter also extends the functionality of .show_technical_specs(). In this case, you first cal .show_technical_specs() from
‘Aircraft using super(). Then you add a new call to print() that adds new information to the technical description of the
helicopter at hand.
Here's how Helicopter instances work in practice:
Python
29> from aircrafts Inport Helicopter
29> stkorsiy UN6@ = Helicopter(1499, 9979, 278, 2)
o> stkorsiy_UM6@. show technical_specs()
Trust: 2650 ka
ft: 9979 ke
Nox speed: 278 kash
Nonber of rotors: 2
When you call .show_technical_specs() on a Helicopter instance, you get the information provided by the base class, Aircraft,
and also the specific information added by Hel copter itself, You've extended the functionality of aircraft in its subclass
Helicopter
Now it’s time to take a look at how you can override a method in a subclass. As an example, say that you have a base class
called Worker that defines several attributes and methods lie in the allowing example:
Pythonworkers.py
lees Worker:
ef _init_(self, nane, address, hourly salary)
Self.nane = nae
selfcaddress = address
self hourly. salary
jourly_salary
eF show profiie(sel/)
print(*== Worker profile
prine(#nane: {sel#.name}")
prine(FAddress: (self.adéress)")
print f*Hourly salary: {self hourly. salary")
>
ef calculate payroll (self, hours=>> fron eraets Inport Fly ingcar
>>> space flyer = FiyingCar("space’, "Flyer", “Black
>>> space flyer. show technical spees()
wake: space
Nogel: Flyer
Color: Black
o> space flyer. stare()
starting the engine.
o> space_flyer-drsve()
Driving on the road
o> space_flyer. y()
Flying in the sky.
o> space_flyer.stop()
In this code snippet, you first create an instance of rlyingcar. Then you call alt its methods, including the inherited ones. As you
can see, multiple inheritance promotes code reuse, allowing you to use functionality from several base classes at the same
time. By the way, ifyou get this rayingcar to really fly, then make sure you don’t stop the engine while you're fying!Method Resolution Order (MRO)
When you're using multiple inheritance, you can face situations where one class inherits from two or more classes that have
the same base class. This is known as the diamond problem. The real issue appears when multiple parents provide specific
versions of the same method. In this case, it'd be dificult to determine which version of that method the subclass will end up
using.
Python deals with ths issue using a specific method resolution order (MRO). So, what is the method resolution order in
Python? It's an algorithm that tells Python how to search for inherited methods in a multiple inheritance context. Python's MRO
determines which implementation of a method or atribute to use when there are multiple versions oft in a class hierarchy.
Python’s MRO is based on the order of parent classes in the subclass definition. For example, car comes before aircraft in the
Flyingcar class from the previous section, MRO also considers the inheritance relationships between classes. In general,
Python searches for methods and attributes in the following order:
1. The current class
2. The leftmost superclasses
3. The superclass listed next, from left to right, up tothe last superclass
4. The superclasses of inherited classes
5. The object class
Its important to note that subclasses come fist in the search. Additionally, ifyou have multiple parents that implement a given
‘method or attributes, then Python will search them in the same order that they’ listed in the class definition
To illustrate the MRO, consider the following sample class hierarchy:
Python
# rosy
class A
ef nethod(sel")
print¢"A.method”)
class 8(a)
ef nethod(sel")
print¢"#.method”)
class c(a).
ef method(sei"):
print(*C.method”)
class 0(8, ©):
In this example, o inherits from # and c, which inherit from a. All the superclasses in the hierarchy define a different version of
method). Which of these versions will > end up calling? To answer this question, go ahead and call .netnod() on ab instance:
Python
o> from nro inport ©
>9> 0() method)
f.nethos
When you call .nethod() on an instance ofp, you get s.nethod on your screen. This means that Python found .zethos() on the 8
class first. That's the version of .nethoa() that you end up calling. You ignore the versions from c and A
Note: Sometimes, you may run into complex inheritance relationships where Python won't be able to create a consistent
‘method resolution order. In those cases, you'll get a TypeError pointing out the issue.
You can check the current MRO of a given class by using the ._nro__ special attribute:
Python a>> Baro
class "_s
class ‘
>> from mxins inport Employee
9> john = Employee("Jonn boo", 38, Seaee)
>>> John. to_jsont)
{nane": "John", “age™: 30, "salary": 59000)
>9> John.to_ pickle)
b's \x@aname x94 x8 \x@820hn Doe\xS4\xBe \x032g0\x94X Lex xO6saT ary
Now your employee class is able to serialize its data using JSON and pickle formats. That’ great! Can you think of any other
Useful mixin classes?
Up to this point, you've learmed a lot about simple and multiple inheritance ia Python. In the following section, you'll go
through some of the advantages of using inheritance when writing and organizing your code.
Benefits of Using Inheritance
Inheritance is a powerful tool that you can use to model and solve many real-world problems in your code. Some benefits of
Using inheritance include the following:
+ Reusability: You can quickly inherit and reuse working code from one or more parent classes in as many subclasses as
you need
+ Modularity: You can use inheritance to organize your code in hierarchies of related classes.
+ Maintainability: You can quickly fix issues or add features to a parent class. These changes will be automatically available
in all its subclasses. Inheritance also reduces code duplication,
+ Polymorphism: You can create subclasses that can replace their parent class, providing the same or equivalent
functionality.
+ Extensibility: You can quickly extend an exiting class by adding new data and behavior to its subclasses.
You can also use inheritance to define a uniform API forall the classes that belong to a given hierarchy. This promotes
consistency and leverages polymorphism
Using classes and inheritance, you can make your code more modular, reusable, and extensible. Inheritance enables you to
apply good design principles, such as separation of concerns, This principle states that you should organize code in small
classes that each take care of a single task.
Even though inheritance comes with several benefits, it can also end up causing issues. Ifyou overuse it or use it incorrectly,
then you can:
* Aiicially increase your code's complexity with multiple inheritance or multiple levels of inheritance
+ Face issues like the diamond problem where you'll have to deal with the method resolution order
+ End up with fragile base classes where changes
2 parent class produce unexpected behaviors in subclasses
Of course, these aren't the only potential pitfalls. For example, having multiple levels of inheritance can make your code harder
to reason about, which may impact your code's maintainability in the long term.
Another drawback of inheritance is that inheritance is defined at compile time, So, there's no way to change the inherited
functionality at runtime. Other techniques, ike composition, allow you to dynamically change the functionality of a given class
by replacing its components.
Using Alterna
Inheritance, and especially multiple inheritance, can be a complex and hard-to-grasp topic. Fortunately, inheritance isn't the
only technique that allows you to reuse functionality in object-oriented programming. You also have composition, which
represents a has-a relationship between classes.
es to InheritanceComposition allows you to build an abject from its components. The composite object doesn’t have direct access to each
component's interface. However, it can leverage cach component's implementation
‘Delegation is another technique that you can use to promote code reuse in your OOP programs. With delegation, you can
represent can-do relationships, where an object relies on another object to perform a given task.
In the following sections, you'll learn more about these techniques and how they can make your abject-oriented code more
robust and flexible.
Composition
‘As you already know, you can use eompasition to model 2 has-a relationshi between objects. In other words, through
composition, you can create complex objects by combining objects that will work as components. Note that these components
may not make sense as stand-alone classes.
Favoring composition over inheritance leads to more flexible class designs. Unlike inheritance, composition is defined at
runtime, which means that you can dynamically replace a current component with another component of the same type. This
characteristic makes it possible to change the composite's behavior at runtime,
In the example below, you use composition to create an Industrial Robot class from the Body and Are components:
Python# robot. py
class IndustesalRobot:
ef _init_(se1):
Self body = Body()
self.ara = Ara)
rotate_body left(sclf, degrees-10)
self body. rotate_LeFt (degrees)
ef rotate body right(selt, degrees
self body. rotate_right (degrees)
2)
ef nove _arm_up(seif, distance-10)
self ara.move_up(distarce)
nove_ara_down(self, distance-t0)
selfcara.move_down(aistance)
wete(sei?)
self.ara.wele()
class ody
Gof _init_(se1):
Self rotation = 0
Gof rotate left(selé, degrees-te):
self rotation -~ degrees
print(#Rotating body {degrees} degrees to the Teft...")
ef rotate night(selt, degrees=10):
self.rotation += degrees
print(fRotating body (degrees) degrees to the right...")
class Arm:
ef _init_(se19):
Sel. position = 6
ef nove_up(selt, distance-t);
self-position += 2
print(# Roving ars {distance} em up...")
ef nove _doun(self, éistance-t):
self.position -- 1
prine(#Roving ans {distance} cm down...°)
Ger wele(seif):
print(*weleing...")
In this example, you build an IndustriatRobot class out ofits components, Body and Arn, The oay class provides horizontal
‘movernents, while the Ars class represents the robot's arm and provides vertical movement and welding functionality
Here's how you can use tnéustrialRobot in your code:
Pythono> from robot snpart IndustrdelRobot
o> robot = industrdairobot()
>>> robot. rotate_body_teFt()
Rotating body 10 degrees to the left
>>> robot.mave_arm_up(15)
oving are 15 ex u
>>> robot.weld()
werding.
>>> robot. rotate pody.right(20)
Rotating body 28 degrees to the righ
>>> robot. move sem_down(5)
oving are 5 cm down
>>> robot.weld()
werding
Great! Your robot works as expected, It allows you to move its body and arm according to your movement needs. It also allows
you to weld different mechanical pieces together.
‘An idea to make this rabot even cooler isto implement several types of arms with different welding technologies. Then you can
change the arm by running robot are = newarn(). You can even add a .change_ara() method to your robot class. How does that
sound as a learning exercise?
Unlike inheritance, composition doesn’t expose the entice interface of components, soit preserves encapsulation. Instead, the
composite objects access and use only the required functionality from their components. This characteristic makes your class
design more robust and reliable because it won't expose unneeded members.
Following the robot example, say you have several different robots in a factory. Each robot can have different capabilites like
welding, cutting, shaping, polishing, and so on. You also have several independent arms. Some of them can perform all those
actions. Some of them can perform just a subset ofthe actions.
Now say that a given robot can only weld. However, this robot can use different arms with different welding technologies. f you
se inheritance, then the robot will have access to other operations like cutting and shaping, which can cause an accident or
breakdown,
Iyou use composition, then the welder robot will only have access to the arm's welding feature. That said, composition can
help you protect your classes from unintended use.
Delegation
Delegationis another technique that you can use as an alternative to inheritance With delegation, you can model can- 0
relationships, where an object hands a task over to another object, which takes care of executing the task. Note that the
delegated object can existindependentl from the delegator.
‘You can use delegation to achieve code reuse, separation of concerns, and modularity, For example, say that you want to create
a stack data structure, You think of taking advantage of Python's list as a quick way to store and manipulate the underlying
data
Here's how you end up writing your stack class:
Python# stack. py
class stack:
ef init__(self, LtemscRone):
FF Stems is None
self._stens = ()
else:
self,_Stens = List (tens)
ef push(self, stem)
Self._{tens. append(ttem)
eF pop( self)
return self._Stens.pop()
ef _repr_(self) => str
return F{type( set?)
ame) ({3e1F._Atens))"
In .__init_0, you define a ist object called ._itens that can take its initial data from the itens argument. You'll use this list
to store the data in the containing stack, so you delegate all the operations related to storing, adding, and deleting data to this
list object. Then you implement the typical stack operations, .push) and -pop(),
Note how these operations conveniently delegate their responsibilities on ._itens.append() and ._iters.pop(), respectively.
Your stack class has handed its operations over to the List object, which already knows how to perform them.
It's important to notice that this class is pretty flexible. You can replace thelist object in ._itens with any other object as long as
itimplements the .pop() and . append) methods. For example, you can use a desu object from the collections module,
Because you've used delegation to write your class, the internal implementation of 1ist isnt visible or directly accessible in
Stack, which preserves encapsulation:
Python a
>9> from stack import stack
o> stack = stack([1, 2, 31)
seack((3, 2, 31)
>> stack. push(4)
>>> stack
stack((1, 2, 3, 41)
o> stack pop0)
>>> stack. pop()
bop stack
stack([2, 21)
o> instock)
c
push
The public interface of your stack class only contains the stack-related methods .pop() and .push(), as you can see in the dir),
function's output. This prevents the users of your class from using list-specific methods that aren't compatible with the classic
stack data structure,
you use inheritance, then your child class, Stack, will inherit all the functionality from its parent class, List
Python ao> clase Stack(list):
det push(selt, item):
self append(iten)
ef pop(set*):
return super().pop()
def _repr_(self) -> str
: return #*{type(sel#)._name_}({super()-_repe_0})"
op stack = stack()
o> eir(stack)
C
‘clear’,
‘copy’,
extend",
pop",
sort
In this example, your stack class has inherited all the methods from list. These methods are exposed as part of your class's
public API, which may lead to incorrect uses of the class and its instances.
With inheritance, the internals of parent classes are visible to subclasses, which breaks encapsulation. Ifsome of the parent’s
functionality isn’t appropriate forthe child, then you run the risk of incorrect use. In this situation, composition and delegation
are safer options.
Finally, in Python, you can quickly implement delegation through the .__getattr__() special method. Python calls this method
automatically whenever you access an instance attribute or method, You can use this method to redirect the request to
another object that can provide the appropriate method or attribute.
To illustrate this technique, get back to the mixin example where you used 2 mixin class to provide serialization capabilities to
your Employee class, Here's how to rewrite the example using delegation:
Python