Python 2.7.x - trying to get the hang of OOP and classes

Learn python 2.7.x - trying to get the hang of oop and classes with practical examples, diagrams, and best practices. Covers python, oop development techniques with visual explanations.

Mastering Python 2.7.x OOP: A Comprehensive Guide to Classes and Objects

Hero image for Python 2.7.x - trying to get the hang of OOP and classes

Dive into Object-Oriented Programming (OOP) in Python 2.7.x. This guide covers classes, objects, inheritance, and polymorphism with practical examples to solidify your understanding.

Object-Oriented Programming (OOP) is a fundamental paradigm that structures software around data, or objects, rather than functions and logic. While Python 2.7.x is an older version, understanding its approach to OOP provides a strong foundation for grasping modern Python's object model. This article will walk you through the core concepts of classes, objects, attributes, methods, inheritance, and polymorphism, specifically tailored for Python 2.7.x.

The Basics: Classes and Objects

At the heart of OOP are classes and objects. A class is a blueprint for creating objects (a particular data structure), providing initial values for state (member variables or attributes) and implementations of behavior (member functions or methods). An object is an instance of a class. Think of a class as a cookie cutter and objects as the cookies it produces. Each cookie (object) has the same shape (structure defined by the class) but can have different toppings (attribute values).

class Dog:
    # Class attribute
    species = "Canis familiaris"

    # Initializer / Instance attributes
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # Instance method
    def bark(self):
        return "Woof! My name is {} and I am {} years old.".format(self.name, self.age)

# Creating objects (instances of the Dog class)
my_dog = Dog("Buddy", 3)
your_dog = Dog("Lucy", 5)

print my_dog.name         # Output: Buddy
print your_dog.age        # Output: 5
print my_dog.species      # Output: Canis familiaris
print my_dog.bark()       # Output: Woof! My name is Buddy and I am 3 years old.

Defining a simple class and creating objects in Python 2.7.x

Understanding Inheritance and Polymorphism

Inheritance allows a class (child or subclass) to inherit attributes and methods from another class (parent or superclass). This promotes code reusability and establishes a natural hierarchy. Polymorphism, meaning 'many forms', allows objects of different classes to be treated as objects of a common type. In Python, this is often achieved through method overriding and duck typing (if it walks like a duck and quacks like a duck, then it's a duck).

classDiagram
    Animal <|-- Dog
    Animal <|-- Cat
    Animal : +str name
    Animal : +int age
    Animal : +make_sound()
    Dog : +bark()
    Cat : +meow()

Class diagram illustrating inheritance between Animal, Dog, and Cat classes.

class Animal(object):
    def __init__(self, name):
        self.name = name

    def make_sound(self):
        raise NotImplementedError("Subclass must implement abstract method")

class Dog(Animal):
    def __init__(self, name, breed):
        super(Dog, self).__init__(name) # Python 2.7.x super() call
        self.breed = breed

    def make_sound(self):
        return "Woof!"

class Cat(Animal):
    def __init__(self, name, color):
        super(Cat, self).__init__(name) # Python 2.7.x super() call
        self.color = color

    def make_sound(self):
        return "Meow!"

# Polymorphism in action
def animal_action(animal):
    print "{} says: {}".format(animal.name, animal.make_sound())

my_dog = Dog("Rex", "German Shepherd")
my_cat = Cat("Whiskers", "Tabby")

animal_action(my_dog) # Output: Rex says: Woof!
animal_action(my_cat) # Output: Whiskers says: Meow!

Demonstrating inheritance and polymorphism in Python 2.7.x

Encapsulation and Data Hiding

Encapsulation is the bundling of data (attributes) and methods that operate on the data into a single unit, or class. It also involves restricting direct access to some of an object's components, which is known as data hiding. While Python doesn't have strict private keywords like Java or C++, it uses conventions to indicate that an attribute or method is intended for internal use. A single leading underscore (_attribute) suggests it's protected (should not be accessed directly from outside the class), and a double leading underscore (__attribute) triggers name mangling, making it harder to access directly and effectively 'private' within the class.

class BankAccount:
    def __init__(self, initial_balance):
        self.__balance = initial_balance # 'Private' attribute due to name mangling
        self._account_number = "12345" # Protected attribute

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount
            print "Deposited {}. New balance: {}".format(amount, self.__balance)
        else:
            print "Deposit amount must be positive."

    def get_balance(self):
        return self.__balance

my_account = BankAccount(100)
my_account.deposit(50) # Output: Deposited 50. New balance: 150

# Attempting to access 'private' attribute directly (will cause AttributeError or mangled access)
# print my_account.__balance # This will raise an AttributeError
print my_account._BankAccount__balance # This works, but is not recommended

print my_account.get_balance() # Output: 150
print my_account._account_number # Accessing protected attribute (conventionally discouraged)

Illustrating encapsulation and Python's approach to data hiding