Skip to content

Latest commit

 

History

History
235 lines (166 loc) · 8.9 KB

File metadata and controls

235 lines (166 loc) · 8.9 KB

Python Attributes & Methods — Instance vs Class Attributes & Dot Notation

Big idea: A class's attributes are the data it holds, and its methods are the actions it can perform. Attributes come in two flavours: instance attributes (unique per object) and class attributes (shared by every object).

🧰 The Two Sides of a Class

flowchart LR
	CLS["📐 class Dog"] --> ATTR["🏷️ Attributes<br>(data)"]
	CLS --> METH["🐝 Methods<br>(behavior)"]

	ATTR --> CA["🏢 Class attributes<br>shared by all objects"]
	ATTR --> IA["🏠 Instance attributes<br>unique per object"]

	METH --> M1["def bark(self): ..."]
	METH --> M2["def describe(self): ..."]
Loading

🏷️ Attributes — Variables That Live on an Object

Attributes are variables that belong to a class or an object. They hold the data. There are two kinds:

Instance attribute Class attribute
Belongs to The object (each instance has its own copy) The class itself (one copy shared by all instances)
Where it's defined Usually in __init__ via self.name = ... Directly in the class body, above __init__
Differs per object? Yes — each object has its own value No — same value for everyone
How to access object.attr Class.attr or object.attr

Example — Class vs Instance Attribute

class Dog:
    species = "French Bulldog"      # 🏢 class attribute (shared)

    def __init__(self, name):
        self.name = name            # 🏠 instance attribute (per-object)

print(Dog.species)   # French Bulldog   — read straight from the class

dog1 = Dog("Jack")
print(dog1.name)     # Jack
print(dog1.species)  # French Bulldog   — inherited from the class

dog2 = Dog("Tom")
print(dog2.name)     # Tom
print(dog2.species)  # French Bulldog
💡

You can read class attributes directly from the class (Dog.species) — no object needed. But instance attributes only exist once you've created an object and __init__ has run — Dog.name would AttributeError.

Example — Instance Attributes Only

A Car whose color and model differ per object:

class Car:
    def __init__(self, color, model):
        self.color = color           # 🏠 instance attribute
        self.model = model           # 🏠 instance attribute

car_1 = Car("red", "Toyota Corolla")
car_2 = Car("green", "Lamborghini Revuelto")

print(car_1.model)  # Toyota Corolla
print(car_2.model)  # Lamborghini Revuelto

print(car_1.color)  # red
print(car_2.color)  # green

How Lookup Works — dog1.species

flowchart TD
	A["dog1.species"] --> B{"Does dog1 have<br>its own 'species'?"}
	B -- Yes --> C["Use the instance attribute"]
	B -- No --> D{"Does Dog (class)<br>have 'species'?"}
	D -- Yes --> E["Use the class attribute"]
	D -- No --> F["AttributeError"]
Loading

Python looks at the instance first, then falls back to the class. That's why every Dog sees the same species even though only one copy exists.

⚠️

Watch out: writing dog1.species = "Pug" does not change the class attribute — it creates a new instance attribute on dog1 that shadows the class attribute. Other dogs still see "French Bulldog". To actually change the shared value, write Dog.species = "Pug".

🐝 Methods — Functions That Live on a Class

Methods are functions defined inside a class. They let an object perform actions — usually using its own attributes via self.

class Dog:
    species = "French Bulldog"

    def __init__(self, name):
        self.name = name

    def bark(self):
        return f"{self.name} says woof woof!"

jack = Dog("Jack")
jill = Dog("Jill")

print(jack.bark())   # Jack says woof woof!
print(jill.bark())   # Jill says woof woof!
  • Defined with def inside the class.
  • First parameter is self — the object the method is called on.
  • Called with dot notation + parentheses: jack.bark().
  • Same method, different output — because each object's self.name is different.

Another Example — Car.describe()

class Car:
    def __init__(self, color, model):
        self.color = color
        self.model = model

    def describe(self):
        return f"This car is a {self.color} {self.model}"

car_1 = Car("red", "Toyota Corolla")
car_2 = Car("green", "Lamborghini Revuelto")

print(car_1.describe())  # This car is a red Toyota Corolla
print(car_2.describe())  # This car is a green Lamborghini Revuelto

⚙️ Dot Notation — The Universal Access Pattern

Goal Syntax Example
Read an instance attribute object.attribute car_1.color
Write an instance attribute object.attribute = value car_1.color = "blue"
Read a class attribute Class.attribute (or object.attribute) Dog.species
Change a class attribute (truly) Class.attribute = value Dog.species = "Pug"
Call a method object.method(args) car_1.describe()

🧪 Worked Example — Class Attribute as a Counter

class Dog:
    species = "French Bulldog"   # 🏢 class attribute (shared)
    count = 0                    # 🏢 shared counter

    def __init__(self, name):
        self.name = name
        Dog.count += 1           # bump the class-level counter

    def bark(self):
        return f"{self.name} ({Dog.species}) says woof! [#{Dog.count}]"

Dog("Jack")
Dog("Jill")
bo = Dog("Bo")

print(Dog.count)     # 3   — same for every instance
print(bo.bark())     # Bo (French Bulldog) says woof! [#3]

Why this works: instance attributes (self.name) are different for every dog, but the class attribute count is the one place that tracks “how many dogs exist?” — the perfect home for shared state.

✅ Best Practices

  • Initialize all instance attributes in __init__ so objects start in a consistent state.
  • Use class attributes for values that are the same for every instance (constants, shared counters, default config).
  • Use instance attributes for anything that varies per object (name, balance, email).
  • Access methods with dot notation + parentheses: obj.method(). Forgetting () gives you the method object, not its result.
  • To change a shared class-level value, set it on the class (Dog.species = ...), not on an instance.
  • Name attributes for what they hold; name methods for what they do.

❌ Common Mistakes

  • Confusing dog1.species = "Pug" with changing the class attribute — it actually shadows it on that one instance.
  • Trying to read an instance attribute from the class: Dog.nameAttributeError.
  • Forgetting self in a method definition, then getting TypeError: ... takes 0 positional arguments but 1 was given.
  • Defining “instance attributes” at the class level by mistake — mutable defaults like tricks = [] are then shared by every dog, which surprises people. Use self.tricks = [] inside __init__.
  • Calling methods like attributes (dog.bark instead of dog.bark()) — you get the method object, not the result.

🧠 Quick Self-Check

1. What are the two types of attributes in Python?

  • Public and private attributes
  • Local and global attributes
  • Instance attributes and class attributes
  • Mutable and immutable attributes

2. What is required to access instance attributes?

  • The class name only
  • Decorators
  • An instance or object of the class
  • A static method
💡

Class attributes can be read straight from the class (Dog.species). Instance attributes only exist after you create an object and __init__ runs — so you need an actual object to read them.

3. How do you define and access methods?

  • As standalone functions outside a class, accessed with brackets
  • As variables in a class, accessed like attributes
  • With special keywords, called automatically
  • They are defined inside a class and accessed with dot notation

🎯 Key Takeaways

  • Attributes are the data of a class; methods are its behavior.
  • Instance attributes are unique to each object — set in __init__ via self.attr = value.
  • Class attributes are shared by every object — defined directly in the class body.
  • Python looks up obj.attr on the instance first, then on the class — that's how shared class attributes “appear” on every object.
  • Reassigning obj.attr = ... creates an instance attribute that shadows the class one; change Class.attr = ... to update the shared value.
  • Methods are functions inside a class with self as the first parameter — call them with dot notation + parentheses (obj.method()).
  • Use class attributes for shared constants/counters, and instance attributes for anything that varies per object.