Advanced Python Objects
Introduction
Python is an object-oriented programming language that allows the creation and manipulation
of objects. Advanced Python objects involve deeper concepts such as custom objects, special
methods, property decorators, class methods, static methods, and more. These concepts are
crucial for writing more efficient, readable, and maintainable code.
1. Custom Objects and Classes
Defining a Class
A class is a blueprint for creating objects (instances). Classes encapsulate data and functions
that operate on the data.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self):
return f"Hello, my name is {self.name} and I am {self.age} years
old."
Creating an Object
person1 = Person("Alice", 30)
print(person1.greet())
2. Special Methods (Magic Methods)
Special methods in Python start and end with double underscores (__). They are used to
emulate built-in behavior within user-defined classes.
Common Special Methods
__init__(self, ...) : Constructor, initializes the object's attributes.
__str__(self) : Returns a string representation of the object.
__repr__(self) : Returns an official string representation of the object.
__len__(self) : Defines behavior for the built-in len() function.
__getitem__(self, key) : Defines behavior for element access, e.g., obj[key].
__setitem__(self, key, value) : Defines behavior for setting elements, e.g.,
obj[key] = value.
Interfacing with Mathematical Operators
The following special methods control how an object interacts with + , * , ** , and other
mathematical operators.
Method Signature Explanation
Add __add__(self, other) x + y invokes x.__add__(y)
Subtract __sub__(self, other) x - y invokes x.__sub__(y)
Multiply __mul__(self, other) x * y invokes x.__mul__(y)
__truediv__(self, oth x / y invokes x.__truediv__(
Divide
er) y)
Power __pow__(self, other) x ** y invokes x.__pow__(y)
Example
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __len__(self):
return int((self.x**2 + self.y**2) ** 0.5)
v1 = Vector(3, 4)
v2 = Vector(1, 2)
v3 = v1 + v2
print(v3) # Output: Vector(4, 6)
print(len(v1)) # Output: 5
3. Property Decorators
Property decorators provide a way to manage the access to instance attributes. They are
useful for adding getter, setter, and deleter functionality.
Using Property Decorators
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Radius cannot be negative")
self._radius = value
@property
def area(self):
return 3.14159 * self._radius ** 2
c = Circle(5)
print(c.radius) # Output: 5
print(c.area) # Output: 78.53975
c.radius = 3
print(c.area) # Output: 28.27431
4. Class Methods and Static Methods
Class methods and static methods are different ways to define methods that are not bound to
instance objects.
Class Methods
Class methods are bound to the class and not the instance of the class. They can modify class
state that applies across all instances. A class method receives the class as an implicit first
argument, just like an instance method receives the instance
class MyClass:
class_variable = 0
@classmethod
def increment_class_variable(cls):
cls.class_variable += 1
MyClass.increment_class_variable()
print(MyClass.class_variable) # Output: 1
Static Methods
Static methods are bound to the class and not the instance of the class. They do not modify
object or class state.
class MathOperations:
@staticmethod
def add(x, y):
return x + y
print(MathOperations.add(3, 4)) # Output: 7
5. Inheritance and Polymorphism
Inheritance allows one class to inherit attributes and methods from another class.
Polymorphism allows methods to do different things based on the object it is acting upon.
Inheritance
Syntax:
class DerivedClass(BaseClass):
# Class definition
Single Inheritance
class parent: # parent class
def func1(self):
print("Hello Parent")
class child(parent):
# child class
def func2(self): # we include the parent class
print("Hello Child") # as an argument in the child
# class
# Driver Code
test = child() # object created
test.func1() # parent method called via child object
test.func2()
Multiple Inheritance
class parent1: # first parent class
def func1(self):
print ("Hello Parent1")
class parent2: # second parent class
def func2(self):
print ("Hello Parent2")
class parent3: # third parent class
def func2(self): # the function name is same as
parent2
print ("Hello Parent3")
class child (parent1, parent2, parent3): # child class
def func3(self): # we include the parent classes
print ("Hello Child") # as an argument comma separated
# Driver Code
test = child() # object created
test.func1() # parent1 method called via child
test.func2() # parent2 method called via child instead of
parent3
test.func3(
Multilevel Inheritance
class grandparent: # first level
def func1(self):
print("Hello Grandparent")
class parent(grandparent): # second level
def func2(self):
print("Hello Parent")
class child(parent): # third level
def func3(self):
print("Hello Child")
# Driver Code
test = child() # object created
test.func1() # 3rd level calls 1st level
test.func2() # 3rd level calls 2nd level
test.func3()
Polymorphism
Polymorphism with class methods
class Liquid:
def __init__(self, name, formula):
self.name = name
self.formula = formula
def info(self):
print(f"I am Liquid Form. I am {self.name}. and my formula is {self.formula}")
def property(self):
print("I am clear and light form")
class Solid:
def __init__(self, name, formula):
self.name = name
self.formula = formula
def info(self):
print(f"I am Solid Form. I am {self.name}. and my formula is {self.formula}")
def property(self):
print("I can be transparent or clear or completely opaque")
liquid_1 = Liquid("Water", "H20")
solid_1 = Solid("Ice", "H20")
for material in (liquid_1,solid_1):
material.info()
material.property()
And the output is:
I am Liquid Form. I am Water. and my formula is H20
I am clear and light form
I am Liquid Form. I am Ice. and my formula is H20
I can be transparent or clear or complete opaque
Here, we create two classes: Liquid and Solid. Here, we define info() and property() for both
of them. These functions work differently depending on which class object you call.
Polymorphism with Inheritance
In Object-oriented programming, class inheritance lets you inherit the methods
and attributes of a parent class to a child class. The parent class is also known as base class,
whereas the derived subclass is known as derived or child class.
Child classes uses method overriding polymorphism to implement inheritance.
class F1():
def run(self):
print("Win")
class F2(F1):
def run(self):
print("2nd place - F2 cars are slower than F1 cars")
class F3(F1):
def run(self):
print("3rd place - F3 cars are slower than F2 and F1 cars")
obj_F1 = F1()
obj_F2 = F2()
obj_F3 = F3()
obj_F1.run()
obj_F2.run()
obj_F3.run()
And the output is:
Win
2nd place - F2 cars are slower than F1 cars
3rd place - F3 cars are slower than F2 and F1 cars
Here, we first defined our F1 class and then passed it as a parameter in the F2 class. Each time
we called the run() function on objects of F1, F2 and F3 class, the inheritance and method
overriding took place.
6. Encapsulation
Encapsulation restricts access to methods and variables to prevent data from being modified
directly. This is achieved using private and protected access modifiers.
Public, Private and Protected Attributes
The members of a class that are declared public are easily accessible from any part of the
program. All data members and member functions of a class are public by default.
The members of a class that are declared protected are only accessible to a class derived
from it. Data members of a class are declared protected by adding a single underscore ‘_’
symbol before the data member of that class.
The members of a class that are declared private are accessible within the class only,
private access modifier is the most secure access modifier. Data members of a class are
declared private by adding a double underscore ‘__’ symbol before the data member of that
class.
class Super:
# public data member
var1 = None
# protected data member
_var2 = None
# private data member
__var3 = None
# constructor
def __init__(self, var1, var2, var3):
self.var1 = var1
self._var2 = var2
self.__var3 = var3
# public member function
def displayPublicMembers(self):
# accessing public data members
print("Public Data Member: ", self.var1)
# protected member function
def _displayProtectedMembers(self):
# accessing protected data members
print("Protected Data Member: ", self._var2)
# private member function
def __displayPrivateMembers(self):
# accessing private data members
print("Private Data Member: ", self.__var3)
# public member function
def accessPrivateMembers(self):
# accessing private member function
self.__displayPrivateMembers()
# derived class
class Sub(Super):
# constructor
def __init__(self, var1, var2, var3):
Super.__init__(self, var1, var2, var3)
# public member function
def accessProtectedMembers(self):
# accessing protected member functions of super class
self._displayProtectedMembers()
# creating objects of the derived class
obj = Sub("Geeks", 4, "Geeks !")
# calling public member functions of the class
obj.displayPublicMembers()
obj.accessProtectedMembers()
obj.accessPrivateMembers()
# Object can access protected member
print("Object is accessing protected member:", obj._var2)
# object can not access private member, so it will generate Attribute
error
# print(obj.__var3)