class Attendee:
# Class attributes (same for all instances of the class)
topic = "Programming"
language = "Python"
level = "Intro"
# Instance attributes (specific to each instance)
def __init__(self, name, email):
self.name = name
self.email = email
# Instance methods
def introduce(self):
return f"My name is {self.name}."
def goal(self, goal_description):
return f"I want to learn Python {goal_description}."Object-oriented programming
Python is a flexible language that can be written using various programming styles. Different paradigms can be used in different parts of the same code to suit specific needs.
So far in this course, we have focused on procedural programming. In this section, I will briefly talk about the various programming paradigms in Python, then focus on a very common style: object-oriented programming.
Programming paradigms in Python
Imperative programming
In imperative programming, the code follows an execution flow and the state of a program changes throughout its execution.
This is the paradigm most commonly used in Python and the one you are most likely to be familiar with.
Procedural programming
Procedural programming is a subtype of imperative programming widely used for simple, top-down sets of instructions. The focus is on procedures (functions) that operate on data.
Small scripts, task automation, and data analyses often rely on procedural programming. This is what we have covered in this course so far.
Object-oriented programming
In object-oriented programming (OOP), the code is organized around objects which are instances of classes and bundle together data (attributes) and behaviour (methods). This reduces the amount of code that needs to be written thanks to the property of inheritance. It also hides parts of the code that doesn’t need to be exposed.
Web applications, software development, and complex code such as can be seen in deep learning rely on OOP, making it probably the most common programming style in Python outside of data science.
Declarative programming
Declarative programming does not rely on state changes or control flow, but instead on formal logic.
Functional programming
Functional programming is the subset of declarative programming that can be used in Python. It relies on pure functions without side effects.
This is a restrictive style, but it has the benefits of being more readable, easier to maintain, and promotes better code modularity and reusability.
If you ever want to use JAX—a Python library for high-performance array computing, you will have to write functional programming code.
OOP in Python
Object-oriented programming allows to create objects with attributes and methods.
First you define a class which works as the blueprint for those objects, then you instantiate individual objects from that class. Those objects are called instances.
This is very convenient in a number of situations. For instance, in deep learning, most frameworks define model classes and you create a model as an instance of one of these classes. The data is also often packaged in a Dataset class which contains methods convenient to access individual samples.
Defining classes
By convention, classes names are capitalized (and compound class names use TheCamelCase notation). You define a class with the class keyword:
__init__ is called a dunder (double underscore), special method, or magic method. To get the list of all dunders, you can run:
print(dir(int))['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'is_integer', 'numerator', 'real', 'to_bytes']
Here is an example from one of my deep learning courses on how to define a dataset class:
class NABirdsDataset:
"""NABirds dataset class."""
def __init__(self, metadata, data_dir):
self.metadata = metadata
self.data_dir = data_dir
def __len__(self):
return len(self.metadata)
def __getitem__(self, idx):
path = os.path.join(self.data_dir, self.metadata.get_column('path')[idx])
img = iio.imread(path)
species_name = self.metadata.get_column('species_name')[idx]
species_id = self.metadata.get_column('species_id')[idx]
photographer = self.metadata.get_column('photographer')[idx]
element = {
'img': img,
'species_name': species_name,
'species_id': species_id,
'photographer': photographer,
}
return elementNote the use of dunders to set the methods here. When a dunder already exists, you don’t have to define the method yourself. __len__ is a method that allows us to get the length of the dataset and __getitem__ allows us to get samples from it.
Your turn:
- How many class attributes does this class have?
- How many instance attributes?
- How many methods?
Instantiating objects
To create an instance of a class, you run the class name followed by parentheses. If it doesn’t have instance attributes, you don’t pass anything between the parentheses. If it does, you pass the attributes as arguments.
In our case, we have 2 instance attributes, so we need to pass to arguments to create instances:
attendee1 = Attendee("Alex", "alex@email.com")
attendee2 = Attendee("Marie", "marie@email.com")attendee1 and attendee2 are instances of the class Attendee.
print(attendee1)<__main__.Attendee object at 0x7f515ad93620>
Using instances
You can get attributes:
print(attendee1.topic)
print(attendee1.name)
print(attendee1.email)Programming
Alex
alex@email.com
And use the methods:
print(attendee1.introduce())
print(attendee1.goal("to teach it"))My name is Alex.
I want to learn Python to teach it.
Inheritance
Classes can inherit attributes and methods from another class. The initial class is called a superclass, parent class, or base class. The class that inherits from it is called a subclass, or child class. This is a useful way to recycle code.
For instance, when you define a Model class in most frameworks, you will define it as a subclass of some superclass defined in the framework of your choice.
Here is an example from a JAX tutorial:
# nnx.Module is the superclass
# SimpleNN is the subclass
class SimpleNN(nnx.Module):
# Instance attributes
def __init__(self, n_features: int = 64, n_hidden: int = 100, n_targets: int = 10,
*, rngs: nnx.Rngs):
self.n_features = n_features
self.layer1 = nnx.Linear(n_features, n_hidden, rngs=rngs)
self.layer2 = nnx.Linear(n_hidden, n_hidden, rngs=rngs)
self.layer3 = nnx.Linear(n_hidden, n_targets, rngs=rngs)
# Method for the forward pass
def __call__(self, x):
x = x.reshape(x.shape[0], self.n_features)
x = nnx.selu(self.layer1(x))
x = nnx.selu(self.layer2(x))
x = self.layer3(x)
return xThis makes the code very lean: the nnx.Module from the Flax library already contains all the attributes and behaviours that you want for a Model class. All you have to do is to overwrite a handful of them to fit your particular needs.
With our example of a the class Attendee, we could create a subclass for another course by overwriting a handful of sections while keeping the rest as is:
class NewAttendee(Attendee):
language = "Julia"
def goal(self, goal_description):
return f"I want to learn Julia {goal_description}."Note how we don’t have to define what we are happy to inherit from the superclass. This is efficient code recycling.
new_attendee = NewAttendee("Eric", "eric@email.com")print(new_attendee.topic)
print(new_attendee.language)
print(new_attendee.name)Programming
Julia
Eric
print(new_attendee.introduce())
print(new_attendee.goal("because it is fast"))My name is Eric.
I want to learn Julia because it is fast.
Your turn:
Create a subclass of Attendee for an advanced Python course.