Python Object-Oriented Programming Fundamentals

Procedural vs Object-Oriented Programming Paradigms

Procedural programming focuses on solving problems through sequential steps, resembling an assembly line approach with mechanical thinking patterns.

Advantages: Complex problems become streamlined and simplified. Disadvantages: Poor extensibility.

Object-oriented programming treats objects as combinations of attributes and behaviors. This approach involves manipulating individual objects by providing them with methods and properties to achieve specific goals without concerning oneself with the underlying process.

Advantages: High extensibility. Disadvantages: Higher complexity compared to procedural programming.

Classes

Objects combine features and skills, while classes represent shared characteristics and abilities among multiple objects.

Key points:

  1. Objects are concrete entities, whereas classes are abstract concepts
  2. Different perspectives yield different class and object definitions

In programs: define classes first, then instantiate objects from those classes.

# Class definition example
class Learner:
    # Shared attributes
    institution = "Fourth Educational Facility"
    
    # Shared behavior
    def learn(self):
        print("Diligent learning approach.")

# A class serves as a namespace containing variables and functions
# Class body executes immediately during definition, creating a namespace

# Access operations
print(Learner.__dict__)  # View class namespace
print(Learner.institution)  # Equivalent to Learner.__dict__['institution']
print(Learner.learn)

# Modification operations
Learner.institution = "Academic Institution"  # Learner.__dict__['institution'] = 'Academic Institution'
Learner.nation = "Country"    # Learner.__dict__["nation"] = "Country"
del Learner.nation             # del Learner.__dict__["nation"]

# Using behavior (function)
Learner.learn(123)

Summary:

  1. Classes fundamentally serve as namespaces containing variable and function names
  2. Purpose: Extract names for usage or instantiate objects from the class

Objects

Calling a class creates an object; objects also function as namespaces.

The process of calling a class is called instantiation, and the returned value is an object/instance of the class.

class Learner:
    # Shared attributes
    institution = "Fourth Educational Facility"

    def __init__(self, full_name, years, gender):
        self.full_name = full_name
        self.years = years
        self.gender = gender

    # Shared behavior
    def learn(self):
        print("Diligent learning approach.")

student1 = Learner('jones', 23, 'male')
# When calling the class:
# 1. Creates empty object student1, then returns it
# 2. Triggers execution of __init__ function, passing object and parameters

# __init__ purpose: Initialize unique attributes for each instance during instantiation
# Note: Must not return values, otherwise errors occur

Attribute lookup sequence: First search in the object, then in the class, then raise error if not found.

Attributes defined in classes are shared among all objects; both objects and classes can access them.

When objects modify class-defined attributes, they add new attributes to their own namespace.

When classes modify their defined attributes, all objects see the changes upon access.

# Counting instances
class Learner:
    # Shared attributes
    institution = "Fourth Educational Facility"
    
    counter = 0

    def __init__(self, full_name, years, gender):
        self.full_name = full_name
        self.years = years
        self.gender = gender
        Learner.counter += 1

    # Shared behavior
    def learn(self):
        print("Diligent learning approach.")

student1 = Learner('alpha', 13, 'male')
student2 = Learner('beta', 13, 'female')
student3 = Learner('gamma', 16, 'male')
print(Learner.counter, student1.counter, student2.counter, student3.counter)
# Output: 3 3 3 3

Bound methods: Functions defined in classes become class function attributes and are bound for object usage.

class Learner:
    # Shared attributes
    institution = "Fourth Educational Facility"

    def __init__(self, full_name, years, gender):
        self.full_name = full_name
        self.years = years
        self.gender = gender

    # Shared behavior
    def learn(self, parameter):
        print("Diligent learning.%s" % self.full_name)

student1 = Learner('alpha', 13, 'male')
# Usage by class
# Learner.learn(student1,123)

# Object invocation
# When invoked by object, passes itself as first parameter
# Additional parameters go directly in parentheses
student1.learn(123)

Inheritance and Specialization

Inheritance is a way to create new classes. New classes are called subclasses/derived classes, while inherited classes are parent/base/super classes.

Python inheritance characteristics:

  1. Subclasses can reuse parent class attributes
  2. Python allows single class to inherit multiple parent classes simultaneously
  3. Classes are categorized as new-style or classic in inheritance contexts

New-style classes: Any clas inheriting from object, including subclasses Python 3: All classes implicitly inherit object even without explicit declaration Classic classes: Classes not inheriting object or their subclasses Python 2: Only distinguishes between new-style and classic classes

Purpose of inheritance: Reduce code redundancy between classes

View parent classes: __bases__

# Method one: Explicitly reference specific class function
# This approach operates independently of inheritance
# Accesses class functions without automatic value passing

class Person:
    institution = 'Fourth Educational Facility'

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

class Student():
    def __init__(self, name, age, gender, grade=0):
        # Use parent class attributes
        Person.__init__(self, name, age, gender)
        # Derive new attributes
        self.grade = grade

    def study(self):
        print('%s is studying.' % self.name)

class Instructor():
    def __init__(self, name, age, gender, rank):
        Person.__init__(self, name, age, gender)
        self.rank = rank

    def assign_grade(self, student, number):
        student.grade = number

student1 = Student('jones', 29, 'male')
print(student1.__dict__)
student1.study()
instructor1 = Instructor('bbb', 19, 'male', 11)
print(instructor1.__dict__)

Polymorphism

Polymorphism refers to different forms of the same type of entity.

Polymorphic behavior: Under polymorphic conditions, objects can be used directly without considering their specific types.

Core principle of polymorphism: Standardization.

# First approach:
# Python provides strict interface standardization (not recommended)
import abc
class Creature(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def vocalize(self):
        pass
    @abc.abstractmethod
    def move(self):
        pass

# Creature() # Parent class only establishes standards, cannot instantiate

class Human(Creature):
    def vocalize(self):
        print('speak greetings')
    def move(self):
        pass

class Canine(Creature):
    def vocalize(self):
        print('bark bark bark')
    def move(self):
        pass

class Swine(Creature):
    def vocalize(self):
        print('grunt grunt grunt')
    def move(self):
        pass

obj1 = Human()
obj2 = Canine()
obj3 = Swine()
# Instantiation doesn't cause errors,
# but calling methods without required vocalize, move methods causes errors
# This enforces standardized interfaces

Encapsulation

Encapsulation involves hiding internal implementation while exposing controlled interfaces.

How to encapsulate: Add __ prefix to attributes or methods in class definition (no __ suffix)

Summary:

  1. __ prefixed attributes create syntactic transformation, not true external restriction (Encapsulated method: __func becomes _ClassName__func)
  2. Transformation occurs once during class definition phase
  3. Parent classes can prevent child class overrides using __ prefix
class Container:
    __x = 111  # _Container__x
    __y = 222  # _Container__y

    def __init__(self, identifier, duration):
        self.__identifier = identifier
        self.__duration = duration

    def __process(self):  # _Container__process
        print('process')

    def retrieve_info(self):
        print(self.__identifier, self.__duration, self.__x)  # print(self._Container__identifier, self._Container__duration, self._Container__x)

Why encapsulation: For data attributes: Hide data internally, preventing direct external manipulation. Create internal interfaces for controlled access with validation logic.

class Individual:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def display_info(self):
        print('<name:%s age:%s>' % (self.__name, self.__age))

    def update_info(self, name, age):
        if type(name) is not str:
            print('Name must be string type')
            return
        if type(age) is not int:
            print('Age must be integer type')
            return
        self.__name = name
        self.__age = age

person = Individual('jones', 19)
person.display_info()

# person.update_info(123,20)
person.update_info('jones', '19')
# Neither modification succeeds

Property Decorator

Transforms class methods into attribute-like access.

# Usage example
class Individual:
    def __init__(self, name):
        self.__name = name

    @property
    def name(self):
        return '<name:%s>' % self.__name

    @name.setter
    def name(self, value):
        if type(value) is not str:
            print('Name must be string type')
            return
        self.__name = value

    @name.deleter
    def name(self):
        del self.__name

obj = Individual('jones')
# View
# print(obj.name)
# Modify
# obj.name='JONES'
# obj.name=123
# print(obj.name)
# Delete
# del obj.name
# print(obj.__dict__)

Built-in Methods

isinstance and issubclass

isinstance checks if an object is an instance of a class, issubclass checks subclass relationships.

isinstance() vs type() differences:
type() doesn't consider subclass-parent class relationships
isinstance() considers inheritance relationships
Recommend isinstance() for type comparison

Example:
class Parent:
    pass
class Child(Parent):
    pass
isinstance(Parent(), Parent)    # returns True
type(Parent()) == Parent        # returns True
isinstance(Child(), Parent)    # returns True
type(Child()) == Parent        # returns False

Reflection

Map strings to object or class attributes (often used with input).

# hasattr checks if object/class has method/attribute matching string
hasattr(obj, 'name')  # 'name' in obj.__dict__
getattr(obj, 'name')  # obj.__dict__['name']
setattr(obj, 'age', 18)  # obj.sex = 18
delattr(obj, 'name')
print(obj.__dict__)

Exception Handling

Exceptions are signals indicating errors. Unhandled exceptions terminate programs.

Exception information includes:

  • Traceback: exception tracking information
  • Exception type
  • Exception content

Handling exceptions prevents program crashes, enhancing robustness.

Classification:

  • Syntax errors: must be corrected before execution
  • Logic errors: preventable through if statements or try-except blocks
try:
    code.....
except NameError as e:
    # e contains error information
    # Execute when exception matches NameError
code.....
except NameError2:
    # Multiple branches
    code....

# Can be on single line
except (NameError, NameError2)

# Universal exception
Exception

# else: execute when no exceptions occur
try:
    .....
else:
    ....

# finally: execute regardless of exceptions

Custom exception types:

class CustomException(BaseException):
    def __str__(self):  # Print exception value
        return ""

# Raise exception
raise CustomException("Custom exception defined")

Assertion:

assert condition, raises exception if condition fails

Tags: python object-oriented-programming Classes Inheritance Polymorphism

Posted on Fri, 15 May 2026 09:09:57 +0000 by flyersman