Python object attributes are named values attached to individual instances or classes of a class. They store data specific to each object or shared across all instances.
When you create an object from a class, you can assign and access attributes using dot notation. For example, if you have a Car class, an instance called my_car can have attributes like my_car.color = "red" or my_car.mileage = 15000.
These attributes exist independently for each object. Two instances of the same class can have different values for the same attribute. This behavior enables flexible, reusable code.
Attributes are stored in an object's namespace, which is a dictionary-like structure accessible via the __dict__ attribute. You can inspect an object's current attributes by printing object.__dict__.
When you access an attribute using dot notation, Python checks the instance namespace first. If it doesn't find the attribute there, it looks in the class's namespace. If still not found, it raises an AttributeError.
For example:
class Dog:
species = "Canis lupus familiaris"
def __init__(self, name):
self.name = name
fido = Dog("Fido")
print(fido.__dict__) # {'name': 'Fido'}
print(fido.species) # Canis lupus familiaris
This shows that name is an instance attribute stored in fido.__dict__, while species is a class attribute found in the class definition.
There are two main types: instance attributes and class attributes. Instance attributes belong to a specific object. Class attributes are shared among all instances of the class.
| Type | Scope | Declaration Location | Example |
|---|---|---|---|
| Instance attribute | Per-instance | In __init__ method |
self.age = 5 |
| Class attribute | Shared across all instances | Inside class body, outside methods | species = "Homo sapiens" |
Class attributes are useful for constants or default values. Instance attributes hold unique data, such as a person's name or a bank account balance.
A common mistake is modifying a mutable class attribute (like a list or dictionary) through an instance. Because the attribute is shared, changes affect all instances.
Example:
class Student:
grades = [] # Shared list across all instances
def __init__(self, name):
self.name = name
alice = Student("Alice")
bob = Student("Bob")
alice.grades.append(95)
print(bob.grades) # [95] - unexpected! Both share the same list.
To fix this, initialize mutable defaults inside __init__:
class Student:
def __init__(self, name):
self.name = name
self.grades = [] # Now unique per instance
Many developers confuse class attributes with instance attributes. A class attribute appears in every instance's __dict__ only if accessed. It doesn't appear in instance.__dict__ unless explicitly set.
For example:
class Circle:
pi = 3.14159
c = Circle()
print(c.pi) # 3.14159 - works
print(c.__dict__) # {} - no entry for pi
But if you assign c.pi = 3.14, Python adds it to c.__dict__, hiding the class value. This creates a shadowed attribute.
Another confusion: treating attributes as static variables. Unlike languages like Java, Python doesn't require static keywords. Any variable defined at the class level is a class attribute, accessible via dot notation on both class and instance.
As noted in the official Python documentation, "Instance variables are for data unique to each instance and class variables are for attributes and methods shared by all instances of the class." (Python.org: Classes)
Consider a User class for a web app. Each user has a unique username and email, but all users share the same default role: "member".
Code:
class User:
default_role = "member"
active_count = 0
def __init__(self, username, email):
self.username = username
self.email = email
self.role = User.default_role
User.active_count += 1
# Create users
alice = User("alice123", "[email protected]")
bob = User("bob456", "[email protected]")
print(alice.role) # member
print(User.active_count) # 2
print(bob.__dict__) # {'username': 'bob456', 'email': '[email protected]', 'role': 'member'}
This demonstrates how instance attributes handle personal data, while class attributes manage shared state. The active_count tracks how many users exist globally.
I built a similar system for a small team project. When we accidentally assigned a new role to the class instead of the instance, all existing users got promoted unexpectedly. We caught it during testing because we logged every user creation event.
Always initialize mutable attributes (lists, dicts) inside __init__ to avoid unintended sharing. Use class attributes only for values that truly apply to all instances.
Use hasattr() or getattr() to safely check for attributes. Avoid relying on exceptions to control flow.
Example:
if hasattr(user, 'last_login'):
print(f"Last login: {user.last_login}")
else:
print("Never logged in.")
Also, consider using @property decorators for computed attributes. These allow you to define methods that behave like attributes.
Python object attributes are fundamental to building structured, maintainable code. They enable clear separation between data and behavior, making programs easier to debug and extend.