Mastering Object-Oriented Python: Attribute Protection, Inheritance, and More

Protecting Object Attributes

There are two ways to modify an object's attributes:

  • Direct assignment: object.attribute = value
  • Indirect modification via a method: object.method()

To ensure attribute safety and prevent arbitrary changes, a common approach is:

  • Make attributes private
  • Provide public methods that enforce validation logic
class Person:
    def __init__(self, name):
        self.__name = name

    def get_name(self):
        return self.__name

    def set_name(self, new_name):
        if len(new_name) >= 5:
            self.__name = new_name
        else:
            print("Error: name must be at least 5 characters long")

# This will raise an AttributeError
# xiaoming = Person("dongGe")
# print(xiaoming.__name)

xiaoming = Person("dongGe")
xiaoming.set_name("wanger")
print(xiaoming.get_name())

xiaoming.set_name("lisi")
print(xiaoming.get_name())

Key Points

  • Python does not have explicit public or private keywords like C++. Instead, name mangling is used: a double underscore prefix (__) makes an attribute (or method) private.
  • Private members are not directly accessible from outside the class; they are accessed through public methods.

The __del__ Method

When an object is created, Python cals the __init__ method. When an object is deleted, Python calls the __del__ method (the destructor).

import time

class Animal:
    def __init__(self, name):
        print('__init__ called')
        self.__name = name

    def __del__(self):
        print("__del__ called")
        print(f"{self.__name} is about to be destroyed...")

# Create and delete an object
dog = Animal("Husky")
del dog

# Reference counting example
cat = Animal("Persian")
cat2 = cat
cat3 = cat

print("--- deleting cat")
del cat
print("--- deleting cat2")
del cat2
print("--- deleting cat3")
del cat3

print("Program ending in 2 seconds")
time.sleep(2)

Observation: The destructor runs only when the reference count drops to zero. In the second part, cat, cat2, and cat3 all refer to the same object; the __del__ method is called only after the last reference is deleted.

Summary on Reference Counting

  • Every variable that holds a reference to an object increments that object's reference count.
  • Using del reduces the reference count by one, not necessarily removing the object immediately.
  • The object is truly deleted when its reference count becomes zero.

Inheritance and Single Inheritance

Concept

Inheritance models an "is-a" relationship. For example, a Dog is an Animal, and a PersianCat is a Cat.

Example

class Cat:
    def __init__(self, name, color="white"):
        self.name = name
        self.color = color

    def run(self):
        print(f"{self.name} is running")

class PersianCat(Cat):
    def rename(self, new_name):
        self.name = new_name

    def eat(self):
        print(f"{self.name} is eating")

bs = PersianCat("Indian Cat")
print(f"Name: {bs.name}")
print(f"Color: {bs.color}")
bs.eat()
bs.rename("Persian")
bs.run()

Even though the subclass does not define __init__, it inherits the parant's constructor.

Rules

  • The parent class is specified in parentheses during class definition.
  • The subclass inherits all public attributes and methods from the parent.

Important Note on Private Members

Private attributes and methods (double underscore) are not inherited by subclasses. They are accessible only within the defining class.

class Animal:
    def __init__(self, name='animal', color='white'):
        self.__name = name
        self.color = color

    def __secret(self):
        print(self.__name, self.color)

    def reveal(self):
        print(self.__name, self.color)

class Dog(Animal):
    def dog_test(self):
        # print(self.__name)   # Not accessible
        print(self.color)
        # self.__secret()      # Not accessible
        self.reveal()

a = Animal()
print(a.color)
# print(a.__name)  # Error
a.reveal()

d = Dog(name="spot", color="brown")
d.dog_test()

Key takeaway: Private members (both attributes and methods) are name-mangled and cannot be accessed from subclasses or outside the class.

Multiple Inheritance

Python supports multiple inheritance, where a class can inherit from more than one parent.

class A:
    def print_a(self):
        print('----A----')

class B:
    def print_b(self):
        print('----B----')

class C(A, B):
    def print_c(self):
        print('----C----')

obj = C()
obj.print_a()
obj.print_b()

Method Resolution Order (MRO)

When two parents have a method with the same name, MRO determines which one is called.

class Base:
    def test(self):
        print('----Base test----')

class A(Base):
    def test(self):
        print('----A test----')

class B(Base):
    def test(self):
        print('----B test----')

class C(A, B):
    pass

obj = C()
obj.test()  # Calls A.test, because A appears before B in inheritance
print(C.__mro__)  # Shows search order

Overriding and Calling Parent Methods

Overriding

A subclass can define a method with the same name as a parent method, which overrides the parent's implementation.

class Cat:
    def greet(self):
        print("halou-----1")

class PersianCat(Cat):
    def greet(self):
        print("halou-----2")

p = PersianCat()
p.greet()

Calling Parent Methods

If you want to extend (not completely replace) the parent's method, you can explicitly call it.

class Cat:
    def __init__(self, name):
        self.name = name
        self.color = 'yellow'

class PersianCat(Cat):
    def __init__(self, name):
        # Option 1: Python 2 style (not recommended)
        # Cat.__init__(self, name)
        # Option 2: explicit super (Python 2 and 3)
        # super(PersianCat, self).__init__(name)
        # Option 3: recommended Python 3 style
        super().__init__(name)

    def get_name(self):
        return self.name

bosi = PersianCat('xiaohua')
print(bosi.name)
print(bosi.color)

Polymorphism and Duck Typing

Polymorphism in languages like Java requires explicit type hierarchies. Python embraces "duck typing" — if an object has the required method, it can be used regardless of its type.

class F1:
    def show(self):
        print('F1.show')

class S1(F1):
    def show(self):
        print('S1.show')

class S2(F1):
    def show(self):
        print('S2.show')

def display(obj):
    obj.show()

s1 = S1()
display(s1)  # Output: S1.show

s2 = S2()
display(s2)  # Output: S2.show

The function display can accept any object that has a show method.

Class Attributes vs Instance Attributes

  • Class attributes belong to the class itself and are shared by all instances.
  • Instance attributes are unique to each instance.
class People:
    country = 'china'  # class attribute

# Access via class or instance
print(People.country)
p = People()
print(p.country)

# Modifying via instance creates a new instance attribute
p.country = 'japan'
print(p.country)   # instance attribute shadows class attribute
print(People.country)  # class attribute unchanged

del p.country
print(p.country)  # now refers to class attribute again

Class Methods and Static Methods

Class Methods

A class method takes cls as the first parameter and can modify class state.

class People:
    country = 'china'

    @classmethod
    def get_country(cls):
        return cls.country

    @classmethod
    def set_country(cls, country):
        cls.country = country

p = People()
print(p.get_country())          # instance call
print(People.get_country())     # class call

p.set_country('japan')
print(p.get_country())          # now returns 'japan'
print(People.get_country())     # also 'japan'

Static Methods

A static method does not receive any special first parameter. It behaves like a regular function but belongs to the class's namespace.

class People:
    country = 'china'

    @staticmethod
    def get_country():
        return People.country

print(People.get_country())

Summary

  • Instance methods have self and can access both instance and class attributse.
  • Class methods have cls and can only access class attributes (they are useful for factory methods or modifying class state).
  • Static methods do not have self or cls; they are independent functions grouped under the class.

Tags: python Object-Oriented Programming Inheritance Private Attributes Methods

Posted on Thu, 23 Jul 2026 16:00:27 +0000 by JoeZar