No history yet

Class Anatomy

From Scripts to Blueprints

So far, you've likely worked with data using Python's built-in types like strings, lists, and dictionaries. This is great for many tasks, but as programs grow, you need a way to create your own structured types. This is where classes come in. A class is a blueprint for creating objects. Think of it like an architect's plan for a house: the plan itself isn't a house, but it defines all the properties and features a house will have. You can then build multiple houses from that single blueprint, each with its own unique address and occupants.

A class in Python is a blueprint for creating objects.

To define a class, you use the class keyword, followed by the name of the class (by convention, in CamelCase), and a colon. An empty class, while valid, doesn't do much.

# This is a valid, but not very useful, class definition.
class SmartDevice:
    pass

To make our blueprint useful, we need to define what happens when a new object is created from it. We need a way to initialize its state.

The Constructor Method

When you create an object from a class, Python automatically calls a special method called __init__. This method is the constructor; its job is to set up the initial state of the new object. Methods like __init__, with double underscores at the beginning and end, are often called dunder methods (short for double underscore). They signal to Python that this method has a special, built-in purpose.

The first parameter of __init__ (and any method inside a class) is always a special reference to the instance being created. By convention, this parameter is named self. It's how an object can access its own data and behavior. When you call a method on an object, like my_device.turn_on(), Python passes that object, my_device, as the first argument, self, behind the scenes.

class SmartDevice:
    def __init__(self, brand, model, ip_address):
        # Assigning arguments to instance attributes
        self.brand = brand
        self.model = model
        self.ip_address = ip_address
        self.is_on = False  # Set a default state

# Creating two distinct objects (instances) from the class blueprint
phone = SmartDevice("Pixel", "8 Pro", "192.168.1.101")
speaker = SmartDevice("Sonos", "One", "192.168.1.102")

print(phone.brand)  # Output: Pixel
print(speaker.brand) # Output: Sonos

Inside __init__, we use self to create and assign values to instance attributes. self.brand = brand means "take the brand value that was passed in, and store it in an attribute named brand that belongs to this specific object." Each object created from the SmartDevice class gets its own separate set of these attributes. The phone object has its own brand, and the speaker object has its own, different brand. They don't interfere with each other. This is called namespace isolation; the internal data of one object is kept separate from all others.

Class vs. Instance Attributes

Attributes attached to self belong to a specific instance. But you can also define attributes that are shared across all instances of a class. These are called class attributes.

Class attributes are defined directly inside the class definition, but outside of any method. They are useful for storing data that is constant or relevant to the class as a whole, like a configuration setting or a counter. All objects created from the class can access this attribute, and they'll all see the same value.

class SmartDevice:
    # This is a class attribute
    # It's shared by all instances of SmartDevice
    protocol_version = "4.1a"

    def __init__(self, brand, model, ip_address):
        # These are instance attributes
        self.brand = brand
        self.model = model
        self.ip_address = ip_address
        self.is_on = False

phone = SmartDevice("Pixel", "8 Pro", "192.168.1.101")
speaker = SmartDevice("Sonos", "One", "192.168.1.102")

# Accessing the class attribute from the instances
print(phone.protocol_version)    # Output: 4.1a
print(speaker.protocol_version)  # Output: 4.1a

# You can also access it directly from the class
print(SmartDevice.protocol_version) # Output: 4.1a

Here, protocol_version is the same for every smart device we create. If you were to change it on the class itself (SmartDevice.protocol_version = "5.0"), all instances would see the new value. This distinction is crucial. Use instance attributes for data unique to each object (like an IP address) and class attributes for data shared by all objects of that type.

By defining classes, you create powerful, reusable, and self-contained components. This approach keeps your code organized, reduces complexity, and prevents conflicts in large applications by isolating data within each object's unique namespace.

Quiz Questions 1/5

In object-oriented programming with Python, what is the primary role of a class?

Quiz Questions 2/5

What is the special __init__ method in a Python class primarily used for?