What an Object Knows and What It Can Do
The idea
Every object has two things: data it holds and actions it can perform.
The data is stored in attributes. The actions are defined as methods.
You've already seen both — in the previous article. This one goes deeper.
Attributes — what an object knows
Attributes are variables that belong to a specific object. They're set in __init__ and accessed with dot notation.
class Animal:
def __init__(self, name, species, size, health, age):
self.name = name
self.species = species
self.size = size
self.health = health
self.age = age
self.status = "available" # default value — not passed in
status has a default value. Every new animal starts as available — no argument needed. You can still change it later:
lassie = Animal("Lassie", "dog", "medium", "healthy", 4)
print(lassie.status) # available
lassie.status = "adopted"
print(lassie.status) # adopted
Class attributes vs instance attributes
Attributes defined in __init__ belong to each individual object — they're instance attributes. Every object has its own copy.
Attributes defined directly on the class — outside __init__ — are class attributes. They're shared by all objects.
class Animal:
shelter = "Safe Paws" # class attribute — shared by all
def __init__(self, name, species):
self.name = name # instance attribute — unique per object
self.species = species
lassie = Animal("Lassie", "dog")
whiskers = Animal("Whiskers", "cat")
print(lassie.shelter) # Safe Paws
print(whiskers.shelter) # Safe Paws
print(lassie.name) # Lassie
print(whiskers.name) # Whiskers
Use class attributes for data that belongs to the concept, not the individual — a shelter name, a version number, a default configuration.
Methods — what an object can do
Methods are functions defined inside a class. They always take self as the first parameter — that's how they access the object's own data.
class Animal:
def __init__(self, name, species, size, health, age):
self.name = name
self.species = species
self.size = size
self.health = health
self.age = age
self.status = "available"
def describe(self):
print(f"{self.name} is a {self.age} year old, {self.size} sized {self.species}.")
print(f"Health: {self.health}. Status: {self.status}.")
def adopt(self):
self.status = "adopted"
print(f"{self.name} has been adopted!")
def update_health(self, new_health):
self.health = new_health
print(f"{self.name}'s health updated to: {self.health}.")
lassie = Animal("Lassie", "dog", "medium", "ill", 4)
lassie.describe()
lassie.update_health("healthy")
lassie.adopt()
lassie.describe()
Output:
Lassie is a 4 year old, medium sized dog.
Health: ill. Status: available.
Lassie's health updated to: healthy.
Lassie has been adopted!
Lassie is a 4 year old, medium sized dog.
Health: healthy. Status: adopted.
The object holds its own state. Methods change that state. No global dictionary. No external functions. Everything in one place.
Methods that return values
Methods can return values just like regular functions:
def is_available(self):
return self.status == "available"
lassie = Animal("Lassie", "dog", "medium", "healthy", 4)
print(lassie.is_available()) # True
lassie.adopt()
print(lassie.is_available()) # False
Heads up!
- Instance attributes belong to each object — class attributes are shared by all
- Methods always take
selfas first parameter — Python passes it automatically - Access attributes and call methods with dot notation —
obj.attr,obj.method() - Methods can both read and modify the object's attributes
The mindset shift
Stop thinking: "I have a function that takes a dictionary and modifies it."
Start thinking: "I have an object that knows its own state and can change it."
What you should understand now
- Attributes store data on the object — set in
__init__, accessed with dot notation - Default attribute values don't require arguments at creation
- Class attributes are shared — instance attributes are unique per object
- Methods are functions inside a class — they use
selfto access the object's data - Methods can read, modify, and return the object's attributes