Attributes, Methods, and Object State
What an object knows and what it can do
In this lesson
In Python, an attribute is a piece of data stored on an object, and a method is an action that reads or updates that data. Together an object’s attributes make up its state — the information it holds, which can change as the program runs. In this lesson you’ll see how attributes store state, how methods change it safely, and why returning a value usually beats printing one.
Explain it like I’m 5
Attributes are what an object knows; methods are what it can do. A bank account knows its balance, and it can deposit or withdraw — each action changes what it knows.
Attributes hold state; methods change it
An attribute is a value stored on an object (like self.balance). Together, an object’s attributes are its state. A method is an action that reads or updates that state. Because the data and the actions live in the same object, you can trust that a deposit always updates the right balance.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
acc = BankAccount("Ada", 100)
acc.deposit(50)
print(acc.owner, "has", acc.balance)
Ada has 150
Return a value instead of printing inside a method
It’s tempting to print() from inside a method, but that locks the method into one use — displaying. If a method returns its result instead, the caller can print it, store it, or make a decision with it. Reserve printing for the outer layer of your program.
class BankAccount:
def __init__(self, balance=0):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
return False
self.balance -= amount
return True
acc = BankAccount(100)
if acc.withdraw(150):
print("Withdrawn")
else:
print("Insufficient funds")
Insufficient funds
Common mistake: Printing inside a method that should return a value
Printing gives instant feedback while you’re experimenting, so it sneaks into methods permanently.
Have the method return its result and let the caller decide whether to print it, store it, or branch on it. A method that returns is reusable; one that prints is stuck doing only that.
Common mistake: Changing an attribute from outside the object
Writing acc.balance -= 50 directly seems simpler than calling a method.
Go through a method like withdraw() so the object can enforce its own rules (no overdraft, no negative deposit). That’s the whole point of keeping behaviour with the data.
Common mistake: Letting state become invalid
Skipping a check lets a negative deposit or an overdraft slip through, and later code trusts a balance that no longer makes sense.
Guard the change: reject the bad value (return False or raise an error) before touching the attribute.
What is an object's state?
State is the data an object currently holds in its attributes — and it can change as methods run.
Why might withdraw() return False instead of printing an error?
Returning a value keeps the method reusable; the caller can print, log, or branch on the result.
After acc.deposit(25) on a balance of 100, what is acc.balance?
deposit adds the amount to the current balance: 100 + 25 = 125.
Mini exercise (medium)
Build a BankAccount class with deposit(amount) and withdraw(amount) methods. deposit should reject zero or negative amounts (return False), and withdraw should refuse to overdraw (return False). Both return True on success.
Do it here. Finish the code below and press Run to try your answer right in the browser — no setup needed.
class BankAccount:
def __init__(self, balance=0):
self.balance = balance
def deposit(self, amount):
# TODO: reject amounts <= 0 (return False); otherwise add and return True
pass
def withdraw(self, amount):
# TODO: reject overdrawing (return False); otherwise subtract and return True
pass
acc = BankAccount(100)
print(acc.deposit(-5))
print(acc.deposit(50))
print(acc.withdraw(200))
print(acc.balance)
Guard first: in deposit, if amount <= 0: return False. In withdraw, if amount > self.balance: return False. Only change self.balance after the guard passes.
class BankAccount:
def __init__(self, balance=0):
self.balance = balance
def deposit(self, amount):
if amount <= 0:
return False
self.balance += amount
return True
def withdraw(self, amount):
if amount > self.balance:
return False
self.balance -= amount
return True
acc = BankAccount(100)
print(acc.deposit(-5))
print(acc.deposit(50))
print(acc.withdraw(200))
print(acc.balance)
False
True
False
150
assert acc.deposit(-1) is False, "deposit should reject zero or negative amounts by returning False"
assert acc.balance == 150, "a rejected deposit must not change the balance"
assert acc.deposit(50) is True and acc.balance == 200, "a valid deposit should add to the balance and return True"
assert acc.withdraw(1000) is False and acc.balance == 200, "withdraw should refuse to overdraw and leave the balance unchanged"
print("✓ Guards in place!")