backend / python oop / 03_objects_in_python.md

Objects in Python

4 interview angles 6 min read source

Objects in Python

An object is an instance of a class. It’s a concrete entity that has state (attributes) and behavior (methods). In Python, everything is an object - from simple data types like integers and strings to complex user-defined classes.


Creating Objects

Objects are created by calling a class (instantiation):

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

    def greet(self):
        return f"Hello, I'm {self.name}"

# Creating objects (instances)
person1 = Person("Alice", 30)
person2 = Person("Bob", 25)

print(person1.greet())  # Hello, I'm Alice
print(person2.greet())  # Hello, I'm Bob

Object Identity and References

Every object has a unique identity and can be referenced:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

# Creating objects
p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = p1  # p3 references the same object as p1

print(id(p1))  # Unique identity (memory address)
print(id(p2))  # Different identity
print(id(p3))  # Same as p1's identity

print(p1 is p2)  # False (different objects)
print(p1 is p3)  # True (same object)
print(p1 == p2)  # False (unless __eq__ is defined)

Object Attributes

Instance Attributes

  • Belong to specific instances
  • Stored in __dict__
class Car:
    def __init__(self, brand, model):
        self.brand = brand      # Instance attribute
        self.model = model      # Instance attribute
        self.speed = 0          # Instance attribute

car1 = Car("Toyota", "Camry")
car2 = Car("Honda", "Civic")

print(car1.brand)  # Toyota
print(car2.brand)  # Honda

# Adding attributes dynamically
car1.color = "Red"
print(car1.color)  # Red

# Viewing all attributes
print(car1.__dict__)  # {'brand': 'Toyota', 'model': 'Camry', 'speed': 0, 'color': 'Red'}

Class Attributes

  • Shared among all instances
  • Accessed through class or instance
class Student:
    school = "Python University"  # Class attribute

    def __init__(self, name, grade):
        self.name = name    # Instance attribute
        self.grade = grade  # Instance attribute

student1 = Student("Alice", "A")
student2 = Student("Bob", "B")

print(student1.school)  # Python University
print(student2.school)  # Python University
print(Student.school)   # Python University

# Modifying class attribute affects all instances
Student.school = "New Python University"
print(student1.school)  # New Python University
print(student2.school)  # New Python University

Object Methods

Instance Methods

  • Operate on instance data
  • Take self as first parameter
class BankAccount:
    def __init__(self, balance):
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount
        return f"Deposited ${amount}. Balance: ${self.balance}"

    def withdraw(self, amount):
        if self.balance >= amount:
            self.balance -= amount
            return f"Withdrew ${amount}. Balance: ${self.balance}"
        return "Insufficient funds"

    def get_balance(self):
        return self.balance

account = BankAccount(1000)
print(account.deposit(500))   # Deposited $500. Balance: $1500
print(account.withdraw(200))  # Withdrew $200. Balance: $1300
print(account.get_balance())  # 1300

Method Binding

  • Methods are bound to instances
  • self is automatically passed
class Calculator:
    def add(self, x, y):
        return x + y

    def multiply(self, x, y):
        return x * y

calc = Calculator()

# These are equivalent:
result1 = calc.add(5, 3)
result2 = Calculator.add(calc, 5, 3)

print(result1)  # 8
print(result2)  # 8

Object Introspection

Python provides tools to examine objects at runtime:

class Example:
    class_var = "I'm a class variable"

    def __init__(self, value):
        self.instance_var = value

    def method(self):
        return "I'm a method"

obj = Example("test")

# Type checking
print(type(obj))  # <class '__main__.Example'>
print(isinstance(obj, Example))  # True

# Attribute inspection
print(hasattr(obj, 'instance_var'))  # True
print(hasattr(obj, 'nonexistent'))   # False

# Getting attributes
print(getattr(obj, 'instance_var'))  # test
print(getattr(obj, 'nonexistent', 'default'))  # default

# Setting attributes
setattr(obj, 'new_attr', 'new_value')
print(obj.new_attr)  # new_value

# Listing attributes
print(dir(obj))  # List all attributes and methods
print(obj.__dict__)  # Dictionary of instance attributes

Object Lifecycle

Creation

class LifecycleExample:
    def __new__(cls, *args, **kwargs):
        print("1. __new__ called - creating instance")
        return super().__new__(cls)

    def __init__(self, value):
        print("2. __init__ called - initializing instance")
        self.value = value

    def __del__(self):
        print("3. __del__ called - destroying instance")

obj = LifecycleExample("test")
# Output:
# 1. __new__ called - creating instance
# 2. __init__ called - initializing instance

del obj
# Output: 3. __del__ called - destroying instance

Garbage Collection

  • Python automatically manages memory
  • Objects are destroyed when no references remain
import gc

class MemoryExample:
    def __init__(self, name):
        self.name = name

    def __del__(self):
        print(f"Destroying {self.name}")

# Creating and destroying objects
obj1 = MemoryExample("Object 1")
obj2 = MemoryExample("Object 2")

# Removing references
del obj1
del obj2

# Force garbage collection
gc.collect()

Object Relationships

Composition (Has-A)

class Engine:
    def __init__(self, horsepower):
        self.horsepower = horsepower

    def start(self):
        return "Engine started"

class Car:
    def __init__(self, brand, engine_hp):
        self.brand = brand
        self.engine = Engine(engine_hp)  # Composition

    def start_car(self):
        return f"{self.brand} car: {self.engine.start()}"

car = Car("Toyota", 200)
print(car.start_car())  # Toyota car: Engine started

Aggregation (Uses-A)

class Student:
    def __init__(self, name):
        self.name = name
        self.courses = []  # Aggregation

    def enroll(self, course):
        self.courses.append(course)

    def get_courses(self):
        return [course.name for course in self.courses]

class Course:
    def __init__(self, name):
        self.name = name

student = Student("Alice")
course1 = Course("Python Programming")
course2 = Course("Data Structures")

student.enroll(course1)
student.enroll(course2)
print(student.get_courses())  # ['Python Programming', 'Data Structures']

Object Comparison

Identity vs Equality

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        if not isinstance(other, Point):
            return False
        return self.x == other.x and self.y == other.y

    def __hash__(self):
        return hash((self.x, self.y))

p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = p1

print(p1 is p2)   # False (different objects)
print(p1 is p3)   # True (same object)
print(p1 == p2)   # True (same values)
print(p1 == p3)   # True (same object)

Object Serialization

Pickle (Python’s built-in serialization)

import pickle

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

    def __str__(self):
        return f"Person(name={self.name}, age={self.age})"

# Creating object
person = Person("Alice", 30)

# Serializing to file
with open('person.pkl', 'wb') as f:
    pickle.dump(person, f)

# Deserializing from file
with open('person.pkl', 'rb') as f:
    loaded_person = pickle.load(f)

print(loaded_person)  # Person(name=Alice, age=30)

Summary Table

Concept Description Example
Object Creation obj = ClassName() person = Person("Alice", 30)
Object Identity id(obj) id(person)
Object Reference obj1 is obj2 person1 is person2
Instance Attributes obj.attribute person.name
Class Attributes ClassName.attribute Person.species
Method Call obj.method() person.greet()
Attribute Check hasattr(obj, 'attr') hasattr(person, 'name')
Object Destruction del obj del person

Key Interview Points

  1. Everything in Python is an object - including integers, strings, functions
  2. Objects have identity, type, and value
  3. self refers to the instance in methods
  4. Objects are created by calling classes
  5. Instance attributes belong to specific objects
  6. Class attributes are shared among all instances
  7. Object lifecycle includes creation, usage, and destruction
  8. Garbage collection automatically manages memory
  9. Object relationships include composition and aggregation
  10. Object comparison can be identity-based (is) or value-based (==)

Understanding objects is crucial for Python programming and object-oriented design!

Interview angle

  • is versus ==?”is compares identity (same object in memory), == compares value via __eq__. Use is only for singletons: None, True, False. Small-int and string interning makes is appear to work on values, which is exactly why it produces bugs that pass in testing.
  • “Are Python arguments passed by value or reference?” — neither exactly; it’s pass-by-object-reference. The function receives a reference to the same object, so mutating a list argument is visible to the caller, but rebinding the name inside the function is not.
  • “What does id() tell you?” — the object’s identity for its lifetime. It can be reused after garbage collection, so comparing stored id() values across time is unsound.
  • “How does attribute lookup work?” — instance __dict__, then the class and its MRO, with data descriptors taking precedence over the instance dict, and __getattr__ as the fallback when everything else fails. See 08_descriptors_in_python.md.