Python Tutorial (30) - Object-Oriented

Time: Column:Python views:223

Python Object-Oriented Programming

From the very beginning, Python has been an object-oriented language. As a result, creating classes and objects in Python is straightforward. In this chapter, we will introduce Python's object-oriented programming (OOP) in detail.

If you are new to object-oriented programming (OOP) languages, you may need to first understand some of the basic features of OOP to form a foundational concept. This will help you learn Python’s OOP more easily.

Let’s start by briefly understanding some key characteristics of OOP.


Introduction to Object-Oriented Concepts

  • Class: Describes a collection of objects with the same properties and methods. It defines the attributes and methods shared by each object in that collection. Objects are instances of classes.

  • Method: Functions defined within a class.

  • Class Variable: Variables shared across all instances of a class. Defined inside the class but outside of methods. Class variables are typically not used as instance variables.

  • Data Member: Class or instance variables that hold data related to the class and its instances.

  • Method Overriding: If a method inherited from the parent class doesn’t meet the needs of the subclass, it can be rewritten. This process is called overriding.

  • Local Variable: A variable defined inside a method that is only accessible within that method.

  • Instance Variable: Variables defined in a class and associated with a particular instance of that class, usually prefixed with self.

  • Inheritance: A derived class (child class) inherits fields and methods from its base class (parent class). Inheritance allows the derived class to be treated as if it were a base class object. For example, a Dog class derived from an Animal class simulates an "is-a" relationship (e.g., "a dog is an animal").

  • Instantiation: The process of creating an instance (object) of a class.

  • Object: An instance of a class that contains data members (class and instance variables) and methods.

Compared to other programming languages, Python includes classes while trying not to introduce too much additional syntax or semantics.

Python classes provide all the fundamental OOP features: class inheritance, method overriding, and the ability to call base class methods from derived classes.

Objects in Python can hold any amount and type of data.


Class Definition

The syntax for defining a class is as follows:

class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>

Once a class is instantiated, its attributes can be accessed. In fact, after creating a class, its attributes can be accessed by referencing the class name.


Class Object

A class object supports two operations: attribute reference and instantiation.

Attribute references follow the same syntax as any other attribute reference in Python: obj.name.

When a class is created, all names defined in the class namespace become valid attribute names. For example:


Example (Python 3.0+)

#!/usr/bin/python3

class MyClass:
    """A simple class example"""
    i = 12345
    
    def f(self):
        return 'hello world'

# Instantiating the class
x = MyClass()

# Accessing class attributes and methods
print("The attribute i of MyClass is:", x.i)
print("The method f of MyClass returns:", x.f())

This creates a new class instance and assigns it to the local variable x. x is an empty object.

The output of the above code will be:

The attribute i of MyClass is: 12345
The method f of MyClass returns: hello world

__init__() Method

Classes have a special method named __init__(), known as the constructor. This method is automatically called when a class is instantiated, like this:

def __init__(self):
    self.data = []

When a class defines an __init__() method, it will automatically be called when an instance of the class is created. For example, when instantiating the class MyClass, the __init__() method will be invoked:

x = MyClass()

Of course, the __init__() method can accept parameters, which can be passed during class instantiation:

Example (Python 3.0+)

#!/usr/bin/python3

class Complex:
    def __init__(self, realpart, imagpart):
        self.r = realpart
        self.i = imagpart

x = Complex(3.0, -4.5)
print(x.r, x.i)  # Output: 3.0 -4.5

self Represents the Instance of the Class

The only distinction between a class method and a regular function is that a class method must have an additional first parameter, typically named self.

class Test:
    def prt(self):
        print(self)
        print(self.__class__)

t = Test()
t.prt()

The output of this example is:

<__main__.Test instance at 0x100771878>
__main__.Test

Here, self refers to the instance of the class, and self.__class__ refers to the class itself.

Customizing the self Parameter

The name self is not a Python keyword; you can use any other name, and the code will still work:

class Test:
    def prt(runoob):
        print(runoob)
        print(runoob.__class__)

t = Test()
t.prt()

The output will still be:

<__main__.Test instance at 0x100771878>
__main__.Test

In Python, self is a convention for naming the reference to the current instance. It allows methods to access and modify the attributes of the class instance.

Example

class MyClass:
    def __init__(self, value):
        self.value = value

    def display_value(self):
        print(self.value)

# Creating an instance of the class
obj = MyClass(42)

# Calling the instance method
obj.display_value()  # Output: 42

In this example, self refers to the instance of the class. It is used in the __init__ constructor to initialize the instance's attributes and in the display_value method to access the instance's attributes. By using self, class methods can access and modify the instance’s data and define the behavior of the class.


Class Methods in Python

In Python, class methods are defined using the def keyword, similar to regular functions. However, there is one key difference: class methods must include self as the first parameter, which represents the instance of the class.

Example (Python 3.0+)

#!/usr/bin/python3

# Class definition
class People:
    # Define basic attributes
    name = ''
    age = 0
    # Define private attributes (cannot be accessed directly outside the class)
    __weight = 0
    # Define the constructor method
    def __init__(self, n, a, w):
        self.name = n
        self.age = a
        self.__weight = w
    def speak(self):
        print("%s says: I am %d years old." % (self.name, self.age))

# Instantiate the class
p = People('runoob', 10, 30)
p.speak()

Output:

runoob says: I am 10 years old.

Inheritance

Python supports inheritance, which is a crucial feature of object-oriented programming. Without inheritance, the concept of classes would lose much of its power. A derived class can inherit properties and methods from a base class. The syntax for inheritance is as follows:

class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>

A subclass (derived class) inherits the properties and methods of a parent class (base class). The base class name must be within the same scope as the derived class definition. Additionally, if the base class is in another module, an expression can be used, like this:

class DerivedClassName(modname.BaseClassName):

Example (Python 3.0+)

#!/usr/bin/python3

# Class definition
class People:
    # Define basic attributes
    name = ''
    age = 0
    # Define private attributes (cannot be accessed directly outside the class)
    __weight = 0
    # Define the constructor method
    def __init__(self, n, a, w):
        self.name = n
        self.age = a
        self.__weight = w
    def speak(self):
        print("%s says: I am %d years old." % (self.name, self.age))

# Single inheritance example
class Student(People):
    grade = ''
    def __init__(self, n, a, w, g):
        # Call the constructor of the parent class
        People.__init__(self, n, a, w)
        self.grade = g
    # Override the parent class's method
    def speak(self):
        print("%s says: I am %d years old and I am in grade %d." % (self.name, self.age, self.grade))

s = Student('Ken', 10, 60, 3)
s.speak()

Output:

Ken says: I am 10 years old and I am in grade 3.

Multiple Inheritance

Python also supports limited multiple inheritance. The syntax for multiple inheritance is as follows:

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>

The order of base classes matters: when searching for a method, Python looks from left to right, stopping as soon as it finds the method in one of the base classes.


Example (Python 3.0+)

#!/usr/bin/python3

# Class definition
class People:
    # Define basic attributes
    name = ''
    age = 0
    # Define private attributes (cannot be accessed directly outside the class)
    __weight = 0
    # Define the constructor method
    def __init__(self, n, a, w):
        self.name = n
        self.age = a
        self.__weight = w
    def speak(self):
        print("%s says: I am %d years old." % (self.name, self.age))

# Single inheritance example
class Student(People):
    grade = ''
    def __init__(self, n, a, w, g):
        # Call the constructor of the parent class
        People.__init__(self, n, a, w)
        self.grade = g
    # Override the parent class's method
    def speak(self):
        print("%s says: I am %d years old and I am in grade %d." % (self.name, self.age, self.grade))

# Another class, preparing for multiple inheritance
class Speaker:
    topic = ''
    name = ''
    def __init__(self, n, t):
        self.name = n
        self.topic = t
    def speak(self):
        print("My name is %s, I am a speaker, and my topic is %s." % (self.name, self.topic))

# Multiple inheritance
class Sample(Speaker, Student):
    a = ''
    def __init__(self, n, a, w, g, t):
        Student.__init__(self, n, a, w, g)
        Speaker.__init__(self, n, t)

test = Sample("Tim", 25, 80, 4, "Python")
test.speak()   # Since the method name is the same, Python calls the method from the first parent class (Speaker)

Output:

My name is Tim, I am a speaker, and my topic is Python.

In this example, Python calls the speak method from the Speaker class because it appears first in the class hierarchy of Sample.


Method Overriding

If the functionality of a method in the parent class does not meet your needs, you can override it in the subclass. Here’s an example:

Example (Python 3.0+)

#!/usr/bin/python3

class Parent:  # Define parent class
   def myMethod(self):
      print('Calling parent method')

class Child(Parent):  # Define child class
   def myMethod(self):
      print('Calling child method')

c = Child()  # Create an instance of the child class
c.myMethod()  # Call the overridden method in the child class
super(Child, c).myMethod()  # Use the child instance to call the overridden parent class method

The super() function is used to call a method from the parent (super) class.

Output:

Calling child method
Calling parent method

Class Attributes and Methods

Private Attributes

  • __private_attrs: Attributes that start with two underscores are private and cannot be accessed directly outside the class. To use them inside the class, you use self.__private_attrs.

Defining Methods in a Class

Class methods are defined using the def keyword inside the class. A class method must include self as the first parameter, representing the instance of the class.

  • The name self is a convention and can be replaced by another name like this, but it’s best to follow the standard.

Private Methods

  • __private_method: Private methods start with two underscores and can only be called inside the class. They cannot be called from outside the class.

Example: Private Attributes

Example (Python 3.0+)

#!/usr/bin/python3

class JustCounter:
    __secretCount = 0  # Private variable
    publicCount = 0    # Public variable

    def count(self):
        self.__secretCount += 1
        self.publicCount += 1
        print(self.__secretCount)

counter = JustCounter()
counter.count()
counter.count()
print(counter.publicCount)
print(counter.__secretCount)  # Error: Cannot access private variable

Output:

1
2
2
Traceback (most recent call last):
  File "test.py", line 16, in <module>
    print(counter.__secretCount)  # Error: Cannot access private variable
AttributeError: 'JustCounter' object has no attribute '__secretCount'

Example: Private Methods

Example (Python 3.0+)

#!/usr/bin/python3

class Site:
    def __init__(self, name, url):
        self.name = name       # Public attribute
        self.__url = url       # Private attribute

    def who(self):
        print('Name  : ', self.name)
        print('URL : ', self.__url)

    def __foo(self):           # Private method
        print('This is a private method')

    def foo(self):             # Public method
        print('This is a public method')
        self.__foo()

x = Site('Tutorial', 'www.example.com')
x.who()      # Outputs normally
x.foo()      # Outputs normally
x.__foo()    # Error: Cannot call private method

Output:

Name  :  Tutorial
URL :  www.example.com
This is a public method
This is a private method
Traceback (most recent call last):
  File "test.py", line 18, in <module>
    x.__foo()      # Error
AttributeError: 'Site' object has no attribute '__foo'

Special Methods in Classes

  • __init__: Constructor method, called when an object is created.

  • __del__: Destructor method, called when an object is deleted.

  • __repr__: Converts an object to a string representation.

  • __setitem__: Sets an item by index.

  • __getitem__: Retrieves an item by index.

  • __len__: Returns the length of an object.

  • __cmp__: Compares two objects.

  • __call__: Enables an object to be called as a function.

  • __add__: Defines the addition behavior.

  • __sub__: Defines the subtraction behavior.

  • __mul__: Defines the multiplication behavior.

  • __truediv__: Defines the division behavior.

  • __mod__: Defines the modulo operation.

  • __pow__: Defines the exponentiation operation.


Operator Overloading

Python supports operator overloading, allowing you to define custom behavior for operators in your class. Here's an example:

Example (Python 3.0+)

#!/usr/bin/python3

class Vector:
   def __init__(self, a, b):
      self.a = a
      self.b = b

   def __str__(self):
      return 'Vector (%d, %d)' % (self.a, self.b)

   def __add__(self, other):
      return Vector(self.a + other.a, self.b + other.b)

v1 = Vector(2, 10)
v2 = Vector(5, -2)
print(v1 + v2)

Output:

Vector (7, 8)

This example demonstrates overloading the + operator to add two vector objects.