← Previous

Lesson 21

Objects

~35 min

1. Objects in Python

In Python, there is a famous phrase: "Everything is an object." Integers, strings, lists, functions, modules, and custom class instances are all objects under the hood. Understanding how Python handles objects in memory—and how object identity, mutability, and attributes work—is key to writing bug-free, efficient code.

Learning Objectives

  • Understand what an object is in memory and why "everything is an object" in Python.
  • Master the 3 pillars of any Python object: Identity, Type, and Value (State).
  • Differentiate between Value Equality (==) and Identity Equality (is).
  • Explore object mutability, pass-by-object-reference behavior, and memory aliasing.
  • Inspect object capabilities dynamically using id(), type(), isinstance(), and dir().

2. What is an Object?

An Object is a container in system memory that holds data (state/attributes) and code (methods/behavior). When you assign a variable in Python, you are not placing data directly inside a box labeled with the variable's name. Instead, Python creates an object in memory and attaches a reference name (the variable) that points to that object's memory address.

Think of an object like a house on a street, and a variable like a sticky note with the house's address written on it. You can stick multiple address notes pointing to the same physical house!

Python code example:

# Standard data types are objects
x = 42              # 'x' references an Integer Object with value 42
msg = "Hello"       # 'msg' references a String Object
numbers = [1, 2, 3] # 'numbers' references a List Object

# Checking types using the built-in type() function
print(type(x))        # Output: <class 'int'>
print(type(numbers))  # Output: <class 'list'>

3. The Anatomy of an Object

Every single object created in Python possesses three distinct characteristics:

Property What It Represents How to Inspect It Can It Change?
Identity The unique memory address where the object lives. id(obj) or a is b No (Constant for object lifetime)
Type The class blueprint that defines what the object can do. type(obj) or isinstance() No (Cannot change type after creation)
Value (State) The actual data payload held inside the object. print(obj) or attribute access Yes (If object is mutable)

4. Equality: Value (==) vs Identity (is)

One of the most essential concepts when dealing with objects is distinguishing between checking if two objects hold the same contents versus checking if they are the exact same memory object.

  • == (Value Equality): Asks "Do these two objects contain equal data?"
  • is (Identity Equality): Asks "Do these two variables point to the exact same physical memory location?" (i.e., id(a) == id(b)).

Python code example:

list_a = [10, 20, 30]
list_b = [10, 20, 30]
list_c = list_a  # Copying reference! Both point to same object

# Comparing Contents (Value Equality)
print(list_a == list_b)  # Output: True (Identical numbers inside)

# Comparing Memory Locations (Identity Equality)
print(list_a is list_b)  # Output: False (Two separate list objects in RAM!)
print(list_a is list_c)  # Output: True (Both point to the exact same memory address!)

# Inspecting actual memory address IDs
print(id(list_a))  # e.g., 1402394820392
print(id(list_b))  # e.g., 1402394820544 (Different ID!)
print(id(list_c))  # e.g., 1402394820392 (Same ID as list_a!)

5. Object Mutability & References

When you pass an object into a function, Python uses a mechanism called Pass-by-Object-Reference. If the object passed is mutable (like a list, dictionary, or custom class instance), modifications made inside a function directly alter the original object outside!

Python code example:

class Player:
    def __init__(self, username, score=0):
        self.username = username
        self.score = score

def award_bonus_points(player_obj, points):
    # Mutates the object state directly in memory
    player_obj.score += points

p1 = Player("Gamer123", score=100)
award_bonus_points(p1, 50)

# The original object state changed!
print(f"{p1.username} Score: {p1.score}")  # Output: Gamer123 Score: 150

6. Dynamic Attribute Inspection & Manipulation

Because Python objects are dynamic, you can inspect their internal methods or dynamically read, set, and delete attributes at runtime using built-in functions:

  • dir(obj): Returns a list of all attributes and methods available on the object.
  • hasattr(obj, 'attr_name'): Checks if an attribute exists on the object.
  • getattr(obj, 'attr_name', default): Dynamically reads an attribute value.
  • setattr(obj, 'attr_name', value): Dynamically sets or creates a new attribute on an object.
class Car:
    def __init__(self, brand):
        self.brand = brand

my_car = Car("Toyota")

# Dynamically checking and setting attributes
if hasattr(my_car, "brand"):
    print(f"Brand is: {getattr(my_car, 'brand')}")

# Dynamically adding a new attribute to this specific instance!
setattr(my_car, "color", "Red")
print(my_car.color)  # Output: Red

7. Common Beginner Mistakes

⚠️ Watch out for these mistakes:

  • Using is for Value Comparison: Writing if name is "Alice": can cause mysterious bugs because Python may optimize small strings/integers in memory while treating larger ones as separate objects. Always use == for content checks!
  • Accidental Object Aliasing: Writing list_b = list_a does NOT duplicate the list. Modifying list_b.append(99) will unintentionally modify list_a as well! Use list_b = list_a.copy() instead.
  • Attempting to Set Attributes on Immutable Built-in Types: Trying to dynamically attach an attribute to built-in immutable types like numbers or strings (e.g., x = 5; x.name = "five") raises an AttributeError.
  • Forgetting that None is an Object: In Python, None is a singleton object of class NoneType. The standard way to check for None is using identity equality: if value is None:.

8. Code Examples

Here are essential patterns for inspecting and cloning objects safely in Python:

import copy

# 1. Shallow Copy vs. Deep Copying Objects
original_data = {"user": "Alice", "skills": ["Python", "SQL"]}

# Shallow copy shares nested objects (lists/dicts inside)
shallow_copy = original_data.copy()

# Deep copy duplicates the main object AND all nested objects recursively
deep_copy = copy.deepcopy(original_data)

original_data["skills"].append("Docker")

print(shallow_copy["skills"]) # Output: ['Python', 'SQL', 'Docker'] (Mutated!)
print(deep_copy["skills"])    # Output: ['Python', 'SQL'] (Preserved original!)

# 2. Verifying object types cleanly with isinstance()
def process_data(data_obj):
    if isinstance(data_obj, list):
        print(f"Processing list of {len(data_obj)} items.")
    elif isinstance(data_obj, dict):
        print(f"Processing dictionary with keys: {list(data_obj.keys())}")

process_data([1, 2, 3])
process_data({"a": 1, "b": 2})
Interactive Practice

Mini Practice: Object Inspector

Define a custom class named Gadget with attributes name and price. Create two separate instances with identical values. Print whether their values are equal (==) and whether their memory identities are equal (is).

0% complete
Check when done
Quiz

Test Your Understanding

1. What does the `is` operator evaluate in Python?

2. What function returns the unique integer representing an object's memory address in Python?

3. What happens when you pass a mutable object (like a list or custom object) into a function and modify it inside?

4. Which built-in function allows you to safely check if an object has a specific attribute before accessing it?

9. Challenge Project

Object Memory & Clone Analyzer: Write a Python program that creates a custom BankAccount object with owner and balance attributes. Implement three operations:

  1. Create an alias reference (acc_alias = acc1) and modify the balance. Print both variables to observe shared memory!
  2. Use the copy module to create a shallow copy, modify the copy's balance, and verify that the original object remains untouched.
  3. Use id() and is statements to output a clear memory inspection report comparing all three instances!

10. Key Takeaways

  • In Python, everything is an object (integers, strings, lists, functions, classes).
  • Every object has three core pillars: an Identity (memory address), a Type, and a Value.
  • Use == to test content equality and is to test memory identity.
  • Variables store references to objects in memory, not the raw data values themselves.
  • Dynamically inspect object capabilities using id(), isinstance(), dir(), and hasattr().

🚀 Progression

You've mastered Objects and Memory Concepts! Next up, we will explore code reusability and class relationships with **Inheritance & Polymorphism in Python**!