Get AI summaries of any video or article — Sign up free
Python OOP Tutorial 2: Class Variables thumbnail

Python OOP Tutorial 2: Class Variables

Corey Schafer·
4 min read

Based on Corey Schafer's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.

TL;DR

Class variables are shared across all instances and are defined on the class (e.g., raise_amount, num_employees).

Briefing

Class variables let one shared value live on a class and be reused across every instance—perfect for data that should stay consistent company-wide, like an annual raise rate or a global employee counter. The tutorial contrasts this with instance variables, which store per-object data such as each employee’s name, email, and pay.

To make the difference concrete, the lesson starts with an Employee class that has an apply_raise method. Initially, the raise percentage is hard-coded inside apply_raise (pay multiplied by 1.04). That works, but it creates two problems: there’s no clean way to read the raise rate as employee.raise_amount, and updating the rate would require hunting through the code wherever the number appears. Moving the 4% value into a class variable (raise_amount = 1.04) fixes both issues—now the rate is defined once at the class level and can be accessed as either Employee.raise_amount or employee_instance.raise_amount.

The tutorial then digs into why instance access still works for class variables. When an attribute is requested on an instance, Python first checks the instance’s own namespace. If the attribute isn’t found there, Python falls back to the class (and its base classes). That’s why employee_instance.raise_amount returns the class’s raise_amount even though the instance itself doesn’t store that attribute. The lesson reinforces this by printing employee_instance.__dict__ (showing no raise_amount there) while also inspecting the class attribute via the class namespace.

A key nuance follows: assigning to a class variable through an instance can create an instance-specific override. When raise_amount is changed via employee_instance.raise_amount = 1.05, Python adds raise_amount to that instance’s namespace, so only that employee sees the new value; other instances still fall back to the original class value. This is why the tutorial recommends using self.raise_amount inside apply_raise: it allows per-instance overrides when needed, while still defaulting to the class value.

The lesson closes with a second class-variable example where self is less appropriate: tracking the total number of employees. A num_employees class variable starts at 0 and increments inside __init__ each time a new Employee instance is created. Accessing Employee.num_employees yields the shared count across all instances, and the increment happens reliably because __init__ runs for every instantiation.

Overall, the takeaway is practical: use instance variables for data unique to each object, use class variables for shared constants or global counters, and understand Python’s attribute lookup rules so you know when changes affect everyone versus only one instance.

Cornell Notes

Class variables store values shared across all instances of a class, while instance variables store per-object data. In the Employee example, moving a hard-coded raise percentage into a class variable (raise_amount) makes it readable as Employee.raise_amount or employee_instance.raise_amount and easier to update in one place. Python resolves attributes by first checking an instance’s namespace; if the attribute isn’t there, it falls back to the class. Assigning to a class variable through an instance creates an instance-specific override, so only that instance changes. The tutorial also uses a num_employees class variable incremented in __init__ to demonstrate a shared global counter across instances.

Why is a class variable better than hard-coding the raise percentage inside apply_raise?

Hard-coding (e.g., pay *= 1.04) makes the raise rate hard to access directly and forces updates to be repeated wherever the number appears. By defining raise_amount at the class level, the value becomes a single source of truth that can be read as Employee.raise_amount or employee_instance.raise_amount, and changing the rate requires editing one line.

How can an instance access a class variable if the instance doesn’t store it?

When employee_instance.raise_amount is requested, Python checks the instance’s namespace first (its __dict__). If raise_amount isn’t present there, Python searches the class (and base classes) for the attribute. That fallback behavior is why both Employee.raise_amount and employee_instance.raise_amount return the class variable’s value.

What happens when raise_amount is assigned through an instance (e.g., employee1.raise_amount = 1.05)?

That assignment creates raise_amount inside that specific instance’s namespace, effectively overriding the class value for that one object. Other instances that don’t have their own raise_amount continue to fall back to the original class variable.

Why does using self.raise_amount inside apply_raise matter?

Using self.raise_amount ensures the method respects instance-level overrides. If an instance has its own raise_amount, self will pick it up first; otherwise it falls back to the class variable. This also makes it easier for subclasses to override the constant later.

When is it appropriate to use the class name instead of self for a class variable?

For a shared counter like num_employees, the tutorial increments Employee.num_employees inside __init__. Since the total should be consistent across all instances, referencing the class variable directly avoids implying per-instance state. The count increases each time a new instance is created.

Review Questions

  1. What is the attribute lookup order in Python when accessing an attribute on an instance that may exist only on the class?
  2. How would you predict the behavior of apply_raise if one instance overrides raise_amount but others do not?
  3. Why does incrementing num_employees inside __init__ produce the correct shared total across multiple instances?

Key Points

  1. 1

    Class variables are shared across all instances and are defined on the class (e.g., raise_amount, num_employees).

  2. 2

    Instance variables hold data unique to each object (e.g., name, email, pay).

  3. 3

    Python attribute access checks the instance namespace first; if missing, it falls back to the class namespace.

  4. 4

    Reading a class variable through an instance works because of that fallback lookup behavior.

  5. 5

    Assigning a class variable through an instance creates an instance-specific override that affects only that instance.

  6. 6

    Using self.raise_amount inside methods lets per-instance overrides take effect while still defaulting to the class value.

  7. 7

    Incrementing a class variable in __init__ is a reliable way to maintain a shared global counter across all instances.

Highlights

Moving the raise rate from a hard-coded literal into a class variable turns it into a single, reusable constant accessible as Employee.raise_amount or employee_instance.raise_amount.
Python falls back from an instance’s namespace to the class when an attribute isn’t found on the instance, which is why instances can read class variables.
Setting employee_instance.raise_amount creates an override stored on that instance, leaving other instances using the original class value.
A num_employees class variable incremented in __init__ provides a shared count that updates automatically with each new instance.

Topics

  • Class Variables
  • Instance Variables
  • Attribute Lookup
  • Overriding Class Values
  • Global Counters

Mentioned