Python Objects: A Comprehensive Deep Dive

In Python, everything is an object —from numbers and strings to functions and classes. This core principle underpins Python’s design as an object-oriented language, where objects serve as the fundamental building blocks of programs. Objects encapsulate data and behavior, enabling flexibility, reusability, and a consistent programming model. In this blog, we’ll explore what Python objects are, how they work, their internal structure, practical examples, and their significance in Python’s ecosystem.


What Are Python Objects?

link to this section

An object in Python is an instance of a type (or class) that combines data (attributes) and behavior (methods). Every value in Python—whether a built-in type like int or a custom class instance—is an object with a unique identity, type, and value.

Key Characteristics

  • Identity : A unique identifier (accessible via id()), distinguishing one object from another.
  • Type : Defines what the object is and what it can do (accessible via type()).
  • Value : The data the object holds, which may be mutable or immutable.

Example

x = 42 
print(id(x)) # Unique identity, e.g., 140712834927872 
print(type(x)) # Output: <class 'int'> 
print(x) # Value: 42

Objects Everywhere

  • Primitives: 5, "hello", True.
  • Containers: [1, 2, 3], (1, 2).
  • Functions: def my_func():.
  • Classes and instances: class MyClass:.

How Python Objects Work

link to this section

Object Creation

Objects are created from types or classes:

  • Built-in Types : Instantiated implicitly (e.g., x = 10 creates an int object).
  • Custom Classes : Defined with class and instantiated explicitly (e.g., obj = MyClass()).

Example

class Person: 
    def __init__(self, name): 
        self.name = name 
        
p = Person("Alice") # Creates a Person object 
print(p.name) # Output: Alice

Object Lifecycle

  1. Creation : Allocated in memory when instantiated.
  2. Usage : Accessed and manipulated via references (variables).
  3. Destruction : Garbage-collected when no longer referenced, handled by Python’s memory manager.

Internal Structure

In CPython, every object is a PyObject or derivative:

typedef struct {
    PyObject_HEAD  // Includes refcount and type pointer
} PyObject;
  • Reference Count : Tracks how many references point to the object (sys.getrefcount()).
  • Type Pointer : Links to the object’s type (e.g., PyInt_Type for integers).

Features of Python Objects

link to this section

1. Attributes

Objects store data as attributes:

  • Instance Attributes : Unique to each object.
  • Class Attributes : Shared across instances of a class.

Example

class Car:
    wheels = 4  # Class attribute
    
    def __init__(self, color):
        self.color = color  # Instance attribute

c1 = Car("Red")
c2 = Car("Blue")
print(c1.color, c1.wheels)  # Output: Red 4
print(c2.color, c2.wheels)  # Output: Blue 4

2. Methods

Functions bound to objects, accessed via dot notation:

class Dog: 
    def bark(self): 
        return "Woof!" 
        
d = Dog() 
print(d.bark()) # Output: Woof!

3. Mutability

  • Immutable : Cannot change value (e.g., int, str, tuple).
  • Mutable : Can change value (e.g., list, dict).

Example

s = "hello"  # Immutable
# s[0] = "H"  # TypeError
lst = [1, 2]  # Mutable
lst[0] = 10
print(lst)    # Output: [10, 2]

4. Identity and Equality

  • Identity : is checks if two references point to the same object.
  • Equality : == checks if values are equal.

Example

a = [1, 2]
b = a
c = [1, 2]
print(a is b)  # Output: True (same object)
print(a is c)  # Output: False (different objects)
print(a == c)  # Output: True (same value)

5. Special Methods

Objects can define behavior with dunder (double-underscore) methods:

  • __str__: String representation.
  • __add__: Addition behavior.

Example

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __str__(self):
        return f"({self.x}, {self.y})"
    
    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)

p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1)         # Output: (1, 2)
print(p1 + p2)    # Output: (4, 6)

Practical Examples

link to this section

Example 1: Custom Object

class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author
    
    def details(self):
        return f"{self.title} by {self.author}"

b = Book("Python 101", "Jane Doe")
print(b.details())  # Output: Python 101 by Jane Doe

Example 2: Mutable Object

class Basket:
    def __init__(self):
        self.items = []
    
    def add(self, item):
        self.items.append(item)

basket = Basket()
basket.add("Apple")
basket.add("Banana")
print(basket.items)  # Output: ['Apple', 'Banana']

Example 3: Built-in Objects

num = 42          # int object
text = "Hello"    # str object
lst = [1, 2, 3]   # list object
print(num + 8)    # Output: 50
print(text.upper())  # Output: HELLO
print(lst.pop())  # Output: 3

Example 4: Object with Special Methods

class Counter:
    def __init__(self, value=0):
        self.value = value
    
    def __eq__(self, other):
        return self.value == other.value
    
    def __repr__(self):
        return f"Counter({self.value})"

c1 = Counter(5)
c2 = Counter(5)
print(c1 == c2)  # Output: True
print(c1)        # Output: Counter(5)

Performance Implications

link to this section

Memory Usage

  • Overhead : Every object has a base memory cost (e.g., PyObject header).
  • Reference Counting : Python tracks references, adding slight overhead.

Example

import sys 
x = 42 
lst = [1, 2, 3] 
print(sys.getsizeof(x)) # Output: ~28 bytes (int) 
print(sys.getsizeof(lst)) # Output: ~64 bytes (list, plus contents)

Speed

  • Attribute Access : Dot notation (e.g., obj.attr) involves lookup, slower than local variables.
  • Method Calls : Include binding overhead (self).

Benchmarking

import time

class Test:
    def method(self):
        pass

obj = Test()
start = time.time()
for _ in range(1000000):
    obj.method()
print(time.time() - start)  # Slightly slower than plain function

Objects vs. Other Constructs

link to this section
  • Primitives : In Python, even int and float are objects, unlike C’s non-object primitives.
  • Functions : Functions are objects with attributes (e.g., __name__).
  • Modules : Modules are objects, containing other objects as attributes.

Example

def my_func(): 
    pass 
print(type(my_func)) # Output: <class 'function'> 
print(my_func.__name__) # Output: my_func

Practical Use Cases

link to this section
  1. Data Modeling :
    class User: 
        def __init__(self, id, name): 
            self.id = id 
            self.name = name
  2. State Management :
    class Game: 
        def __init__(self): 
            self.score = 0 
            
        def increment(self): 
            self.score += 1
  3. Custom Behavior :
    class Vector:
        def __init__(self, x, y):
            self.x = x
            self.y = y
        
        def __mul__(self, scalar):
            return Vector(self.x * scalar, self.y * scalar)
  4. Resource Handling :
    class FileHandler:
        def __init__(self, filename):
            self.file = open(filename, "w")
        
        def write(self, text):
            self.file.write(text)
        
        def __del__(self):
            self.file.close()

Edge Cases and Gotchas

link to this section

1. Shared Mutable Defaults

class Trap:
    def __init__(self, lst=[]):
        self.lst = lst
    
    def add(self, item):
        self.lst.append(item)

t1 = Trap()
t1.add(1)
t2 = Trap()
print(t2.lst)  # Output: [1] (shared default list)

2. Object Identity

a = 256
b = 256
print(a is b)  # Output: True (small integer caching)
a = 1000
b = 1000
print(a is b)  # Output: False (no caching for large ints)

3. Garbage Collection

class Resource:
    def __del__(self):
        print("Deleted")

r = Resource()
del r  # Output: Deleted (eventually, GC-dependent)

4. Attribute Overwrites

class Demo:
    def __init__(self):
        self.x = 1

d = Demo()
d.x = "string"  # Overwrites type, no enforcement
print(d.x)      # Output: string

Conclusion

link to this section

Python objects are the unifying fabric of the language, turning every value, function, and class into a cohesive entity with identity, type, and behavior. Their universality—spanning built-in types to custom instances—offers unparalleled flexibility, while features like attributes, methods, and special behaviors provide depth for modeling complex systems. Understanding objects in Python—how they’re created, managed, and interacted with—reveals the elegance of its object-oriented design, empowering you to craft expressive, efficient code.