Python Object-Oriented Programming (OOP): Tutorial

Learn the basics of object-oriented programming (OOP) in Python: explore classes, objects, instance methods, attributes, and more!

Object-oriented programming is a widely used concept for writing powerful applications. As a data scientist, you will need to create applications that handle data and perform a variety of other tasks. In this tutorial, we will explore the basics of object-oriented programming in Python. You will learn the following.

  • How to create a class
  • Object instantiation
  • Adding attributes to a class
  • Defining methods within a class
  • Passing arguments to methods
  • How to use OOP in finance with Python

 

OOP: Introduction

Object-oriented programming has several advantages over other design patterns. Development is faster and cheaper, and the maintainability of software is better. This ultimately leads to higher-quality software that can be extended with new methods and attributes. However, the learning curve is steeper, and the concepts can be too complex for beginners. Computationally, OOP software may be slower and require more lines of code, thus consuming more memory.

Object-oriented programming is based on the imperative programming paradigm, which uses statements to change the program’s state. It focuses on how the program should work. Examples of imperative programming languages include C, C++, Java, Go, Ruby, and Python. This contrasts with declarative programming, which focuses on what the computer program should achieve without specifying how. Examples include database query languages such as SQL and XQuery, where you tell the computer where and what data to query but not how to do it.

OOP uses the concepts of objects and classes. A class can be thought of as a ‘blueprint’ for creating objects. These objects can have unique properties (characteristics they own) and methods (actions they perform).

 

OOP Example

An example of a class is a class called Dog. Do not think of it as a specific dog or your own dog. We are describing what a dog is and what it can do in general. A dog typically has a name and an age. These are instance attributes. A dog can also bark; this is a method.

When talking about a specific dog, there will be an object in programming. An object is an instance of a class. This is the core principle of object-oriented programming. For example, my dog Ozzy belongs to the class Dog, and his attributes are name = ‘Ozzy’ and age = ‘2’. Other dogs will have different attributes.

 

Object-Oriented Programming in Python

Is Python Object-Oriented?

Python is a great programming language that supports OOP. You can define classes with properties and methods and then call them. Python offers several advantages over other programming languages such as Java, C++, or R. It is a dynamic language with high-level data types, which means it allows much faster development compared to Java or C++. Programmers do not need to declare variable and argument types. Additionally, Python is easy for beginners to understand and learn, and its code is more readable and intuitive.

 

How to Create a Class

To define a class in Python, use the class keyword, followed by the class name and a colon. Inside the class, methods should be defined with def __init__. This acts as an initializer that can later be used to instantiate an object. It is similar to constructors in Java. The __init__ method must always exist! It takes one argument, self, which refers to the object itself. Inside the method, you can use the pass keyword because Python expects something to be input there. Don’t forget to use the correct indentation!

class Dog:

    def __init__(self):
        pass

Note: In Python, self is equivalent to this in C++ or Java.

In this case, we have the Dog class (mostly empty), but we do not yet have an object. Let’s create one!

Instantiating Objects

To instantiate an object, you simply type the class name followed by two parentheses. You can assign this to a variable to keep track of the object.

ozzy = Dog()

And print it:

print(ozzy)

<__main__.Dog object at 0x111f47278>

 

Adding Attributes to a Class

After printing ozzy, it becomes clear that this object is indeed a dog. But we haven’t added any attributes yet. Let’s rewrite the Dog class to specify a name and age.

class Dog:

    def __init__(self, name, age):  
        self.name = name
        self.age = age

Now the function takes two arguments: name and age. These are then assigned to self.name and self.age, respectively. Now we can create a new object named ozzy using the name and age.

ozzy = Dog(“Ozzy”, 2)

To access an object’s attributes in Python, you can use dot notation. This is done by typing the object’s name followed by a dot and the name of the attribute.

print(ozzy.name)

print(ozzy.age)

Ozzy
2

This can also be combined into more sophisticated statements:

print(ozzy.name + ” is ” + str(ozzy.age) + ” year(s) old.”)

Ozzy is 2 year(s) old.

The str() function here is used to convert the integer attribute age to a string so it can be used within the print() function.

 

Defining Methods in a Class

Now that we have a Dog class with a name and age, we can keep track of them, but they don’t actually do anything yet. This is where instance methods come into play. We can rewrite the class to include a method. Note how the keyword is reused and how arguments are used in bark().

class Dog:

    def __init__(self, name, age):  
        self.name = name
        self.age = age

    def bark(self):
        print(“bark bark!”)

Now we can instantiate a new object and use dot notation to call the method bark. The method should print “bark bark!” on the screen. Note the parentheses when calling the bark() method. This is always used when calling a method, and in this case, the method bark() does not take any arguments, so the parentheses are empty.

ozzy = Dog(“Ozzy”, 2)

ozzy.bark()

bark bark!

Do you remember what you printed before ozzy? The code below now implements this functionality in the Dog class using the doginfo() method. Then we instantiate some objects with different attributes and call the method on those objects.

class Dog:

    def __init__(self, name, age):  
        self.name = name
        self.age = age

    def bark(self):
        print(“bark bark!”)

    def doginfo(self):
        print(self.name + ” is ” + str(self.age) + ” year(s) old.”)

ozzy = Dog(“Ozzy”, 2)
skippy = Dog(“Skippy”, 12)
filou = Dog(“Filou”, 8)

ozzy.doginfo()
skippy.doginfo()
filou.doginfo()

Ozzy is 2 year(s) old.
Skippy is 12 year(s) old.
Filou is 8 year(s) old.

As you can see, you can call a method from an object using dot notation. Now the responses depend on which object is calling the doginfo() method.

As dogs age, it’s good to adjust their age accordingly. Ozzy just turned 3, so let’s change his age.

ozzy.age = 3

print(ozzy.age)

3

It is as easy as assigning a new value to the attribute. You can also implement this as a method in the Dog class called birthday().

class Dog:

    def __init__(self, name, age):  
        self.name = name
        self.age = age

    def bark(self):
        print(“bark bark!”)

    def doginfo(self):
        print(self.name + ” is ” + str(self.age) + ” year(s) old.”)

    def birthday(self):
        self.age += 1

ozzy = Dog(“Ozzy”, 2)

print(ozzy.age)

2

ozzy.birthday()

print(ozzy.age)

3

Now there’s no need to manually change the dog’s age. You just need to call the birthday() method when it is the dog’s birthday.

 

Passing Arguments to Methods

English: You want your dog to have a buddy. Not all dogs are social, so this should be optional. Take a look at the below method. As usual, you use setBuddy() with an argument. In this case, it’s another object. This sets the attributes buddy and buddy, meaning the relationship is reciprocal; you’re friends with each other’s friends. In this case, Filou becomes Ozzy’s buddy, and thus Ozzy automatically becomes Filou’s buddy. Instead of defining the method, you could manually set these attributes, but that requires more work (two lines of code instead of one) every time you set a buddy. Python does not require you to specify the types of arguments. If this were Java, it would be mandatory.

class Dog:

    def __init__(self, name, age):  
        self.name = name
        self.age = age

    def bark(self):
        print(“bark bark!”)

    def doginfo(self):
        print(self.name + ” is ” + str(self.age) + ” year(s) old.”)

    def birthday(self):
        self.age += 1

    def setBuddy(self, buddy):
        self.buddy = buddy
        buddy.buddy = self

Now you can call the method using dot notation and pass another Dog object. In this case, Ozzy’s buddy becomes Filou.

ozzy = Dog(“Ozzy”, 2)
filou = Dog(“Filou”, 8)

ozzy.setBuddy(filou)

If you want to know about Ozzy’s buddy, you can use dot notation twice. The first refers to Ozzy’s buddy, and the second refers to that buddy’s attributes.

print(ozzy.buddy.name)
print(ozzy.buddy.age)

Filou
8

Note how this is also possible for Filou.

print(filou.buddy.name)
print(filou.buddy.age)

Ozzy
2

You can also call methods of friends. The argument being passed is now self, which is ozzy.buddy, meaning Filou.

ozzy.buddy.doginfo()

Filou is 8 year(s) old.

 

Python OOP Example

An example of where object-oriented programming can be useful in Python is in the Python For Finance: Algorithmic Trading tutorial, where we outline how to set up trading strategies for stock portfolios. The trading strategy is based on moving averages of stock prices; a signal is generated when signals[‘short_mavg’][short_window:] > signals[‘long_mavg’][short_window:] is met. This signal is a prediction of future price changes in the stock. In the code below, there is an initialization first and then the moving average calculations and signal generation. This is a single large chunk of code that runs at once, and this aapl is Apple’s stock ticker. You would need to rewrite the code to perform this task for another stock.

# Initialize
short_window = 40
long_window = 100
signals = pd.DataFrame(index=aapl.index)
signals[‘signal’] = 0.0

# Create short simple moving average over the short window
signals[‘short_mavg’] = aapl[‘Close’].rolling(window=short_window, min_periods=1, center=False).mean()

# Create long simple moving average over the long window
signals[‘long_mavg’] = aapl[‘Close’].rolling(window=long_window, min_periods=1, center=False).mean()

# Create signals
signals[‘signal’][short_window:] = np.where(signals[‘short_mavg’][short_window:] > signals[‘long_mavg’][short_window:], 1.0, 0.0)

# Generate trading orders
signals[‘positions’] = signals[‘signal’].diff()

# Print `signals`
print(signals)

In the object-oriented approach, you need to write the initialization and signal generation code just once. Then you can create new objects for each stock you want to compute the strategy for and call the method generate_signals() on them. The OOP code is very similar to the code above but uses self.

class MovingAverage():

    def __init__(self, symbol, bars, short_window, long_window):
        self.symbol = symbol
        self.bars = bars
        self.short_window = short_window
        self.long_window = long_window

    def generate_signals(self):
        signals = pd.DataFrame(index=self.bars.index)
        signals[‘signal’] = 0.0

        signals[‘short_mavg’] = bars[‘Close’].rolling(window=self.short_window, min_periods=1, center=False).mean()
        signals[‘long_mavg’] = bars[‘Close’].rolling(window=self.long_window, min_periods=1, center=False).mean()

        signals[‘signal’][self.short_window:] = np.where(signals[‘short_mavg’][self.short_window:] > signals[‘long_mavg’][self.short_window:], 1.0, 0.0)

        signals[‘positions’] = signals[‘signal’].diff()   

        return signals

Now you can simply instantiate an object with the desired parameters and generate signals for that object.

apple = MovingAverage(‘aapl’, aapl, 40, 100)
print(apple.generate_signals())

Doing this for other stocks becomes very easy; it’s just a matter of instantiating a new object with a different stock symbol.

microsoft = MovingAverage(‘msft’, msft, 40, 100)
print(microsoft.generate_signals())

Object-Oriented Programming in Python: Wrap Up

We have covered some of the main OOP concepts in Python. You now know how to declare classes and methods, instantiate objects, set attributes, and call instance methods. These skills will be useful in your future career as a data scientist.

With OOP, as programs grow, the complexity of code increases. There are various classes, subclasses, objects, inheritance, instance methods, and more. You need to structure your code properly and keep it readable. Following design patterns is a good way to do this. They represent a set of guidelines to avoid poor design. They address specific problems that frequently occur in Python OOP and provide solutions that can be reused. These OOP design patterns can be categorized into several groups, including creational patterns, structural patterns, and behavioral patterns. An example of a creational pattern is a singleton, which is used when you want to ensure that only one instance of a class can be created.