close
Skip to main content

Object-Oriented Programming in Python: A Complete Guide

Learn the basics of object-oriented programming in Python: classes, objects, attributes, and methods explained step by step with code examples.
Updated May 28, 2026  · 12 min read

Object-Oriented programming (OOP) is a widely used concept to write applications. As a data scientist, you should be able to write applications to process your data, among a range of other things.

In this tutorial, I cover the basics of object-oriented programming in Python. You will learn how to create a class and instantiate objects, how to define methods and pass arguments to it, and how to add attributes to a class.

TL;DR

  • OOP organizes code around classes (blueprints) and objects (instances of those blueprints)

  • Define a class with the class keyword and initialize attributes inside __init__(self)

  • Instance methods operate on object data via self and are called with dot notation: my_object.method()

  • Python supports all four OOP pillars: encapsulation, inheritance, polymorphism, and abstraction

  • OOP promotes code reuse to create multiple objects from a single class template

What Is OOP?

Object-oriented programming (OOP) is based on the imperative programming paradigm, which uses statements to change a program's state. Imperative means that it focuses on describing how a program should operate. Examples of imperative programming languages are C, C++, Java, Go, Ruby and Python.

This stands in contrast to declarative programming, which focuses on what the computer program should accomplish, without specifying how (e.g., SQL, XQuery). For a comparison of paradigms, see our guide to functional programming vs. OOP.

OOP uses the concept of objects and classes. A class can be thought of as a 'blueprint' for objects. These can have their own

  • Attributes: characteristics they possess
  • Methods: actions they perform

OOP has a few key advantages over other design patterns. Development is often faster and cheaper, and the modular approach makes software easier to maintain. This, in turn, leads to higher-quality software, which is also extensible with new methods and attributes.

The learning curve is, however, longer. The core concepts require more upfront investment to understandfully. Computationally, OOP software is slower, and uses more memory since more lines of code have to be written.

The Four Pillars of OOP

Beyond classes and methods, OOP rests on four pillars that I have only touched on lightly so far. Each one deserves its own deep dive, but here is a quick overview with links to dedicated tutorials.

PillarWhat it meansLearn more
EncapsulationBundling data and methods together, controlling access with naming conventions (_private, __name_mangled)Encapsulation in Python
InheritanceCreating child classes that reuse and extend parent class behaviorPython Inheritance
PolymorphismDifferent classes sharing the same interface, so code can work with objects interchangeablyOOP in Python course
AbstractionHiding complexity behind a clean interface using abstract base classesPython Abstract Classes

OOP Example

An example of a class is the class Dog. Don't think of it as a specific dog, or your own dog. We're describing what a dog is and can do, in general. Dogs usually have a name and age; these are instance attributes. Dogs can also bark; this is a method.

When you talk about a specific dog, you would have an object in programming: an object is an instantiation of a class. This is the basic principle on which object-oriented programming is based. So my dog Ozzy, for example, belongs to the class Dog. His attributes are name = 'Ozzy' and age = '2'. A different dog will have different attributes.

OOP in Python

Python is one of the most popular languages for learning and applying OOP. Below, I cover what makes Python well-suited for object-oriented code and walk through the core syntax.

Is Python object oriented?

Python is a great programming language that supports OOP. You will use it to define a class with attributes and methods, which you will then call.

Python's key advantage over other programming languages like Java, C++ or R is that it is a dynamic language with high-level data types. This means it does not require the programmer to declare types of variables and arguments, which makes development much faster and code more readable than with Java or C++.

If you're new to Python, be sure to take a look at our Introduction to Python course.

How to create a class

To define a class in Python, you can use the class keyword, followed by the class name and a colon.

Inside the class, an __init__ method (a dunder method) is typically defined with def. This is the initializer that you can later use to instantiate objects. It's similar to a constructor in Java.

__init__ is called automatically when you create a new object. It takes one argument: self, which refers to the object itself.

Inside the method, the pass keyword is used as of now, because Python expects you to type something there. Remember to use correct indentation!

class Dog:

    def __init__(self):
        pass

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

In this case, you have a (mostly empty) Dog class, but no object yet. Let's create one!

Start Learning Python For Free

Instantiating objects

To instantiate an object, type the class name, followed by two brackets. 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 is clear that this object is a dog. But you haven't added any attributes yet. Let's give the Dog class a name and age by rewriting it:

class Dog:

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

You can see that the function now takes two arguments after self: name and age. These then get assigned to self.name and self.age respectively. You can now create a new ozzy object with a name and age:

ozzy = Dog("Ozzy", 2)

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

print(ozzy.name)

print(ozzy.age)
Ozzy
2

This can also be combined in a more elaborate sentence:

print(ozzy.name + " is " + str(ozzy.age) + " year(s) old.")
Ozzy is 2 year(s) old.

The str() function is used here to convert the age attribute, which is an integer, to a string, so you can use it in the print() function.

Define methods in a class

Now that you have a Dog class, it does have a name and age, which you can keep track of, but it doesn't actually do anything. This is where instance methods come in. You can rewrite the class to now include a bark() method. Notice how the def keyword is used again, as well as the self argument.

class Dog:

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

    def bark(self):
        print("bark bark!")

The bark method can now be called using the dot notation, after instantiating a new ozzy object. The method should print "bark bark!" to the screen. Notice the parentheses in .bark(). These are always used when calling a method. They're empty in this case, since the bark() method does not take any arguments.

ozzy = Dog("Ozzy", 2)

ozzy.bark()
bark bark!

Recall how you printed ozzy earlier? The code below now implements this functionality in the Dog class, with the doginfo() method. You then instantiate some objects with different properties and call the method on them.

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 the doginfo() method on objects with the dot notation. The response now depends on which Dog object you are calling the method on.

Since dogs get older, it would be nice if you could adjust their age accordingly. Ozzy just turned 3, so let's change his age.

ozzy.age = 3

print(ozzy.age)
3

It's as easy as assigning a new value to the attribute. You could also implement this as a birthday() method in the Dog class:

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, you don't need to manually change the dog's age. Whenever it is its birthday, you can just call the birthday() method. 

Passing arguments to methods

You would like our dogs to have a buddy. This should be optional since not all dogs are as sociable.

Take a look at the setBuddy() method below. It takes self, as per usual, and buddy as arguments. In this case, buddy will be another Dog object. Set the self.buddy attribute to buddy, and the buddy.buddy attribute to self.

This means that the relationship is reciprocal; you are your buddy's buddy. In this case, Filou will be Ozzy's buddy, which means that Ozzy automatically becomes Filou's buddy.

You could also set these attributes manually instead of defining a method, but that would require more work (writing two lines of code instead of one) every time you want to set a buddy.

Notice that in Python, you don't need to specify what type the argument is. If this were Java, it would be required.

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

You can now call the method with the dot notation and pass it another Dog object. In this case, Ozzy's buddy will be Filou:

ozzy = Dog("Ozzy", 2)
filou = Dog("Filou", 8)

ozzy.setBuddy(filou)

If you now want to get some information about Ozzy's buddy, you can use the dot notation twice: First, to refer to Ozzy's buddy, and a second time to refer to its attribute.

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

Notice how this can also be done for Filou.

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

The buddy's methods can also be called. The self argument that gets passed to doginfo() is now ozzy.buddy, which is filou.

ozzy.buddy.doginfo()
Filou is 8 year(s) old.

Python OOP Example

An example of where Object-Oriented programming in Python might come in handy is our Python For Finance: Algorithmic Trading tutorial. In it, we explain how to set up a trading strategy for a stock portfolio.

The trading strategy is based on the moving average of a stock price. If signals['short_mavg'][short_window:] > signals['long_mavg'][short_window:] is fulfilled, a signal is created. This signal is a prediction for the stock's future price change.

In the code below, you'll see that there is first an initialization, followed by the moving average calculation and signal generation. Since this is not object-oriented code, it's just one big chunk that gets executed at once.

Notice that we're using aapl in the example, which is Apple's stock ticker. If you wanted to do this for a different stock, you would have to rewrite the code.

# 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 an object-oriented approach, you only need to write the initialization and signal generation code once.

You can then create a new object for each stock you want to calculate a strategy on, and call the generate_signals() method on it. Notice that the OOP code is very similar to the code above, with the addition of 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'] = self.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

You can now simply instantiate an object with the parameters you want and generate signals for it.

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

Doing this for another stock 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())

Final Thoughts

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

For a more modern approach to defining classes, check out Python data classes, which reduce boilerplate code. If you want to expand the key concepts that you need to further work with Python, be sure to check out our Intermediate Python course.

With OOP, your code will grow in complexity as your program gets larger. You will have different classes, subclasses, objects, inheritance, instance methods, and more. For complex inheritance scenarios, learn about multiple inheritance and super().

Python OOP FAQs

What is object-oriented programming (OOP)?

Object-oriented programming is a programming paradigm that is based on the concept of "objects", which can contain data and code that manipulates that data. In OOP, objects are created from templates called "classes", which define the properties and behavior of the objects they create. OOP allows you to create reusable code and model real-world concepts more closely, making it a popular choice for many software projects.

What are classes and objects in Python?

In Python, a class is a template for creating objects. It defines the properties and behavior of the objects that are created from it. An object is an instance of a class, created by calling the class like a function. The object contains the data and behavior defined by the class, as well as a unique identity.

How do I define a class in Python?

To define a class in Python, you use the class keyword, followed by the name of the class and a colon. The class definition is indented, and the indented block contains the properties and methods (functions) that belong to the class.

How do I create an object from a class in Python?

To create an object from a class in Python, you call the class like a function, passing any necessary arguments to the class's constructor (the __init__ method). 

What are the four pillars of OOP in Python?

The four pillars of object-oriented programming are: Encapsulation (bundling data and methods together while restricting access), Inheritance (allowing classes to inherit attributes and methods from parent classes), Polymorphism (enabling objects of different types to be treated uniformly), and Abstraction (hiding complex implementation details while exposing essential features).

What is the difference between a class attribute and an instance attribute?

A class attribute is shared by all instances of a class and is defined directly in the class body. An instance attribute is unique to each object and is defined inside the __init__ method using self. For example, all dogs might share a species class attribute, but each dog has its own name instance attribute.

Topics
Related
Image

blog

Introduction to Programming Paradigms

Explore the core concepts of major programming paradigms with Python examples, including object-oriented, functional, procedural, and declarative paradigms.
Samuel Shaibu's photo

Samuel Shaibu

12 min

blog

Python Data Types Explained: A Beginner’s Guide

Learn the different Python data types and how to use them effectively.
Moez Ali's photo

Moez Ali

15 min

Tutorial

Encapsulation in Python: A Comprehensive Guide

Learn the fundamentals of implementing encapsulation in Python object-oriented programming.
Bex Tuychiev's photo

Bex Tuychiev

Tutorial

Python Automation: A Complete Guide

Learn about Python automation, including fundamental concepts, key libraries, working with data, using AI enhancements, and best practices. Includes real-world examples.
Mark Pedigo's photo

Mark Pedigo

Tutorial

Python Classes Tutorial

In Python, everything is an object. Numbers, strings, DataFrames, even functions are objects. In particular, everything you deal with in Python has a class, a blueprint associated with it under the hood.
DataCamp Team's photo

DataCamp Team

Tutorial

Python Data Classes: A Comprehensive Tutorial

A beginner-friendly tutorial on Python data classes and how to use them in practice
Bex Tuychiev's photo

Bex Tuychiev

See MoreSee More