Python Essentials for AI Engineer

Course Content

Python Essentials for AI Engineer

6 sections · 48 lessons

What are Instance Variables and Methods?


How Python finds a.MAX_RPMThe instance's own __dict__The class — class variables, methodsParent classes, in MRO orderNowhere: AttributeError
Assigning through self writes only to the top layer, but appending to a class-level list mutates the one every instance shares.

What you need to know

Instance vs class variables

Python
class ApiClient:    MAX_RPM = 60                         # class variable: one value, shared    def __init__(self, api_key):        self.api_key = api_key           # instance variable: per object        self.calls = 0    def call(self):                      # instance method        self.calls += 1        return self.calls <= self.MAX_RPMa, b = ApiClient("key-a"), ApiClient("key-b")a.call(); a.call(); b.call()print(a.calls, b.calls, ApiClient.MAX_RPM)   # 2 1 60

How attribute lookup works

When you read a.MAX_RPM, Python looks in the instance's own __dict__ first, then in the class, then in parent classes. If none has it, you get AttributeError.

Assigning through the instance, a.MAX_RPM = 10, does not change the class variable. It creates a new instance variable on a that hides the class one for a only. Mutating through the instance is different: a.some_list.append(x) changes the one shared list.

The three kinds of method

KindFirst parameterCan useTypical use
Instance methodselfinstance and class datamost methods
@classmethodclsclass dataalternative constructors like from_config
@staticmethodnoneonly its argumentshelper that belongs with the class
Python
class Tokenizer:    def __init__(self, vocab):        self.vocab = vocab    @classmethod    def from_words(cls, words):        return cls({w: i for i, w in enumerate(words)})    @staticmethod    def normalise(text):        return text.lower().strip()tok = Tokenizer.from_words(["refund", "upi"])print(tok.vocab, Tokenizer.normalise("  UPI "))   # {'refund': 0, 'upi': 1} upi

A real-life example

A gateway calls an LLM on behalf of many customers and tracks request timestamps per API key. The first version put the log in the class body:

Python
class RateLimiter:    log = []                              # BUG: one list for EVERY instance    def __init__(self, key):        self.key = key    def record(self, ts):        self.log.append(ts)               # appends to the shared class list        return len(self.log)a, b = RateLimiter("customer-a"), RateLimiter("customer-b")for t in range(5):    a.record(t)print(b.record(99))                       # 6 -> customer B "used" A's requests

Customer A's burst counted against every customer, and quiet customers started getting throttled. The fix is one line: create self.log = [] inside __init__, so each limiter owns its list. Keep class variables for values that really are shared, such as MAX_RPM = 60.

Follow-up questions to expect

  • "What happens if you assign to a class variable through self?" — You create an instance variable with the same name that shadows the class variable for that one object; the class value is unchanged.
  • "When do you use @classmethod vs @staticmethod?" — @classmethod when you need the class, typically to construct instances; @staticmethod for a helper that needs neither class nor instance.
  • "Where are instance variables stored?" — In each object's __dict__, unless the class uses __slots__ to save memory.