17. Basics of Objects and Classes#
In this chapter, we will begin our exploration of Object-Oriented Programming (OOP) in Python, a powerful programming paradigm that enables developers to model real-world entities as objects in their code. Mastering OOP in Python is essential for writing effective and efficient Python code, as Python is inherently an object-oriented language—everything in Python is an object.
17.1. Playing with Objects#
At its core, an object in programming is a collection of data, known as attributes, and functions, known as methods, that operate on this data. In OOP, objects are instances of classes, which can be thought of as blueprints or templates for creating objects. A class defines a set of attributes and methods that the objects created from it will possess. Essentially, a class represents a type, and objects are instances of that type, sharing the same properties and behaviors.
To define a class in Python, we use the following syntax:
class NameOfClass:
pass
Here, NameOfClass
is the name of the class, and the pass statement indicates that the class currently has no attributes or methods. This is a placeholder that allows us to define the structure of the class without implementing any functionality just yet.
Once a class is defined, we can create objects (instances) of that class by calling the class as if it were a function. This process is known as instantiation. After creating an object, we can inspect its type and retrieve its unique identifier (ID) using the type
and id
functions, respectively.
class Person:
pass
someone = Person()
print(type(someone)) # Outputs: <class '__main__.Person'>
print(id(someone)) # Outputs: A unique identifier, e.g., 4569893136
At this stage, the object someone
is not very useful because it doesn’t have any attributes or methods. However, we can dynamically add attributes to the object using dot notation:
someone.first_name = "John"
someone.last_name = "Doe"
These attributes can then be accessed and used within functions:
def greeting(p):
print(f"Hello, my name is {p.first_name} {p.last_name}.")
greeting(someone) # Outputs: Hello, my name is John Doe.
As demonstrated, objects are useful for grouping related data together. However, dynamically adding attributes to objects is not considered good practice. This approach can lead to inconsistencies and errors, especially when working with multiple objects of the same class.
>>> someother = Person()
>>> someother.name = "Jane"
>>> someother.last_name = "Doe"
>>> greeting(someother)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[23], line 5
2 someother.name = "Jane"
3 someother.last_name = "Doe"
----> 5 greeting(someother)
Cell In[20], line 5, in greeting(someother)
4 def greeting(p):
----> 5 print(f"Hello, my name is {p.first_name} {p.last_name}.")
AttributeError: 'Person' object has no attribute 'first_name'
17.2. The __init__
method#
To ensure that all objects of a given class share the same attributes, we should avoid adding attributes on the fly. Instead, we define a special method within the class called __init__
. The __init__
method is automatically called when a new object is instantiated, allowing us to initialize the object’s attributes in a consistent manner.
class Person:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
Now, when we create a new Person
object, we must provide values for first_name
and last_name
, ensuring consistency across all instances.
someone = Person("John", "Doe")
greeting(someone) # Outputs: Hello, my name is John Doe.
someother = Person("Jane", "Doe")
greeting(someother) # Outputs: Hello, my name is Jane Doe.
The __init__
method is a special method in Python known as the constructor. It is automatically called when a new object (instance) of a class is created. The primary purpose of the __init__
method is to initialize the attributes of the newly created object with the values provided. Let’s break down the process step by step using the example someone = Person("John", "Doe")
.
1. Memory Allocation. When the statement someone = Person("John", "Doe")
is executed, Python first allocates memory for the new Person object. This involves reserving a block of memory to store the object’s attributes and any other necessary data.
2. Initialization with __init__
. Python then calls the __init__
method of the Person class to initialize the new object. The newly created object is passed to the __init__
method as the first parameter, traditionally named self
. This allows the __init__
method to configure the attributes of this specific object. Within the __init__
method, you can set up the initial state of the object.
3. Returning the Object. After the __init__
method is completed, it does not explicitly return anything. Instead, the Person
method implicitly returns the newly created and initialized object. This object is then assigned to the variable someone
.
By using the __init__
method, we establish a clear and reliable structure for our objects, making our code more predictable and easier to maintain.
17.3. Adding a first method#
The function greeting is intrinsically tied to the attributes of the Person
class, as it operates directly on these attributes to generate its output. This close relationship suggests that greeting should logically be a method within the Person
class rather than an external function. By refactoring the function into a method, we ensure that it becomes an integral part of the Person
class, thus promoting better organization and encapsulation of code. Here is how you can refactor the function into a method within the Person
class.
class Person:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
def greeting(self):
print(f"Hello, my name is {self.first_name} {self.last_name}.")
In this revised implementation, greeting
is defined as a method of the Person
class. This means that greeting
is now a function that operates on instances of Person
, utilizing the instance’s attributes (first_name
and last_name
).
Here’s a breakdown of the changes and their implications:
Method Definition. The greeting function is defined within the Person class. This change encapsulates the behavior associated with a
Person
within the class itself, adhering to the principles of object-oriented programming.Self Parameter. Similar to the
__init__
method, thegreeting
method includes a parameter namedself
. This parameter represents the instance of the class on which the method is called. When you invokesomeone.greeting()
, Python automatically passes the instancesomeone
as theself
parameter to thegreeting
method. This allows the method to access and operate on the instance’s attributes.Method Invocation. When you call
someone.greeting()
, Python looks up thegreeting
method in thePerson
class and executes it with thesomeone
instance asself
. This means that the method can accesssomeone.first_name
andsomeone.last_name
, generating a personalized greeting message.Encapsulation and Cohesion By making
greeting
a method ofPerson
, we encapsulate behavior related to thePerson
class, which enhances cohesion. Encapsulation ensures that related data and methods are grouped together, making the code more modular and easier to maintain.
Note
As possible we carefuly use the terms function and method. A method is a function that is defined within a class and is intended to operate on instances of that class. Methods are bound to objects and can access and modify the object’s state through self.