Object-Oriented and Advanced Python
Structure larger programs with classes, iterators, decorators, and typing.
Content
Methods and Attributes
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
Methods and Attributes — Deep Dive for CS50 Students
"If classes are blueprints, methods are the verbs and attributes are the nouns. Together they argue about who gets to run the kitchen." — Your mildly dramatic TA
You're already comfortable with Classes and Objects from the previous section. You know how to define a class, instantiate objects, and appreciate readable Python from our move away from verbose prototypes. Now we zoom in: how do objects store state (attributes) and do things (methods)? How does Python decide where to look when you write x.foo? Let's unpack that — with clarity, a few jokes, and practical examples.
Quick map: what this lesson covers
- Instance attributes vs class attributes (state: per-object vs shared)
- Methods: instance methods, @classmethod, @staticmethod
- The role of self and cls (no, they're not magical)
- Property decorator to make attributes act like computed values
- Attribute lookup order, dict, getattr/setattr, and a taste of descriptors
- Style tips (PEP 8 reminders and packaging implications)
1) Attributes: where objects keep their stuff
Instance attributes (per-object)
These are the data unique to each object. Usually set in init.
class Dog:
def __init__(self, name, age):
self.name = name # instance attribute
self.age = age
fido = Dog('Fido', 3)
spot = Dog('Spot', 2)
fido.name and spot.name are independent.
Class attributes (shared)
Defined on the class, shared by all instances unless overridden.
class Dog:
species = 'Canis familiaris' # class attribute
print(Dog.species) # Canis familiaris
print(fido.species) # Canis familiaris
Change Dog.species and every instance that hasn't overridden species will see the change. This is great for constants, caches, or counters — but beware mutable class attributes (lists, dicts) causing surprising shared state.
2) Methods: functions attached to classes
Instance methods
The common kind. First parameter is self, which refers to the instance. It's explicit in Python — because we like being clear.
class Counter:
def __init__(self):
self.count = 0
def increment(self):
self.count += 1 # accesses instance attribute via self
Calling c.increment() implicitly passes c as self.
@classmethod
Receives the class as the first argument (conventionally cls). Useful for alternate constructors or operations that affect the class.
class Pizza:
def __init__(self, toppings):
self.toppings = toppings
@classmethod
def margherita(cls):
return cls(['mozzarella', 'tomato', 'basil'])
@staticmethod
No implicit first argument. Behaves like a plain function namespaced in the class. Use for utility functions related to the class but not touching cls or self.
class Math:
@staticmethod
def add(a, b):
return a + b
3) Bound vs. unbound calls and attribute lookup
When you do obj.name, Python looks in this order:
- obj.dict (instance attributes)
- type(obj).dict (class attributes, including methods)
- base classes' dicts
If the attribute is a function in the class, Python creates a bound method where the function is wrapped and self is bound automatically. That's why you call obj.method() and don't pass self manually.
You can introspect with obj.dict and cls.dict to see what's what.
4) Properties: make methods behave like attributes
Properties let you keep a nice attribute-style API while computing or validating on access.
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value <= 0:
raise ValueError('radius must be > 0')
self._radius = value
@property
def area(self):
import math
return math.pi * self._radius ** 2 # read-only property
Now c.area acts like an attribute but is computed when accessed. This is great for keeping a stable public API while changing internals — helpful when publishing packages.
5) getattr, setattr, getattr, getattribute (dynamic attribute control)
- getattr(obj, 'x', default) reads attributes dynamically.
- setattr(obj, 'x', value) sets them.
- getattr(self, name) is called only when attribute lookup fails — good for lazy attributes.
- getattribute(self, name) intercepts every lookup — powerful but easy to break if you recurse.
Use these sparingly. If you find yourself overusing them, consider simpler alternatives or document behavior clearly (PEP 8: explicit is better than implicit).
6) Descriptor glimpse (advanced but useful)
A descriptor is an object with get/set/delete methods stored on a class. @property is a common descriptor. Descriptors are how Python implements methods like properties, functions, and cached attributes. You don't need to write them often, but they're the plumbing behind the scenes.
Practical pitfalls & style tips
- Avoid mutable class attributes (like lists/dicts) unless intentional — they are shared across instances.
- Use _single_leading_underscore for “internal” attributes; use __double_leading to trigger name-mangling if you must.
- Follow PEP 8 naming: methods and attributes snake_case, classes PascalCase.
- When packaging (remember Packaging Basics), design a clear public API: prefer properties and methods that are stable and documented.
Example: Bringing it together
class BankAccount:
interest_rate = 0.01 # class attribute (shared default)
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = balance # instance attribute
def deposit(self, amt):
self._balance += amt
def withdraw(self, amt):
if amt > self._balance:
raise ValueError('Insufficient funds')
self._balance -= amt
@property
def balance(self):
return self._balance
@classmethod
def with_bonus(cls, owner):
acc = cls(owner)
acc.deposit(10) # welcome bonus
return acc
This pattern keeps internal state private-ish (_balance), exposes a safe public property (balance), and offers an alternate constructor with @classmethod.
Key takeaways (TL;DR)
- Attributes = data, methods = behavior. Instance attributes belong to the object; class attributes are shared.
- self is the instance; cls is the class. They make binding explicit.
- Use @property to expose computed or validated attributes cleanly.
- Watch out for mutable class attributes — they bite in surprising ways.
- Know attribute lookup order and the difference between instance.dict and class.dict.
- Keep your API clean and documented — especially when packaging code for others.
If a concept feels slippery: try small experiments in the REPL. Create classes, print dict, tweak class attributes, and watch how instances react. This is how you learn the language’s personality.
Final thought: methods and attributes are where your code’s grammar meets its memory — design them clearly, and your programs will stop whispering and start singing.
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!