When you call dog.bark(), how does bark() know which dog? Python passes the object automatically as the first parameter, called self. It's how methods access the object's data and other methods.

What self is

The current object instance.

example
self_basics.py
Replay: real traced execution (multi-file project)
# Understanding self as Current Object

print("=== What is self? ===\n")

class Dog:
    def __init__(self, name):
        print(f"__init__ called, self is: {self}")
        self.name = name

    def bark(self):
        print(f"bark() called, self is: {self}")
        print(f"{self.name} says: Woof!")

# Create two dogs
buddy = Dog("Buddy")
print()
max_dog = Dog("Max")

print(f"\nbuddy is: {buddy}")
print(f"max_dog is: {max_dog}")

# self becomes the object before the dot
print("\n=== self = Object Before the Dot ===")
buddy.bark()  # self = buddy
print()
max_dog.bark()  # self = max_dog

print("\n=== Manual Call (Don't Do This) ===")

# These are equivalent:
buddy.bark()  # Normal way
Dog.bark(buddy)  # What Python actually does

print("\n=== Each Object Has Its Own self ===")

class Counter:
    def __init__(self, start=0):
        self.count = start

    def increment(self):
        self.count += 1

    def display(self):
        print(f"This counter ({id(self)}): {self.count}")

c1 = Counter(0)
c2 = Counter(100)

# Each operates on its own count
c1.increment()
c1.increment()
c2.increment()

c1.display()  # self = c1
c2.display()  # self = c2

print("\n=== self is Just a Name ===")

class Example:
    def method1(self):
        print(f"self = {self}")

    def method2(this):
        print(f"this = {this}")  # Works! But don't do it

    def method3(potato):
        print(f"potato = {potato}")  # Also works! Very bad style

obj = Example()
obj.method1()  # self
obj.method2()  # this (same object)
obj.method3()  # potato (same object)

print("\nAlways use 'self' - it's Python convention!")
# Understanding self as Current Object

print("=== What is self? ===\n")

class Dog:
    def __init__(self, name):
        print(f"__init__ called, self is: {self}")
        self.name = name

    def bark(self):
        print(f"bark() called, self is: {self}")
        print(f"{self.name} says: Woof!")

# Create two dogs
buddy = Dog("Rex")
print()
max_dog = Dog("Max")

print(f"\nbuddy is: {buddy}")
print(f"max_dog is: {max_dog}")

# self becomes the object before the dot
print("\n=== self = Object Before the Dot ===")
buddy.bark()  # self = buddy
print()
max_dog.bark()  # self = max_dog

print("\n=== Manual Call (Don't Do This) ===")

# These are equivalent:
buddy.bark()  # Normal way
Dog.bark(buddy)  # What Python actually does

print("\n=== Each Object Has Its Own self ===")

class Counter:
    def __init__(self, start=0):
        self.count = start

    def increment(self):
        self.count += 1

    def display(self):
        print(f"This counter ({id(self)}): {self.count}")

c1 = Counter(0)
c2 = Counter(100)

# Each operates on its own count
c1.increment()
c1.increment()
c2.increment()

c1.display()  # self = c1
c2.display()  # self = c2

print("\n=== self is Just a Name ===")

class Example:
    def method1(self):
        print(f"self = {self}")

    def method2(this):
        print(f"this = {this}")  # Works! But don't do it

    def method3(potato):
        print(f"potato = {potato}")  # Also works! Very bad style

obj = Example()
obj.method1()  # self
obj.method2()  # this (same object)
obj.method3()  # potato (same object)

print("\nAlways use 'self' - it's Python convention!")
# Understanding self as Current Object

print("=== What is self? ===\n")

class Dog:
    def __init__(self, name):
        print(f"__init__ called, self is: {self}")
        self.name = name

    def bark(self):
        print(f"bark() called, self is: {self}")
        print(f"{self.name} says: Woof!")

# Create two dogs
buddy = Dog("Buddy")
print()
max_dog = Dog("Max")

print(f"\nbuddy is: {buddy}")
print(f"max_dog is: {max_dog}")

# self becomes the object before the dot
print("\n=== self = Object Before the Dot ===")
buddy.bark()  # self = buddy
print()
max_dog.bark()  # self = max_dog

print("\n=== Manual Call (Don't Do This) ===")

# These are equivalent:
buddy.bark()  # Normal way
Dog.bark(buddy)  # What Python actually does

print("\n=== Each Object Has Its Own self ===")

class Counter:
    def __init__(self, start=0):
        self.count = start

    def increment(self):
        self.count += 1

    def display(self):
        print(f"This counter ({id(self)}): {self.count}")

c1 = Counter(0)
c2 = Counter(10)

# Each operates on its own count
c1.increment()
c1.increment()
c2.increment()

c1.display()  # self = c1
c2.display()  # self = c2

print("\n=== self is Just a Name ===")

class Example:
    def method1(self):
        print(f"self = {self}")

    def method2(this):
        print(f"this = {this}")  # Works! But don't do it

    def method3(potato):
        print(f"potato = {potato}")  # Also works! Very bad style

obj = Example()
obj.method1()  # self
obj.method2()  # this (same object)
obj.method3()  # potato (same object)

print("\nAlways use 'self' - it's Python convention!")
# Understanding self as Current Object

print("=== What is self? ===\n")

class Dog:
    def __init__(self, name):
        print(f"__init__ called, self is: {self}")
        self.name = name

    def bark(self):
        print(f"bark() called, self is: {self}")
        print(f"{self.name} says: Woof!")

# Create two dogs
buddy = Dog("Buddy")
print()
max_dog = Dog("Max")

print(f"\nbuddy is: {buddy}")
print(f"max_dog is: {max_dog}")

# self becomes the object before the dot
print("\n=== self = Object Before the Dot ===")
buddy.bark()  # self = buddy
print()
max_dog.bark()  # self = max_dog

print("\n=== Manual Call (Don't Do This) ===")

# These are equivalent:
buddy.bark()  # Normal way
Dog.bark(buddy)  # What Python actually does

print("\n=== Each Object Has Its Own self ===")

class Counter:
    def __init__(self, start=0):
        self.count = start

    def increment(self):
        self.count += 1

    def display(self):
        print(f"This counter ({id(self)}): {self.count}")

c1 = Counter(0)
c2 = Counter(500)

# Each operates on its own count
c1.increment()
c1.increment()
c2.increment()

c1.display()  # self = c1
c2.display()  # self = c2

print("\n=== self is Just a Name ===")

class Example:
    def method1(self):
        print(f"self = {self}")

    def method2(this):
        print(f"this = {this}")  # Works! But don't do it

    def method3(potato):
        print(f"potato = {potato}")  # Also works! Very bad style

obj = Example()
obj.method1()  # self
obj.method2()  # this (same object)
obj.method3()  # potato (same object)

print("\nAlways use 'self' - it's Python convention!")
  1. print("=== What is self? === ")

    3print("=== What is self? ===\n")45class Dog:6    def __init__(self, name):  #?initself7        print(f"__init__ called, self is: {self}")8        self.name = name  #?selfname9    10    def bark(self):  #?barkself11        print(f"bark() called, self is: {self}")12        print(f"{self.name} says: Woof!")1314# Create two dogs  #?twodogs15buddy = Dog("Buddy")  #@buddy=Dog("Rex")16print()
    output=== What is self? ===
  2. self.name ← Buddy

    pass 1 of 2
    5class Dog:6    def __init__(self⟨Dog A⟩, nameBuddy):  #?initself7        print(f"__init__ called, self is: {self}")8        self.name→ Buddy = nameBuddy  #?selfname
    output__init__ called, self is: ⟨Dog A⟩
  3. buddy ← ⟨Dog A⟩

    14# Create two dogs  #?twodogs15buddy→ ⟨Dog A⟩ = Dog("Buddy")  #@buddy=Dog("Rex")16print()17max_dog = Dog("Max")
  4. self.name ← Max

    pass 2 of 2
    5class Dog:6    def __init__(self⟨Dog B⟩, nameMax):  #?initself7        print(f"__init__ called, self is: {self}")8        self.name→ Max = nameMax  #?selfname
    output__init__ called, self is: ⟨Dog B⟩
  5. max_dog ← ⟨Dog B⟩

    16print()17max_dog→ ⟨Dog B⟩ = Dog("Max")1819print(f"\nbuddy is: {buddy⟨Dog A⟩}")20print(f"max_dog is: {max_dog⟨Dog B⟩}")2122# self becomes the object before the dot  #?beforedot23print("\n=== self = Object Before the Dot ===")24buddy⟨Dog A⟩.bark()  # self = buddy  #?selfisbuddy25print()
    output
    buddy is: ⟨Dog A⟩
    max_dog is: ⟨Dog B⟩
    
    === self = Object Before the Dot ===
  6. def bark(self): #?barkself

    pass 1 of 4
    10def bark(self⟨Dog A⟩):  #?barkself11    print(f"bark() called, self is: {self}")12    print(f"{self.nameBuddy} says: Woof!")
    outputbark() called, self is: ⟨Dog A⟩
    Buddy says: Woof!
    All 4 passes — pass 1 is the card above
    passselfself.name
    1⟨Dog A⟩Buddy
    2⟨Dog B⟩Max
    3⟨Dog A⟩Buddy
    4⟨Dog A⟩Buddy
  7. buddy.bark() # self = buddy #?selfisbuddy

    23print("\n=== self = Object Before the Dot ===")24buddy⟨Dog A⟩.bark()  # self = buddy  #?selfisbuddy25print()26max_dog⟨Dog B⟩.bark()  # self = max_dog  #?selfismax
  8. max_dog.bark() # self = max_dog #?selfismax

    25print()26max_dog⟨Dog B⟩.bark()  # self = max_dog  #?selfismax2728print("\n=== Manual Call (Don't Do This) ===")2930# These are equivalent:  #?equivalent31buddy⟨Dog A⟩.bark()  # Normal way32Dog.bark(buddy)  # What Python actually does  #?explicitself
    output
    === Manual Call (Don't Do This) ===
  9. buddy.bark() # Normal way

    30# These are equivalent:  #?equivalent31buddy⟨Dog A⟩.bark()  # Normal way32Dog<class '__main__.Dog'>.bark(buddy⟨Dog A⟩)  # What Python actually does  #?explicitself
  10. Dog.bark(buddy) # What Python actually does #?explicitself

    31buddy.bark()  # Normal way32Dog<class '__main__.Dog'>.bark(buddy⟨Dog A⟩)  # What Python actually does  #?explicitself3334print("\n=== Each Object Has Its Own self ===")3536class Counter:37    def __init__(self, start=0):38        self.count = start  #?owncount39    40    def increment(self):41        self.count += 1  #?incrementself42    43    def display(self):44        print(f"This counter ({id(self)}): {self.count}")  #?idself4546c1 = Counter(0)47c2 = Counter(100)  #@c2=Counter(10), Counter(500)
    output
    === Each Object Has Its Own self ===
  11. self.count ← 0

    pass 1 of 2
    36class Counter:37    def __init__(self⟨Counter C⟩, start0=0):38        self.count→ 0 = start0  #?owncount
  12. c1 ← ⟨Counter C⟩

    46c1→ ⟨Counter C⟩ = Counter(0)47c2 = Counter(100)  #@c2=Counter(10), Counter(500)
  13. self.count ← 100

    pass 2 of 2
    36class Counter:37    def __init__(self⟨Counter D⟩, start100=0):38        self.count→ 100 = start100  #?owncount
  14. c2 ← ⟨Counter D⟩

    46c1 = Counter(0)47c2→ ⟨Counter D⟩ = Counter(100)  #@c2=Counter(10), Counter(500)4849# Each operates on its own count  #?eachown50c1⟨Counter C⟩.increment()51c1.increment()
  15. self.count ← 1

    pass 1 of 3
    40def increment(self⟨Counter C⟩):41    self.count→ 1 += 1  #?incrementself
    All 3 passes — pass 1 is the card above
    passselfself.count
    1⟨Counter C⟩0 1
    2⟨Counter C⟩1 2
    3⟨Counter D⟩100 101
  16. c1.increment()

    49# Each operates on its own count  #?eachown50c1⟨Counter C⟩.increment()51c1⟨Counter C⟩.increment()52c2.increment()
  17. c1.increment()

    50c1.increment()51c1⟨Counter C⟩.increment()52c2⟨Counter D⟩.increment()
  18. c2.increment()

    51c1.increment()52c2⟨Counter D⟩.increment()5354c1⟨Counter C⟩.display()  # self = c155c2.display()  # self = c2
  19. def display(self):

    pass 1 of 2
    43def display(self⟨Counter C⟩):44    print(f"This counter ({id(self)}): {self.count2}")  #?idself
    outputThis counter (124973150205280): 2
  20. c1.display() # self = c1

    54c1⟨Counter C⟩.display()  # self = c155c2⟨Counter D⟩.display()  # self = c2
  21. def display(self):

    pass 2 of 2
    43def display(self⟨Counter D⟩):44    print(f"This counter ({id(self)}): {self.count101}")  #?idself
    outputThis counter (124973150135472): 101
  22. obj ← ⟨Example E⟩

    54c1.display()  # self = c155c2⟨Counter D⟩.display()  # self = c25657print("\n=== self is Just a Name ===")5859class Example:60    def method1(self):  #?conventional61        print(f"self = {self}")62    63    def method2(this):  #?thisworks64        print(f"this = {this}")  # Works! But don't do it65    66    def method3(potato):  #?potatoworks67        print(f"potato = {potato}")  # Also works! Very bad style6869obj→ ⟨Example E⟩ = Example()70obj⟨Example E⟩.method1()  # self71obj.method2()  # this (same object)
    output
    === self is Just a Name ===
  23. def method1(self): #?conventional

    59class Example:60    def method1(self⟨Example E⟩):  #?conventional61        print(f"self = {self}")
    outputself = ⟨Example E⟩
  24. obj.method1() # self

    69obj = Example()70obj⟨Example E⟩.method1()  # self71obj⟨Example E⟩.method2()  # this (same object)72obj.method3()  # potato (same object)
  25. def method2(this): #?thisworks

    63def method2(this⟨Example E⟩):  #?thisworks64    print(f"this = {this⟨Example E⟩}")  # Works! But don't do it
    outputthis = ⟨Example E⟩
  26. obj.method2() # this (same object)

    70obj.method1()  # self71obj⟨Example E⟩.method2()  # this (same object)72obj⟨Example E⟩.method3()  # potato (same object)
  27. def method3(potato): #?potatoworks

    66def method3(potato⟨Example E⟩):  #?potatoworks67    print(f"potato = {potato⟨Example E⟩}")  # Also works! Very bad style
    outputpotato = ⟨Example E⟩
  28. obj.method3() # potato (same object)

    71obj.method2()  # this (same object)72obj⟨Example E⟩.method3()  # potato (same object)7374print("\nAlways use 'self' - it's Python convention!")75#@help initself
    output
    Always use 'self' - it's Python convention!
  1. print("=== What is self? === ")

    3print("=== What is self? ===\n")45class Dog:6    def __init__(self, name):7        print(f"__init__ called, self is: {self}")8        self.name = name9    10    def bark(self):11        print(f"bark() called, self is: {self}")12        print(f"{self.name} says: Woof!")1314# Create two dogs15buddy = Dog("Rex")16print()
    output=== What is self? ===
  2. self.name ← Rex

    pass 1 of 2
    5class Dog:6    def __init__(self⟨Dog A⟩, nameRex):7        print(f"__init__ called, self is: {self}")8        self.name→ Rex = nameRex
    output__init__ called, self is: ⟨Dog A⟩
  3. buddy ← ⟨Dog A⟩

    14# Create two dogs15buddy→ ⟨Dog A⟩ = Dog("Rex")16print()17max_dog = Dog("Max")
  4. self.name ← Max

    pass 2 of 2
    5class Dog:6    def __init__(self⟨Dog B⟩, nameMax):7        print(f"__init__ called, self is: {self}")8        self.name→ Max = nameMax
    output__init__ called, self is: ⟨Dog B⟩
  5. max_dog ← ⟨Dog B⟩

    16print()17max_dog→ ⟨Dog B⟩ = Dog("Max")1819print(f"\nbuddy is: {buddy⟨Dog A⟩}")20print(f"max_dog is: {max_dog⟨Dog B⟩}")2122# self becomes the object before the dot23print("\n=== self = Object Before the Dot ===")24buddy⟨Dog A⟩.bark()  # self = buddy25print()
    output
    buddy is: ⟨Dog A⟩
    max_dog is: ⟨Dog B⟩
    
    === self = Object Before the Dot ===
  6. def bark(self):

    pass 1 of 4
    10def bark(self⟨Dog A⟩):11    print(f"bark() called, self is: {self}")12    print(f"{self.nameRex} says: Woof!")
    outputbark() called, self is: ⟨Dog A⟩
    Rex says: Woof!
    All 4 passes — pass 1 is the card above
    passselfself.name
    1⟨Dog A⟩Rex
    2⟨Dog B⟩Max
    3⟨Dog A⟩Rex
    4⟨Dog A⟩Rex
  7. buddy.bark() # self = buddy

    23print("\n=== self = Object Before the Dot ===")24buddy⟨Dog A⟩.bark()  # self = buddy25print()26max_dog⟨Dog B⟩.bark()  # self = max_dog
  8. max_dog.bark() # self = max_dog

    25print()26max_dog⟨Dog B⟩.bark()  # self = max_dog2728print("\n=== Manual Call (Don't Do This) ===")2930# These are equivalent:31buddy⟨Dog A⟩.bark()  # Normal way32Dog.bark(buddy)  # What Python actually does
    output
    === Manual Call (Don't Do This) ===
  9. buddy.bark() # Normal way

    30# These are equivalent:31buddy⟨Dog A⟩.bark()  # Normal way32Dog<class '__main__.Dog'>.bark(buddy⟨Dog A⟩)  # What Python actually does
  10. Dog.bark(buddy) # What Python actually does

    31buddy.bark()  # Normal way32Dog<class '__main__.Dog'>.bark(buddy⟨Dog A⟩)  # What Python actually does3334print("\n=== Each Object Has Its Own self ===")3536class Counter:37    def __init__(self, start=0):38        self.count = start39    40    def increment(self):41        self.count += 142    43    def display(self):44        print(f"This counter ({id(self)}): {self.count}")4546c1 = Counter(0)47c2 = Counter(100)
    output
    === Each Object Has Its Own self ===
  11. self.count ← 0

    pass 1 of 2
    36class Counter:37    def __init__(self⟨Counter C⟩, start0=0):38        self.count→ 0 = start0
  12. c1 ← ⟨Counter C⟩

    46c1→ ⟨Counter C⟩ = Counter(0)47c2 = Counter(100)
  13. self.count ← 100

    pass 2 of 2
    36class Counter:37    def __init__(self⟨Counter D⟩, start100=0):38        self.count→ 100 = start100
  14. c2 ← ⟨Counter D⟩

    46c1 = Counter(0)47c2→ ⟨Counter D⟩ = Counter(100)4849# Each operates on its own count50c1⟨Counter C⟩.increment()51c1.increment()
  15. self.count ← 1

    pass 1 of 3
    40def increment(self⟨Counter C⟩):41    self.count→ 1 += 1
    All 3 passes — pass 1 is the card above
    passselfself.count
    1⟨Counter C⟩0 1
    2⟨Counter C⟩1 2
    3⟨Counter D⟩100 101
  16. c1.increment()

    49# Each operates on its own count50c1⟨Counter C⟩.increment()51c1⟨Counter C⟩.increment()52c2.increment()
  17. c1.increment()

    50c1.increment()51c1⟨Counter C⟩.increment()52c2⟨Counter D⟩.increment()
  18. c2.increment()

    51c1.increment()52c2⟨Counter D⟩.increment()5354c1⟨Counter C⟩.display()  # self = c155c2.display()  # self = c2
  19. def display(self):

    pass 1 of 2
    43def display(self⟨Counter C⟩):44    print(f"This counter ({id(self)}): {self.count2}")
    outputThis counter (123362345579632): 2
  20. c1.display() # self = c1

    54c1⟨Counter C⟩.display()  # self = c155c2⟨Counter D⟩.display()  # self = c2
  21. def display(self):

    pass 2 of 2
    43def display(self⟨Counter D⟩):44    print(f"This counter ({id(self)}): {self.count101}")
    outputThis counter (123362345509632): 101
  22. obj ← ⟨Example E⟩

    54c1.display()  # self = c155c2⟨Counter D⟩.display()  # self = c25657print("\n=== self is Just a Name ===")5859class Example:60    def method1(self):61        print(f"self = {self}")62    63    def method2(this):64        print(f"this = {this}")  # Works! But don't do it65    66    def method3(potato):67        print(f"potato = {potato}")  # Also works! Very bad style6869obj→ ⟨Example E⟩ = Example()70obj⟨Example E⟩.method1()  # self71obj.method2()  # this (same object)
    output
    === self is Just a Name ===
  23. def method1(self):

    59class Example:60    def method1(self⟨Example E⟩):61        print(f"self = {self}")
    outputself = ⟨Example E⟩
  24. obj.method1() # self

    69obj = Example()70obj⟨Example E⟩.method1()  # self71obj⟨Example E⟩.method2()  # this (same object)72obj.method3()  # potato (same object)
  25. def method2(this):

    63def method2(this⟨Example E⟩):64    print(f"this = {this⟨Example E⟩}")  # Works! But don't do it
    outputthis = ⟨Example E⟩
  26. obj.method2() # this (same object)

    70obj.method1()  # self71obj⟨Example E⟩.method2()  # this (same object)72obj⟨Example E⟩.method3()  # potato (same object)
  27. def method3(potato):

    66def method3(potato⟨Example E⟩):67    print(f"potato = {potato⟨Example E⟩}")  # Also works! Very bad style
    outputpotato = ⟨Example E⟩
  28. obj.method3() # potato (same object)

    71obj.method2()  # this (same object)72obj⟨Example E⟩.method3()  # potato (same object)7374print("\nAlways use 'self' - it's Python convention!")
    output
    Always use 'self' - it's Python convention!
  1. print("=== What is self? === ")

    3print("=== What is self? ===\n")45class Dog:6    def __init__(self, name):7        print(f"__init__ called, self is: {self}")8        self.name = name9    10    def bark(self):11        print(f"bark() called, self is: {self}")12        print(f"{self.name} says: Woof!")1314# Create two dogs15buddy = Dog("Buddy")16print()
    output=== What is self? ===
  2. self.name ← Buddy

    pass 1 of 2
    5class Dog:6    def __init__(self⟨Dog A⟩, nameBuddy):7        print(f"__init__ called, self is: {self}")8        self.name→ Buddy = nameBuddy
    output__init__ called, self is: ⟨Dog A⟩
  3. buddy ← ⟨Dog A⟩

    14# Create two dogs15buddy→ ⟨Dog A⟩ = Dog("Buddy")16print()17max_dog = Dog("Max")
  4. self.name ← Max

    pass 2 of 2
    5class Dog:6    def __init__(self⟨Dog B⟩, nameMax):7        print(f"__init__ called, self is: {self}")8        self.name→ Max = nameMax
    output__init__ called, self is: ⟨Dog B⟩
  5. max_dog ← ⟨Dog B⟩

    16print()17max_dog→ ⟨Dog B⟩ = Dog("Max")1819print(f"\nbuddy is: {buddy⟨Dog A⟩}")20print(f"max_dog is: {max_dog⟨Dog B⟩}")2122# self becomes the object before the dot23print("\n=== self = Object Before the Dot ===")24buddy⟨Dog A⟩.bark()  # self = buddy25print()
    output
    buddy is: ⟨Dog A⟩
    max_dog is: ⟨Dog B⟩
    
    === self = Object Before the Dot ===
  6. def bark(self):

    pass 1 of 4
    10def bark(self⟨Dog A⟩):11    print(f"bark() called, self is: {self}")12    print(f"{self.nameBuddy} says: Woof!")
    outputbark() called, self is: ⟨Dog A⟩
    Buddy says: Woof!
    All 4 passes — pass 1 is the card above
    passselfself.name
    1⟨Dog A⟩Buddy
    2⟨Dog B⟩Max
    3⟨Dog A⟩Buddy
    4⟨Dog A⟩Buddy
  7. buddy.bark() # self = buddy

    23print("\n=== self = Object Before the Dot ===")24buddy⟨Dog A⟩.bark()  # self = buddy25print()26max_dog⟨Dog B⟩.bark()  # self = max_dog
  8. max_dog.bark() # self = max_dog

    25print()26max_dog⟨Dog B⟩.bark()  # self = max_dog2728print("\n=== Manual Call (Don't Do This) ===")2930# These are equivalent:31buddy⟨Dog A⟩.bark()  # Normal way32Dog.bark(buddy)  # What Python actually does
    output
    === Manual Call (Don't Do This) ===
  9. buddy.bark() # Normal way

    30# These are equivalent:31buddy⟨Dog A⟩.bark()  # Normal way32Dog<class '__main__.Dog'>.bark(buddy⟨Dog A⟩)  # What Python actually does
  10. Dog.bark(buddy) # What Python actually does

    31buddy.bark()  # Normal way32Dog<class '__main__.Dog'>.bark(buddy⟨Dog A⟩)  # What Python actually does3334print("\n=== Each Object Has Its Own self ===")3536class Counter:37    def __init__(self, start=0):38        self.count = start39    40    def increment(self):41        self.count += 142    43    def display(self):44        print(f"This counter ({id(self)}): {self.count}")4546c1 = Counter(0)47c2 = Counter(10)
    output
    === Each Object Has Its Own self ===
  11. self.count ← 0

    pass 1 of 2
    36class Counter:37    def __init__(self⟨Counter C⟩, start0=0):38        self.count→ 0 = start0
  12. c1 ← ⟨Counter C⟩

    46c1→ ⟨Counter C⟩ = Counter(0)47c2 = Counter(10)
  13. self.count ← 10

    pass 2 of 2
    36class Counter:37    def __init__(self⟨Counter D⟩, start10=0):38        self.count→ 10 = start10
  14. c2 ← ⟨Counter D⟩

    46c1 = Counter(0)47c2→ ⟨Counter D⟩ = Counter(10)4849# Each operates on its own count50c1⟨Counter C⟩.increment()51c1.increment()
  15. self.count ← 1

    pass 1 of 3
    40def increment(self⟨Counter C⟩):41    self.count→ 1 += 1
    All 3 passes — pass 1 is the card above
    passselfself.count
    1⟨Counter C⟩0 1
    2⟨Counter C⟩1 2
    3⟨Counter D⟩10 11
  16. c1.increment()

    49# Each operates on its own count50c1⟨Counter C⟩.increment()51c1⟨Counter C⟩.increment()52c2.increment()
  17. c1.increment()

    50c1.increment()51c1⟨Counter C⟩.increment()52c2⟨Counter D⟩.increment()
  18. c2.increment()

    51c1.increment()52c2⟨Counter D⟩.increment()5354c1⟨Counter C⟩.display()  # self = c155c2.display()  # self = c2
  19. def display(self):

    pass 1 of 2
    43def display(self⟨Counter C⟩):44    print(f"This counter ({id(self)}): {self.count2}")
    outputThis counter (129460874114352): 2
  20. c1.display() # self = c1

    54c1⟨Counter C⟩.display()  # self = c155c2⟨Counter D⟩.display()  # self = c2
  21. def display(self):

    pass 2 of 2
    43def display(self⟨Counter D⟩):44    print(f"This counter ({id(self)}): {self.count11}")
    outputThis counter (129460874044400): 11
  22. obj ← ⟨Example E⟩

    54c1.display()  # self = c155c2⟨Counter D⟩.display()  # self = c25657print("\n=== self is Just a Name ===")5859class Example:60    def method1(self):61        print(f"self = {self}")62    63    def method2(this):64        print(f"this = {this}")  # Works! But don't do it65    66    def method3(potato):67        print(f"potato = {potato}")  # Also works! Very bad style6869obj→ ⟨Example E⟩ = Example()70obj⟨Example E⟩.method1()  # self71obj.method2()  # this (same object)
    output
    === self is Just a Name ===
  23. def method1(self):

    59class Example:60    def method1(self⟨Example E⟩):61        print(f"self = {self}")
    outputself = ⟨Example E⟩
  24. obj.method1() # self

    69obj = Example()70obj⟨Example E⟩.method1()  # self71obj⟨Example E⟩.method2()  # this (same object)72obj.method3()  # potato (same object)
  25. def method2(this):

    63def method2(this⟨Example E⟩):64    print(f"this = {this⟨Example E⟩}")  # Works! But don't do it
    outputthis = ⟨Example E⟩
  26. obj.method2() # this (same object)

    70obj.method1()  # self71obj⟨Example E⟩.method2()  # this (same object)72obj⟨Example E⟩.method3()  # potato (same object)
  27. def method3(potato):

    66def method3(potato⟨Example E⟩):67    print(f"potato = {potato⟨Example E⟩}")  # Also works! Very bad style
    outputpotato = ⟨Example E⟩
  28. obj.method3() # potato (same object)

    71obj.method2()  # this (same object)72obj⟨Example E⟩.method3()  # potato (same object)7374print("\nAlways use 'self' - it's Python convention!")
    output
    Always use 'self' - it's Python convention!
  1. print("=== What is self? === ")

    3print("=== What is self? ===\n")45class Dog:6    def __init__(self, name):7        print(f"__init__ called, self is: {self}")8        self.name = name9    10    def bark(self):11        print(f"bark() called, self is: {self}")12        print(f"{self.name} says: Woof!")1314# Create two dogs15buddy = Dog("Buddy")16print()
    output=== What is self? ===
  2. self.name ← Buddy

    pass 1 of 2
    5class Dog:6    def __init__(self⟨Dog A⟩, nameBuddy):7        print(f"__init__ called, self is: {self}")8        self.name→ Buddy = nameBuddy
    output__init__ called, self is: ⟨Dog A⟩
  3. buddy ← ⟨Dog A⟩

    14# Create two dogs15buddy→ ⟨Dog A⟩ = Dog("Buddy")16print()17max_dog = Dog("Max")
  4. self.name ← Max

    pass 2 of 2
    5class Dog:6    def __init__(self⟨Dog B⟩, nameMax):7        print(f"__init__ called, self is: {self}")8        self.name→ Max = nameMax
    output__init__ called, self is: ⟨Dog B⟩
  5. max_dog ← ⟨Dog B⟩

    16print()17max_dog→ ⟨Dog B⟩ = Dog("Max")1819print(f"\nbuddy is: {buddy⟨Dog A⟩}")20print(f"max_dog is: {max_dog⟨Dog B⟩}")2122# self becomes the object before the dot23print("\n=== self = Object Before the Dot ===")24buddy⟨Dog A⟩.bark()  # self = buddy25print()
    output
    buddy is: ⟨Dog A⟩
    max_dog is: ⟨Dog B⟩
    
    === self = Object Before the Dot ===
  6. def bark(self):

    pass 1 of 4
    10def bark(self⟨Dog A⟩):11    print(f"bark() called, self is: {self}")12    print(f"{self.nameBuddy} says: Woof!")
    outputbark() called, self is: ⟨Dog A⟩
    Buddy says: Woof!
    All 4 passes — pass 1 is the card above
    passselfself.name
    1⟨Dog A⟩Buddy
    2⟨Dog B⟩Max
    3⟨Dog A⟩Buddy
    4⟨Dog A⟩Buddy
  7. buddy.bark() # self = buddy

    23print("\n=== self = Object Before the Dot ===")24buddy⟨Dog A⟩.bark()  # self = buddy25print()26max_dog⟨Dog B⟩.bark()  # self = max_dog
  8. max_dog.bark() # self = max_dog

    25print()26max_dog⟨Dog B⟩.bark()  # self = max_dog2728print("\n=== Manual Call (Don't Do This) ===")2930# These are equivalent:31buddy⟨Dog A⟩.bark()  # Normal way32Dog.bark(buddy)  # What Python actually does
    output
    === Manual Call (Don't Do This) ===
  9. buddy.bark() # Normal way

    30# These are equivalent:31buddy⟨Dog A⟩.bark()  # Normal way32Dog<class '__main__.Dog'>.bark(buddy⟨Dog A⟩)  # What Python actually does
  10. Dog.bark(buddy) # What Python actually does

    31buddy.bark()  # Normal way32Dog<class '__main__.Dog'>.bark(buddy⟨Dog A⟩)  # What Python actually does3334print("\n=== Each Object Has Its Own self ===")3536class Counter:37    def __init__(self, start=0):38        self.count = start39    40    def increment(self):41        self.count += 142    43    def display(self):44        print(f"This counter ({id(self)}): {self.count}")4546c1 = Counter(0)47c2 = Counter(500)
    output
    === Each Object Has Its Own self ===
  11. self.count ← 0

    pass 1 of 2
    36class Counter:37    def __init__(self⟨Counter C⟩, start0=0):38        self.count→ 0 = start0
  12. c1 ← ⟨Counter C⟩

    46c1→ ⟨Counter C⟩ = Counter(0)47c2 = Counter(500)
  13. self.count ← 500

    pass 2 of 2
    36class Counter:37    def __init__(self⟨Counter D⟩, start500=0):38        self.count→ 500 = start500
  14. c2 ← ⟨Counter D⟩

    46c1 = Counter(0)47c2→ ⟨Counter D⟩ = Counter(500)4849# Each operates on its own count50c1⟨Counter C⟩.increment()51c1.increment()
  15. self.count ← 1

    pass 1 of 3
    40def increment(self⟨Counter C⟩):41    self.count→ 1 += 1
    All 3 passes — pass 1 is the card above
    passselfself.count
    1⟨Counter C⟩0 1
    2⟨Counter C⟩1 2
    3⟨Counter D⟩500 501
  16. c1.increment()

    49# Each operates on its own count50c1⟨Counter C⟩.increment()51c1⟨Counter C⟩.increment()52c2.increment()
  17. c1.increment()

    50c1.increment()51c1⟨Counter C⟩.increment()52c2⟨Counter D⟩.increment()
  18. c2.increment()

    51c1.increment()52c2⟨Counter D⟩.increment()5354c1⟨Counter C⟩.display()  # self = c155c2.display()  # self = c2
  19. def display(self):

    pass 1 of 2
    43def display(self⟨Counter C⟩):44    print(f"This counter ({id(self)}): {self.count2}")
    outputThis counter (131536251368560): 2
  20. c1.display() # self = c1

    54c1⟨Counter C⟩.display()  # self = c155c2⟨Counter D⟩.display()  # self = c2
  21. def display(self):

    pass 2 of 2
    43def display(self⟨Counter D⟩):44    print(f"This counter ({id(self)}): {self.count501}")
    outputThis counter (131536251298560): 501
  22. obj ← ⟨Example E⟩

    54c1.display()  # self = c155c2⟨Counter D⟩.display()  # self = c25657print("\n=== self is Just a Name ===")5859class Example:60    def method1(self):61        print(f"self = {self}")62    63    def method2(this):64        print(f"this = {this}")  # Works! But don't do it65    66    def method3(potato):67        print(f"potato = {potato}")  # Also works! Very bad style6869obj→ ⟨Example E⟩ = Example()70obj⟨Example E⟩.method1()  # self71obj.method2()  # this (same object)
    output
    === self is Just a Name ===
  23. def method1(self):

    59class Example:60    def method1(self⟨Example E⟩):61        print(f"self = {self}")
    outputself = ⟨Example E⟩
  24. obj.method1() # self

    69obj = Example()70obj⟨Example E⟩.method1()  # self71obj⟨Example E⟩.method2()  # this (same object)72obj.method3()  # potato (same object)
  25. def method2(this):

    63def method2(this⟨Example E⟩):64    print(f"this = {this⟨Example E⟩}")  # Works! But don't do it
    outputthis = ⟨Example E⟩
  26. obj.method2() # this (same object)

    70obj.method1()  # self71obj⟨Example E⟩.method2()  # this (same object)72obj⟨Example E⟩.method3()  # potato (same object)
  27. def method3(potato):

    66def method3(potato⟨Example E⟩):67    print(f"potato = {potato⟨Example E⟩}")  # Also works! Very bad style
    outputpotato = ⟨Example E⟩
  28. obj.method3() # potato (same object)

    71obj.method2()  # this (same object)72obj⟨Example E⟩.method3()  # potato (same object)7374print("\nAlways use 'self' - it's Python convention!")
    output
    Always use 'self' - it's Python convention!

self refers to the object the method was called on. Python passes it automatically.

self Reference to current instance. First parameter of every instance method.

Using self in methods

Access attributes and methods through self.

self_in_methods.py
Replay: real traced execution (multi-file project)
# Using self to Access Attributes and Methods

print("=== Accessing Attributes via self ===\n")

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def introduce(self):
        # Access attributes with self.
        print(f"Hi, I'm {self.name} and I'm {self.age} years old.")

    def have_birthday(self):
        self.age += 1
        print(f"Happy birthday {self.name}! Now {self.age} years old.")

    def compare_age(self, other):
        if self.age > other.age:
            return f"{self.name} is older than {other.name}"
        elif self.age < other.age:
            return f"{self.name} is younger than {other.name}"
        else:
            return f"{self.name} and {other.name} are the same age"

alice = Person("Alice", 30)
bob = Person("Bob", 25)

alice.introduce()
bob.introduce()
alice.have_birthday()
print(alice.compare_age(bob))

print("\n=== Accessing Multiple Attributes ===")

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

    def is_square(self):
        return self.width == self.height

    def resize(self, factor):
        self.width *= factor
        self.height *= factor

    def describe(self):
        shape = "square" if self.is_square() else "rectangle"
        return f"{shape} {self.width}x{self.height}, area={self.area()}"

rect = Rectangle(4, 4)
print(rect.describe())
rect.resize(2)
print(rect.describe())

print("\n=== self in Chained Operations ===")

class BankAccount:
    def __init__(self, balance=0):
        self.balance = balance
        self.transactions = []

    def deposit(self, amount):
        self.balance += amount
        self.transactions.append(f"+{amount}")
        return self

    def withdraw(self, amount):
        if amount <= self.balance:
            self.balance -= amount
            self.transactions.append(f"-{amount}")
        return self

    def display(self):
        print(f"Balance: ${self.balance}")
        print(f"Transactions: {self.transactions}")
        return self

# Chain method calls
account = BankAccount(100)
account.deposit(50).deposit(25).withdraw(30).display()

print("\n=== self Refers to Same Object ===")

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

    def check_identity(self, other_obj):
        # Compare if self and other_obj are same object
        if self is other_obj:
            print(f"self IS the same object as {other_obj.name}")
        else:
            print(f"self is DIFFERENT from {other_obj.name}")

obj1 = IdentityChecker("Object1")
obj2 = IdentityChecker("Object2")

obj1.check_identity(obj1)  # Same object
obj1.check_identity(obj2)  # Different objects

print("\n=== self in String Methods ===")

class Book:
    def __init__(self, title, author, pages):
        self.title = title
        self.author = author
        self.pages = pages

    def __str__(self):
        return f"'{self.title}' by {self.author}"

    def __repr__(self):
        return f"Book({self.title!r}, {self.author!r}, {self.pages})"

book = Book("Python Guide", "John Doe", 300)
print(book)  # Calls __str__
print(repr(book))  # Calls __repr__
  1. print("=== Accessing Attributes via self === ")

    3print("=== Accessing Attributes via self ===\n")45class Person:6    def __init__(self, name, age):7        self.name = name  #?createattr8        self.age = age9    10    def introduce(self):11        # Access attributes with self.  #?accessattr12        print(f"Hi, I'm {self.name} and I'm {self.age} years old.")13    14    def have_birthday(self):15        self.age += 1  #?modifyattr16        print(f"Happy birthday {self.name}! Now {self.age} years old.")17    18    def compare_age(self, other):  #?compareother19        if self.age > other.age:20            return f"{self.name} is older than {other.name}"21        elif self.age < other.age:22            return f"{self.name} is younger than {other.name}"23        else:24            return f"{self.name} and {other.name} are the same age"2526alice = Person("Alice", 30)27bob = Person("Bob", 25)
    output=== Accessing Attributes via self ===
  2. self.name ← Alice, self.age ← 30

    pass 1 of 2
    5class Person:6    def __init__(self⟨Person A⟩, nameAlice, age30):7        self.name→ Alice = nameAlice  #?createattr8        self.age→ 30 = age30
  3. alice ← ⟨Person A⟩

    26alice→ ⟨Person A⟩ = Person("Alice", 30)27bob = Person("Bob", 25)
  4. self.name ← Bob, self.age ← 25

    pass 2 of 2
    5class Person:6    def __init__(self⟨Person B⟩, nameBob, age25):7        self.name→ Bob = nameBob  #?createattr8        self.age→ 25 = age25
  5. bob ← ⟨Person B⟩

    26alice = Person("Alice", 30)27bob→ ⟨Person B⟩ = Person("Bob", 25)2829alice⟨Person A⟩.introduce()30bob.introduce()
  6. def introduce(self): # Access attributes with self. #?accessa…

    pass 1 of 2
    10def introduce(self⟨Person A⟩):11    # Access attributes with self.  #?accessattr12    print(f"Hi, I'm {self.nameAlice} and I'm {self.age30} years old.")
    outputHi, I'm Alice and I'm 30 years old.
  7. alice.introduce()

    29alice⟨Person A⟩.introduce()30bob⟨Person B⟩.introduce()31alice.have_birthday()
  8. def introduce(self): # Access attributes with self. #?accessa…

    pass 2 of 2
    10def introduce(self⟨Person B⟩):11    # Access attributes with self.  #?accessattr12    print(f"Hi, I'm {self.nameBob} and I'm {self.age25} years old.")
    outputHi, I'm Bob and I'm 25 years old.
  9. bob.introduce()

    29alice.introduce()30bob⟨Person B⟩.introduce()31alice⟨Person A⟩.have_birthday()32print(alice.compare_age(bob))
  10. self.age ← 31

    14def have_birthday(self⟨Person A⟩):15    self.age→ 31 += 1  #?modifyattr16    print(f"Happy birthday {self.nameAlice}! Now {self.age31} years old.")
    outputHappy birthday Alice! Now 31 years old.
  11. alice.have_birthday()

    30bob.introduce()31alice⟨Person A⟩.have_birthday()32print(alice⟨Person A⟩.compare_age(bob⟨Person B⟩))
  12. def compare_age(self, other): #?compareother

    18def compare_age(self⟨Person A⟩, other⟨Person B⟩):  #?compareother19    if self.age > other.age:20        return f"{self.name} is older than {other.name}"
  13. if self.age > other.age:

    18def compare_age(self, other):  #?compareother19    if self.age31 > other.age25:20        return f"{self.nameAlice} is older than {other.nameBob}"21    elif self.age < other.age:
  14. print(alice.compare_age(bob))

    31alice.have_birthday()32print(alice⟨Person A⟩.compare_age(bob⟨Person B⟩))3334print("\n=== Accessing Multiple Attributes ===")3536class Rectangle:37    def __init__(self, width, height):38        self.width = width39        self.height = height40    41    def area(self):42        return self.width * self.height  #?multiattr43    44    def perimeter(self):45        return 2 * (self.width + self.height)46    47    def is_square(self):48        return self.width == self.height  #?compareattrs49    50    def resize(self, factor):51        self.width *= factor  #?modifymulti52        self.height *= factor53    54    def describe(self):55        shape = "square" if self.is_square() else "rectangle"  #?callmethod56        return f"{shape} {self.width}x{self.height}, area={self.area()}"5758rect = Rectangle(4, 4)59print(rect.describe())
    outputAlice is older than Bob
    
    === Accessing Multiple Attributes ===
  15. self.width ← 4, self.height ← 4

    36class Rectangle:37    def __init__(self⟨Rectangle C⟩, width4, height4):38        self.width→ 4 = width439        self.height→ 4 = height4
  16. rect ← ⟨Rectangle C⟩

    58rect→ ⟨Rectangle C⟩ = Rectangle(4, 4)59print(rect⟨Rectangle C⟩.describe())60rect.resize(2)
  17. def describe(self):

    pass 1 of 2
    54def describe(self⟨Rectangle C⟩):55    shape = "square" if self.is_square() else "rectangle"  #?callmethod56    return f"{shape} {self.width}x{self.height}, area={self.area()}"
  18. def is_square(self):

    pass 1 of 2
    47def is_square(self⟨Rectangle C⟩):48    return self.width4 == self.height4  #?compareattrs
  19. shape ← square

    54def describe(self):55    shape→ square = "square" if self.is_square() else "rectangle"  #?callmethod56    return f"{shapesquare} {self.width4}x{self.height4}, area={self.area()}"
  20. def area(self):

    pass 1 of 2
    41def area(self⟨Rectangle C⟩):42    return self.width4 * self.height4  #?multiattr
  21. print(rect.describe())

    58rect = Rectangle(4, 4)59print(rect⟨Rectangle C⟩.describe())60rect⟨Rectangle C⟩.resize(2)61print(rect.describe())
    outputsquare 4x4, area=16
  22. self.width ← 8, self.height ← 8

    50def resize(self⟨Rectangle C⟩, factor2):51    self.width→ 8 *= factor2  #?modifymulti52    self.height→ 8 *= factor2
  23. rect.resize(2)

    59print(rect.describe())60rect⟨Rectangle C⟩.resize(2)61print(rect⟨Rectangle C⟩.describe())
  24. def describe(self):

    pass 2 of 2
    54def describe(self⟨Rectangle C⟩):55    shape = "square" if self.is_square() else "rectangle"  #?callmethod56    return f"{shape} {self.width}x{self.height}, area={self.area()}"
  25. def is_square(self):

    pass 2 of 2
    47def is_square(self⟨Rectangle C⟩):48    return self.width8 == self.height8  #?compareattrs
  26. shape ← square

    54def describe(self):55    shape→ square = "square" if self.is_square() else "rectangle"  #?callmethod56    return f"{shapesquare} {self.width8}x{self.height8}, area={self.area()}"
  27. def area(self):

    pass 2 of 2
    41def area(self⟨Rectangle C⟩):42    return self.width8 * self.height8  #?multiattr
  28. print(rect.describe())

    60rect.resize(2)61print(rect⟨Rectangle C⟩.describe())6263print("\n=== self in Chained Operations ===")6465class BankAccount:66    def __init__(self, balance=0):67        self.balance = balance68        self.transactions = []69    70    def deposit(self, amount):71        self.balance += amount72        self.transactions.append(f"+{amount}")  #?appendlist73        return self  #?returnself74    75    def withdraw(self, amount):76        if amount <= self.balance:77            self.balance -= amount78            self.transactions.append(f"-{amount}")79        return self80    81    def display(self):82        print(f"Balance: ${self.balance}")83        print(f"Transactions: {self.transactions}")84        return self8586# Chain method calls  #?chaining87account = BankAccount(100)88account.deposit(50).deposit(25).withdraw(30).display()
    outputsquare 8x8, area=64
    
    === self in Chained Operations ===
  29. self.balance ← 100, self.transactions ← []

    65class BankAccount:66    def __init__(self⟨BankAccount D⟩, balance100=0):67        self.balance→ 100 = balance10068        self.transactions→ [] = []
  30. account ← ⟨BankAccount D⟩

    86# Chain method calls  #?chaining87account→ ⟨BankAccount D⟩ = BankAccount(100)88account⟨BankAccount D⟩.deposit(50).deposit(25).withdraw(30).display()
  31. self.balance ← 150, self.transactions ← ['+50']

    pass 1 of 2
    70def deposit(self⟨BankAccount D⟩, amount50):71    self.balance→ 150 += amount5072    self.transactions→ ['+50'].append(f"+{amount50}")  #?appendlist73    return self  #?returnself
  32. self.balance ← 175, self.transactions ← ['+50', '+25']

    pass 2 of 2
    70def deposit(self⟨BankAccount D⟩, amount25):71    self.balance→ 175 += amount2572    self.transactions→ ['+50', '+25'].append(f"+{amount25}")  #?appendlist73    return self  #?returnself
  33. def withdraw(self, amount):

    75def withdraw(self⟨BankAccount D⟩, amount30):76    if amount <= self.balance:77        self.balance -= amount
  34. self.balance ← 145, self.transactions ← ['+50', '+25', '-30']

    75def withdraw(self, amount):76    if amount30 <= self.balance175:77        self.balance→ 145 -= amount3078        self.transactions→ ['+50', '+25', '-30'].append(f"-{amount30}")79    return self
  35. return self

    78    self.transactions.append(f"-{amount}")79return self
  36. def display(self):

    81def display(self⟨BankAccount D⟩):82    print(f"Balance: ${self.balance145}")83    print(f"Transactions: {self.transactions['+50', '+25', '-30']}")84    return self
    outputBalance: $145
    Transactions: ['+50', '+25', '-30']
  37. account.deposit(50).deposit(25).withdraw(30).display()

    87account = BankAccount(100)88account⟨BankAccount D⟩.deposit(50).deposit(25).withdraw(30).display()8990print("\n=== self Refers to Same Object ===")9192class IdentityChecker:93    def __init__(self, name):94        self.name = name95    96    def check_identity(self, other_obj):97        # Compare if self and other_obj are same object  #?identitycheck98        if self is other_obj:99            print(f"self IS the same object as {other_obj.name}")100        else:101            print(f"self is DIFFERENT from {other_obj.name}")102103obj1 = IdentityChecker("Object1")104obj2 = IdentityChecker("Object2")
    output
    === self Refers to Same Object ===
  38. self.name ← Object1

    pass 1 of 2
    92class IdentityChecker:93    def __init__(self⟨IdentityChecker E⟩, nameObject1):94        self.name→ Object1 = nameObject1
  39. obj1 ← ⟨IdentityChecker E⟩

    103obj1→ ⟨IdentityChecker E⟩ = IdentityChecker("Object1")104obj2 = IdentityChecker("Object2")
  40. self.name ← Object2

    pass 2 of 2
    92class IdentityChecker:93    def __init__(self⟨IdentityChecker F⟩, nameObject2):94        self.name→ Object2 = nameObject2
  41. obj2 ← ⟨IdentityChecker F⟩

    103obj1 = IdentityChecker("Object1")104obj2→ ⟨IdentityChecker F⟩ = IdentityChecker("Object2")105106obj1⟨IdentityChecker E⟩.check_identity(obj1)  # Same object107obj1.check_identity(obj2)  # Different objects
  42. def check_identity(self, other_obj): # Compare if self and oth…

    pass 1 of 2
    96def check_identity(self⟨IdentityChecker E⟩, other_obj⟨IdentityChecker E⟩):97    # Compare if self and other_obj are same object  #?identitycheck98    if self is other_obj:99        print(f"self IS the same object as {other_obj.name}")
  43. if self is other_obj:

    97# Compare if self and other_obj are same object  #?identitycheck98if self is other_obj⟨IdentityChecker E⟩:99    print(f"self IS the same object as {other_obj.nameObject1}")100else:
    outputself IS the same object as Object1
  44. obj1.check_identity(obj1) # Same object

    106obj1⟨IdentityChecker E⟩.check_identity(obj1)  # Same object107obj1⟨IdentityChecker E⟩.check_identity(obj2⟨IdentityChecker F⟩)  # Different objects
  45. def check_identity(self, other_obj): # Compare if self and oth…

    pass 2 of 2
    96def check_identity(self⟨IdentityChecker E⟩, other_obj⟨IdentityChecker F⟩):97    # Compare if self and other_obj are same object  #?identitycheck98    if self is other_obj:99        print(f"self IS the same object as {other_obj.name}")
  46. else:

    98if self is other_obj:99    print(f"self IS the same object as {other_obj.name}")100else:101    print(f"self is DIFFERENT from {other_obj.nameObject2}")
    outputself is DIFFERENT from Object2
  47. obj1.check_identity(obj2) # Different objects

    106obj1.check_identity(obj1)  # Same object107obj1⟨IdentityChecker E⟩.check_identity(obj2⟨IdentityChecker F⟩)  # Different objects108109print("\n=== self in String Methods ===")110111class Book:112    def __init__(self, title, author, pages):113        self.title = title114        self.author = author115        self.pages = pages116    117    def __str__(self):  #?strmethod118        return f"'{self.title}' by {self.author}"119    120    def __repr__(self):  #?reprmethod121        return f"Book({self.title!r}, {self.author!r}, {self.pages})"122123book = Book("Python Guide", "John Doe", 300)124print(book)  # Calls __str__
    output
    === self in String Methods ===
  48. self.title ← Python Guide, self.author ← John Doe, self.pages ← 300

    111class Book:112    def __init__(self(empty), titlePython Guide, authorJohn Doe, pages300):113        self.title→ Python Guide = titlePython Guide114        self.author→ John Doe = authorJohn Doe115        self.pages→ 300 = pages300
  49. book ← 'Python Guide' by John Doe

    123book→ 'Python Guide' by John Doe = Book("Python Guide", "John Doe", 300)124print(book'Python Guide' by John Doe)  # Calls __str__125print(repr(book'Python Guide' by John Doe))  # Calls __repr__126#@help createattr
    output'Python Guide' by John Doe
    Book('Python Guide', 'John Doe', 300)

self.name accesses attribute. self.other_method() calls another method.

self vs class attributes

Instance attributes vs class-level attributes.

self_vs_class.py
Replay: real traced execution (multi-file project)
# Instance (self) vs Class Attributes

print("=== Class Attributes ===\n")

class Dog:
    species = "Canis familiaris"
    count = 0

    def __init__(self, name):
        self.name = name
        Dog.count += 1

# Create dogs
buddy = Dog("Buddy")
max_dog = Dog("Max")
rex = Dog("Rex")

# Class attribute - shared
print(f"Dog.species: {Dog.species}")
print(f"buddy.species: {buddy.species}")  # Access via instance
print(f"max_dog.species: {max_dog.species}")  # Same value

# Instance attribute - unique
print(f"\nbuddyname: {buddy.name}")
print(f"max_dog.name: {max_dog.name}")

# Count is shared
print(f"\nTotal dogs created: {Dog.count}")

print("\n=== Modifying Class vs Instance ===")

class Cat:
    species = "Felis catus"

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

whiskers = Cat("Whiskers")
luna = Cat("Luna")

print(f"Before: whiskers.species = {whiskers.species}")
print(f"Before: luna.species = {luna.species}")

# Modify class attribute
Cat.species = "Felis silvestris catus"
print(f"\nAfter Cat.species change:")
print(f"whiskers.species = {whiskers.species}")  # Changed!
print(f"luna.species = {luna.species}")  # Changed!

# Modify on instance - creates instance attribute!
whiskers.species = "Special Cat"
print(f"\nAfter whiskers.species = 'Special Cat':")
print(f"whiskers.species = {whiskers.species}")  # Instance attr
print(f"luna.species = {luna.species}")  # Still class attr
print(f"Cat.species = {Cat.species}")  # Class attr unchanged

print("\n=== self vs Class in Methods ===")

class Counter:
    total = 0  # Class attribute

    def __init__(self, name):
        self.name = name  # Instance attribute
        self.count = 0  # Instance attribute
        Counter.total += 1

    def increment(self):
        self.count += 1
        Counter.total += 1  # Or self.__class__.total

    def display(self):
        print(f"{self.name}: count={self.count}, total={Counter.total}")

c1 = Counter("Counter1")
c2 = Counter("Counter2")

c1.increment()
c1.increment()
c2.increment()

c1.display()
c2.display()

print("\n=== When to Use Each ===")

class Player:
    # Class attributes: shared by all
    max_level = 100
    all_players = []

    def __init__(self, name, level=1):
        # Instance attributes: unique to each
        self.name = name
        self.level = level
        Player.all_players.append(self)

    def level_up(self):
        if self.level < Player.max_level:
            self.level += 1

    @classmethod
    def get_player_count(cls):
        return len(cls.all_players)

p1 = Player("Alice", 10)
p2 = Player("Bob", 5)

print(f"Max level (class): {Player.max_level}")
print(f"Alice's level (instance): {p1.level}")
print(f"Bob's level (instance): {p2.level}")
print(f"Total players: {Player.get_player_count()}")

p1.level_up()
print(f"After level up - Alice: {p1.level}, Bob: {p2.level}")

print("\n=== Summary ===")
print("""
Class Attribute:
  - Defined in class body (not __init__)
  - Shared by ALL instances
  - Access: ClassName.attr or self.attr
  - Modify: Use ClassName.attr

Instance Attribute:
  - Defined with self.attr = value
  - Unique to EACH instance
  - Access: self.attr
  - Modify: self.attr = new_value
""")
  1. species ← (empty), count ← (empty)

    3print("=== Class Attributes ===\n")45class Dog:6    species→ (empty) = "Canis familiaris"  #?classattr7    count→ (empty) = 0  #?classcount8    9    def __init__(self, name):10        self.name = name  #?instanceattr11        Dog.count += 1  #?incrementclass1213# Create dogs14buddy = Dog("Buddy")15max_dog = Dog("Max")
    output=== Class Attributes ===
  2. self.name ← Buddy, Dog.count ← 1

    pass 1 of 3
    9def __init__(self⟨Dog A⟩, nameBuddy):10    self.name→ Buddy = nameBuddy  #?instanceattr11    Dog.count→ 1 += 1  #?incrementclass
    All 3 passes — pass 1 is the card above
    passselfnameself.nameDog.count
    1⟨Dog A⟩BuddyBuddy0 1
    2⟨Dog B⟩MaxMax1 2
    3⟨Dog C⟩RexRex2 3
  3. buddy ← ⟨Dog A⟩

    13# Create dogs14buddy→ ⟨Dog A⟩ = Dog("Buddy")15max_dog = Dog("Max")16rex = Dog("Rex")
  4. max_dog ← ⟨Dog B⟩

    14buddy = Dog("Buddy")15max_dog→ ⟨Dog B⟩ = Dog("Max")16rex = Dog("Rex")
  5. rex ← ⟨Dog C⟩, species ← (empty)

    15max_dog = Dog("Max")16rex→ ⟨Dog C⟩ = Dog("Rex")1718# Class attribute - shared  #?sharedaccess19print(f"Dog.species: {Dog.speciesCanis familiaris}")20print(f"buddy.species: {buddy.speciesCanis familiaris}")  # Access via instance21print(f"max_dog.species: {max_dog.speciesCanis familiaris}")  # Same value2223# Instance attribute - unique  #?uniqueaccess24print(f"\nbuddyname: {buddy.nameBuddy}")25print(f"max_dog.name: {max_dog.nameMax}")2627# Count is shared  #?sharedcount28print(f"\nTotal dogs created: {Dog.count3}")2930print("\n=== Modifying Class vs Instance ===")3132class Cat:33    species→ (empty) = "Felis catus"34    35    def __init__(self, name):36        self.name = name3738whiskers = Cat("Whiskers")39luna = Cat("Luna")
    outputDog.species: Canis familiaris
    buddy.species: Canis familiaris
    max_dog.species: Canis familiaris
    
    buddyname: Buddy
    max_dog.name: Max
    
    Total dogs created: 3
    
    === Modifying Class vs Instance ===
  6. self.name ← Whiskers

    pass 1 of 2
    35def __init__(self⟨Cat D⟩, nameWhiskers):36    self.name→ Whiskers = nameWhiskers
  7. whiskers ← ⟨Cat D⟩

    38whiskers→ ⟨Cat D⟩ = Cat("Whiskers")39luna = Cat("Luna")
  8. self.name ← Luna

    pass 2 of 2
    35def __init__(self⟨Cat E⟩, nameLuna):36    self.name→ Luna = nameLuna
  9. luna ← ⟨Cat E⟩, Cat.species ← Felis silvestris catus, whiskers.species ← Special Cat

    38whiskers = Cat("Whiskers")39luna→ ⟨Cat E⟩ = Cat("Luna")4041print(f"Before: whiskers.species = {whiskers.speciesFelis catus}")42print(f"Before: luna.species = {luna.speciesFelis catus}")4344# Modify class attribute  #?modifyclass45Cat.species→ Felis silvestris catus = "Felis silvestris catus"46print(f"\nAfter Cat.species change:")47print(f"whiskers.species = {whiskers.speciesFelis silvestris catus}")  # Changed!48print(f"luna.species = {luna.speciesFelis silvestris catus}")  # Changed!4950# Modify on instance - creates instance attribute!  #?shadowclass51whiskers.species→ Special Cat = "Special Cat"  #?creates_instance_attr52print(f"\nAfter whiskers.species = 'Special Cat':")53print(f"whiskers.species = {whiskers.speciesSpecial Cat}")  # Instance attr54print(f"luna.species = {luna.speciesFelis silvestris catus}")  # Still class attr55print(f"Cat.species = {Cat.speciesFelis silvestris catus}")  # Class attr unchanged5657print("\n=== self vs Class in Methods ===")5859class Counter:60    total→ (empty) = 0  # Class attribute  #?classtotal61    62    def __init__(self, name):63        self.name = name  # Instance attribute  #?selfname64        self.count = 0  # Instance attribute65        Counter.total += 166    67    def increment(self):68        self.count += 1  #?selfincrement69        Counter.total += 1  # Or self.__class__.total  #?classincrement70    71    def display(self):72        print(f"{self.name}: count={self.count}, total={Counter.total}")7374c1 = Counter("Counter1")75c2 = Counter("Counter2")
    outputBefore: whiskers.species = Felis catus
    Before: luna.species = Felis catus
    
    After Cat.species change:
    whiskers.species = Felis silvestris catus
    luna.species = Felis silvestris catus
    
    After whiskers.species = 'Special Cat':
    whiskers.species = Special Cat
    luna.species = Felis silvestris catus
    Cat.species = Felis silvestris catus
    
    === self vs Class in Methods ===
  10. self.name ← Counter1, self.count ← 0, Counter.total ← 1

    pass 1 of 2
    62def __init__(self⟨Counter F⟩, nameCounter1):63    self.name→ Counter1 = nameCounter1  # Instance attribute  #?selfname64    self.count→ 0 = 0  # Instance attribute65    Counter.total→ 1 += 1
  11. c1 ← ⟨Counter F⟩

    74c1→ ⟨Counter F⟩ = Counter("Counter1")75c2 = Counter("Counter2")
  12. self.name ← Counter2, self.count ← 0, Counter.total ← 2

    pass 2 of 2
    62def __init__(self⟨Counter G⟩, nameCounter2):63    self.name→ Counter2 = nameCounter2  # Instance attribute  #?selfname64    self.count→ 0 = 0  # Instance attribute65    Counter.total→ 2 += 1
  13. c2 ← ⟨Counter G⟩

    74c1 = Counter("Counter1")75c2→ ⟨Counter G⟩ = Counter("Counter2")7677c1⟨Counter F⟩.increment()78c1.increment()
  14. self.count ← 1, Counter.total ← 3

    pass 1 of 3
    67def increment(self⟨Counter F⟩):68    self.count→ 1 += 1  #?selfincrement69    Counter.total→ 3 += 1  # Or self.__class__.total  #?classincrement
    All 3 passes — pass 1 is the card above
    passselfself.countCounter.total
    1⟨Counter F⟩0 12 3
    2⟨Counter F⟩1 23 4
    3⟨Counter G⟩0 14 5
  15. c1.increment()

    77c1⟨Counter F⟩.increment()78c1⟨Counter F⟩.increment()79c2.increment()
  16. c1.increment()

    77c1.increment()78c1⟨Counter F⟩.increment()79c2⟨Counter G⟩.increment()
  17. c2.increment()

    78c1.increment()79c2⟨Counter G⟩.increment()8081c1⟨Counter F⟩.display()82c2.display()
  18. def display(self):

    pass 1 of 2
    71def display(self⟨Counter F⟩):72    print(f"{self.nameCounter1}: count={self.count2}, total={Counter.total5}")
    outputCounter1: count=2, total=5
  19. c1.display()

    81c1⟨Counter F⟩.display()82c2⟨Counter G⟩.display()
  20. def display(self):

    pass 2 of 2
    71def display(self⟨Counter G⟩):72    print(f"{self.nameCounter2}: count={self.count1}, total={Counter.total5}")
    outputCounter2: count=1, total=5
  21. max_level ← (empty), all_players ← (empty)

    81c1.display()82c2⟨Counter G⟩.display()8384print("\n=== When to Use Each ===")8586class Player:87    # Class attributes: shared by all  #?whenshared88    max_level→ (empty) = 10089    all_players→ (empty) = []90    91    def __init__(self, name, level=1):92        # Instance attributes: unique to each  #?whenunique93        self.name = name94        self.level = level95        Player.all_players.append(self)  #?trackinlist96    97    def level_up(self):98        if self.level < Player.max_level:  #?accessboth99            self.level += 1100    101    @classmethod102    def get_player_count(cls):  #?classmethod103        return len(cls.all_players)104105p1 = Player("Alice", 10)106p2 = Player("Bob", 5)
    output
    === When to Use Each ===
  22. self.name ← Alice, self.level ← 10, Player.all_players ← [⟨Player H⟩]

    pass 1 of 2
    91def __init__(self⟨Player H⟩, nameAlice, level10=1):92    # Instance attributes: unique to each  #?whenunique93    self.name→ Alice = nameAlice94    self.level→ 10 = level1095    Player.all_players→ [⟨Player H⟩].append(self)  #?trackinlist
  23. p1 ← ⟨Player H⟩

    105p1→ ⟨Player H⟩ = Player("Alice", 10)106p2 = Player("Bob", 5)
  24. self.name ← Bob, self.level ← 5, Player.all_players ← [⟨Player H⟩, ⟨Player I⟩]

    pass 2 of 2
    91def __init__(self⟨Player I⟩, nameBob, level5=1):92    # Instance attributes: unique to each  #?whenunique93    self.name→ Bob = nameBob94    self.level→ 5 = level595    Player.all_players→ [⟨Player H⟩, ⟨Player I⟩].append(self)  #?trackinlist
  25. p2 ← ⟨Player I⟩

    105p1 = Player("Alice", 10)106p2→ ⟨Player I⟩ = Player("Bob", 5)107108print(f"Max level (class): {Player.max_level100}")109print(f"Alice's level (instance): {p1.level10}")110print(f"Bob's level (instance): {p2.level5}")111print(f"Total players: {Player<class '__main__.Player'>.get_player_count()}")
    outputMax level (class): 100
    Alice's level (instance): 10
    Bob's level (instance): 5
  26. def get_player_count(cls): #?classmethod

    101@classmethod102def get_player_count(cls<class '__main__.Player'>):  #?classmethod103    return len(cls.all_players[⟨Player H⟩, ⟨Player I⟩])
  27. print(f"Total players: {Player.get_player_count()}")

    110print(f"Bob's level (instance): {p2.level}")111print(f"Total players: {Player<class '__main__.Player'>.get_player_count()}")112113p1⟨Player H⟩.level_up()114print(f"After level up - Alice: {p1.level}, Bob: {p2.level}")
    outputTotal players: 2
  28. def level_up(self):

    97def level_up(self⟨Player H⟩):98    if self.level < Player.max_level:  #?accessboth99        self.level += 1
  29. self.level ← 11

    97def level_up(self):98    if self.level10 < Player.max_level100:  #?accessboth99        self.level→ 11 += 1
  30. p1.level_up()

    113p1⟨Player H⟩.level_up()114print(f"After level up - Alice: {p1.level11}, Bob: {p2.level5}")115116print("\n=== Summary ===")117print("""118Class Attribute:119  - Defined in class body (not __init__)120  - Shared by ALL instances121  - Access: ClassName.attr or self.attr122  - Modify: Use ClassName.attr123124Instance Attribute:125  - Defined with self.attr = value126  - Unique to EACH instance127  - Access: self.attr128  - Modify: self.attr = new_value129""")130#@help classattr
    outputAfter level up - Alice: 11, Bob: 5
    
    === Summary ===
    
    Class Attribute:
      - Defined in class body (not __init__)
      - Shared by ALL instances
      - Access: ClassName.attr or self.attr
      - Modify: Use ClassName.attr
    
    Instance Attribute:
      - Defined with self.attr = value
      - Unique to EACH instance
      - Access: self.attr
      - Modify: self.attr = new_value

self.x is per-instance. ClassName.x or bare x in class body is shared.

class attribute Attribute shared by all instances: defined in class body, not in `__init__`.

Calling other methods

Use self to invoke other methods on the same object.

self_method_calls.py
Replay: real traced execution (multi-file project)
# Using self to Call Other Methods

print("=== Calling Methods via self ===\n")

class Calculator:
    def __init__(self, value=0):
        self.value = value

    def add(self, n):
        self.value += n
        return self

    def subtract(self, n):
        self.value -= n
        return self

    def multiply(self, n):
        self.value *= n
        return self

    def square(self):
        self.multiply(self.value)  # Call self.multiply()
        return self

    def double(self):
        self.multiply(2)
        return self

calc = Calculator(5)
calc.square()  # 5 * 5 = 25
print(f"5 squared = {calc.value}")

calc = Calculator(3)
calc.double().double()  # 3 * 2 * 2 = 12
print(f"3 doubled twice = {calc.value}")

print("\n=== Helper Methods ===")

class ShoppingCart:
    def __init__(self):
        self.items = []

    def add_item(self, name, price, qty=1):
        self.items.append({"name": name, "price": price, "qty": qty})
        self._log_action(f"Added {qty}x {name}")

    def remove_item(self, name):
        for item in self.items:
            if item["name"] == name:
                self.items.remove(item)
                self._log_action(f"Removed {name}")
                return
        self._log_action(f"Not found: {name}")

    def _log_action(self, message):
        print(f"[Cart] {message}")

    def _calculate_subtotal(self):
        return sum(i["price"] * i["qty"] for i in self.items)

    def get_total(self):
        subtotal = self._calculate_subtotal()
        return subtotal

cart = ShoppingCart()
cart.add_item("Apple", 1.50, 3)
cart.add_item("Bread", 2.50)
cart.remove_item("Milk")  # Not found
print(f"Total: ${cart.get_total():.2f}")

print("\n=== Validation Methods ===")

class User:
    def __init__(self, username, email, age):
        self.username = username
        self.email = email
        self.age = age

    def _is_valid_username(self):
        return len(self.username) >= 3

    def _is_valid_email(self):
        return "@" in self.email and "." in self.email

    def _is_valid_age(self):
        return 0 <= self.age <= 150

    def is_valid(self):
        return (self._is_valid_username() and
                self._is_valid_email() and
                self._is_valid_age())

    def validation_errors(self):
        errors = []
        if not self._is_valid_username():
            errors.append("Username too short")
        if not self._is_valid_email():
            errors.append("Invalid email format")
        if not self._is_valid_age():
            errors.append("Invalid age")
        return errors

user1 = User("alice", "alice@email.com", 25)
user2 = User("ab", "not-an-email", 200)

print(f"User1 valid: {user1.is_valid()}")
print(f"User2 valid: {user2.is_valid()}")
print(f"User2 errors: {user2.validation_errors()}")

print("\n=== State Management ===")

class Player:
    def __init__(self, name, health=100):
        self.name = name
        self.health = health
        self.alive = True

    def take_damage(self, amount):
        self.health -= amount
        print(f"{self.name} takes {amount} damage! Health: {self.health}")
        self._check_death()

    def heal(self, amount):
        if self.alive:
            self.health = min(100, self.health + amount)
            print(f"{self.name} heals {amount}! Health: {self.health}")

    def _check_death(self):
        if self.health <= 0 and self.alive:
            self.alive = False
            self._on_death()

    def _on_death(self):
        print(f"{self.name} has fallen!")

hero = Player("Hero")
hero.take_damage(30)
hero.take_damage(50)
hero.take_damage(30)  # Dies
hero.heal(20)  # Can't heal when dead

print("\n=== Method Delegation ===")

class Report:
    def __init__(self, title, data):
        self.title = title
        self.data = data

    def generate(self, format="text"):
        if format == "text":
            return self._generate_text()
        elif format == "html":
            return self._generate_html()
        else:
            return self._generate_text()

    def _generate_text(self):
        lines = [f"=== {self.title} ==="]
        for key, value in self.data.items():
            lines.append(f"{key}: {value}")
        return "\n".join(lines)

    def _generate_html(self):
        html = [f"<h1>{self.title}</h1>", "<ul>"]
        for key, value in self.data.items():
            html.append(f"  <li>{key}: {value}</li>")
        html.append("</ul>")
        return "\n".join(html)

report = Report("Sales", {"Q1": 1000, "Q2": 1500, "Q3": 1200})
print(report.generate("text"))
print()
print(report.generate("html"))
  1. print("=== Calling Methods via self === ")

    3print("=== Calling Methods via self ===\n")45class Calculator:6    def __init__(self, value=0):7        self.value = value8    9    def add(self, n):10        self.value += n11        return self12    13    def subtract(self, n):14        self.value -= n15        return self16    17    def multiply(self, n):18        self.value *= n19        return self20    21    def square(self):  #?callself22        self.multiply(self.value)  # Call self.multiply()  #?selfcall23        return self24    25    def double(self):26        self.multiply(2)  #?reuse27        return self2829calc = Calculator(5)30calc.square()  # 5 * 5 = 25
    output=== Calling Methods via self ===
  2. self.value ← 5

    pass 1 of 2
    5class Calculator:6    def __init__(self⟨Calculator A⟩, value5=0):7        self.value→ 5 = value5
  3. calc ← ⟨Calculator A⟩

    29calc→ ⟨Calculator A⟩ = Calculator(5)30calc⟨Calculator A⟩.square()  # 5 * 5 = 2531print(f"5 squared = {calc.value}")
  4. def square(self): #?callself

    21def square(self⟨Calculator A⟩):  #?callself22    self.multiply(self.value5)  # Call self.multiply()  #?selfcall23    return self
  5. self.value ← 25

    pass 1 of 3
    17def multiply(self⟨Calculator A⟩, n5):18    self.value→ 25 *= n519    return self
    All 3 passes — pass 1 is the card above
    passselfnself.value
    1⟨Calculator A⟩55 25
    2⟨Calculator B⟩23 6
    3⟨Calculator B⟩26 12
  6. self.value ← 25

    21def square(self):  #?callself22    self.multiply(self.value→ 25)  # Call self.multiply()  #?selfcall23    return self
  7. calc.square() # 5 * 5 = 25

    29calc = Calculator(5)30calc⟨Calculator A⟩.square()  # 5 * 5 = 2531print(f"5 squared = {calc.value25}")3233calc = Calculator(3)34calc.double().double()  # 3 * 2 * 2 = 12
    output5 squared = 25
  8. self.value ← 3

    pass 2 of 2
    5class Calculator:6    def __init__(self⟨Calculator B⟩, value3=0):7        self.value→ 3 = value3
  9. calc ← ⟨Calculator B⟩

    33calc→ ⟨Calculator B⟩ = Calculator(3)34calc⟨Calculator B⟩.double().double()  # 3 * 2 * 2 = 1235print(f"3 doubled twice = {calc.value}")
  10. def double(self):

    pass 1 of 2
    25def double(self⟨Calculator B⟩):26    self.multiply(2)  #?reuse27    return self
  11. self.multiply(2) #?reuse

    25def double(self):26    self.multiply(2)  #?reuse27    return self
  12. def double(self):

    pass 2 of 2
    25def double(self⟨Calculator B⟩):26    self.multiply(2)  #?reuse27    return self
  13. self.multiply(2) #?reuse

    25def double(self):26    self.multiply(2)  #?reuse27    return self
  14. calc.double().double() # 3 * 2 * 2 = 12

    33calc = Calculator(3)34calc⟨Calculator B⟩.double().double()  # 3 * 2 * 2 = 1235print(f"3 doubled twice = {calc.value12}")3637print("\n=== Helper Methods ===")3839class ShoppingCart:40    def __init__(self):41        self.items = []42    43    def add_item(self, name, price, qty=1):44        self.items.append({"name": name, "price": price, "qty": qty})45        self._log_action(f"Added {qty}x {name}")  #?privatehelper46    47    def remove_item(self, name):48        for item in self.items:49            if item["name"] == name:50                self.items.remove(item)51                self._log_action(f"Removed {name}")52                return53        self._log_action(f"Not found: {name}")54    55    def _log_action(self, message):  #?helpermethod56        print(f"[Cart] {message}")57    58    def _calculate_subtotal(self):  #?calchelper59        return sum(i["price"] * i["qty"] for i in self.items)60    61    def get_total(self):62        subtotal = self._calculate_subtotal()  #?usehelper63        return subtotal6465cart = ShoppingCart()66cart.add_item("Apple", 1.50, 3)
    output3 doubled twice = 12
    
    === Helper Methods ===
  15. self.items ← []

    39class ShoppingCart:40    def __init__(self⟨ShoppingCart C⟩):41        self.items→ [] = []
  16. cart ← ⟨ShoppingCart C⟩

    65cart→ ⟨ShoppingCart C⟩ = ShoppingCart()66cart⟨ShoppingCart C⟩.add_item("Apple", 1.50, 3)67cart.add_item("Bread", 2.50)
  17. self.items ← [{'name': 'Apple', 'price': 1.5, 'qty': 3}]

    pass 1 of 2
    43def add_item(self⟨ShoppingCart C⟩, nameApple, price1.5, qty3=1):44    self.items→ [{'name': 'Apple', 'price': 1.5, 'qty': 3}].append({"name": nameApple, "price": price1.5, "qty": qty3})45    self._log_action(f"Added {qty3}x {nameApple}")  #?privatehelper
  18. def _log_action(self, message): #?helpermethod

    pass 1 of 3
    44    self.items.append({"name": name, "price": price, "qty": qty})45    self._log_action(f"Added {qty3}x {nameApple}")  #?privatehelper4647def remove_item(self, name):48    for item in self.items:49        if item["name"] == name:50            self.items.remove(item)51            self._log_action(f"Removed {name}")52            return53    self._log_action(f"Not found: {name}")5455def _log_action(self⟨ShoppingCart C⟩, messageAdded 3x Apple):  #?helpermethod56    print(f"[Cart] {messageAdded 3x Apple}")
    output[Cart] Added 3x Apple
    All 3 passes — pass 1 is the card above
    passmessageqtyname
    1Added 3x Apple3Apple
    2Added 1x Bread1Bread
    3Not found: MilkMilk
  19. cart.add_item("Apple", 1.50, 3)

    65cart = ShoppingCart()66cart⟨ShoppingCart C⟩.add_item("Apple", 1.50, 3)67cart⟨ShoppingCart C⟩.add_item("Bread", 2.50)68cart.remove_item("Milk")  # Not found
  20. self.items ← [{'name': 'Apple', 'price': 1.5, 'qty': 3}, {'name': 'Bread', 'price': 2.5, 'qty': 1}]

    pass 2 of 2
    43def add_item(self⟨ShoppingCart C⟩, nameBread, price2.5, qty1=1):44    self.items→ [{'name': 'Apple', 'price': 1.5, 'qty': 3}, {'name': 'Bread', 'price': 2.5, 'qty': 1}].append({"name": nameBread, "price": price2.5, "qty": qty1})45    self._log_action(f"Added {qty1}x {nameBread}")  #?privatehelper
  21. cart.add_item("Bread", 2.50)

    66cart.add_item("Apple", 1.50, 3)67cart⟨ShoppingCart C⟩.add_item("Bread", 2.50)68cart⟨ShoppingCart C⟩.remove_item("Milk")  # Not found69print(f"Total: ${cart.get_total():.2f}")
  22. def remove_item(self, name):

    47def remove_item(self⟨ShoppingCart C⟩, nameMilk):48    for item in self.items:49        if item["name"] == name:
  23. for item in self.items:

    pass 1 of 2
    47def remove_item(self, name):48    for item{'name': 'Apple', 'price': 1.5, 'qty': 3} in self.items[{'name': 'Apple', 'price': 1.5, 'qty': 3}, {'name': 'Bread', 'price': 2.5, 'qty': 1}]:49        if item["name"] == name:50            self.items.remove(item)
  24. for item in self.items:

    pass 2 of 2
    47def remove_item(self, name):48    for item{'name': 'Bread', 'price': 2.5, 'qty': 1} in self.items[{'name': 'Apple', 'price': 1.5, 'qty': 3}, {'name': 'Bread', 'price': 2.5, 'qty': 1}]:49        if item["name"] == name:50            self.items.remove(item)
  25. self._log_action(f"Not found: {name}")

    52        return53self._log_action(f"Not found: {nameMilk}")
  26. cart.remove_item("Milk") # Not found

    67cart.add_item("Bread", 2.50)68cart⟨ShoppingCart C⟩.remove_item("Milk")  # Not found69print(f"Total: ${cart⟨ShoppingCart C⟩.get_total():.2f}")
  27. def get_total(self):

    61def get_total(self⟨ShoppingCart C⟩):62    subtotal = self._calculate_subtotal()  #?usehelper63    return subtotal
  28. def _calculate_subtotal(self): #?calchelper

    58def _calculate_subtotal(self⟨ShoppingCart C⟩):  #?calchelper59    return sum(i["price"](empty) * i["qty"](empty) for i in self.items[{'name': 'Apple', 'price': 1.5, 'qty': 3}, {'name': 'Bread', 'price': 2.5, 'qty': 1}])
  29. subtotal ← 7.0

    61def get_total(self):62    subtotal→ 7.0 = self._calculate_subtotal()  #?usehelper63    return subtotal7.0
  30. print(f"Total: ${cart.get_total():.2f}")

    68cart.remove_item("Milk")  # Not found69print(f"Total: ${cart⟨ShoppingCart C⟩.get_total():.2f}")7071print("\n=== Validation Methods ===")7273class User:74    def __init__(self, username, email, age):75        self.username = username76        self.email = email77        self.age = age78    79    def _is_valid_username(self):  #?validatemethod80        return len(self.username) >= 381    82    def _is_valid_email(self):83        return "@" in self.email and "." in self.email84    85    def _is_valid_age(self):86        return 0 <= self.age <= 15087    88    def is_valid(self):  #?combinedvalid89        return (self._is_valid_username() and  #?callvalidators90                self._is_valid_email() and 91                self._is_valid_age())92    93    def validation_errors(self):  #?errorlist94        errors = []95        if not self._is_valid_username():96            errors.append("Username too short")97        if not self._is_valid_email():98            errors.append("Invalid email format")99        if not self._is_valid_age():100            errors.append("Invalid age")101        return errors102103user1 = User("alice", "alice@email.com", 25)104user2 = User("ab", "not-an-email", 200)
    outputTotal: $7.00
    
    === Validation Methods ===
  31. self.username ← alice, self.email ← alice@email.com, self.age ← 25

    pass 1 of 2
    73class User:74    def __init__(self⟨User D⟩, usernamealice, emailalice@email.com, age25):75        self.username→ alice = usernamealice76        self.email→ alice@email.com = emailalice@email.com77        self.age→ 25 = age25
  32. user1 ← ⟨User D⟩

    103user1→ ⟨User D⟩ = User("alice", "alice@email.com", 25)104user2 = User("ab", "not-an-email", 200)
  33. self.username ← ab, self.email ← not-an-email, self.age ← 200

    pass 2 of 2
    73class User:74    def __init__(self⟨User A⟩, usernameab, emailnot-an-email, age200):75        self.username→ ab = usernameab76        self.email→ not-an-email = emailnot-an-email77        self.age→ 200 = age200
  34. user2 ← ⟨User A⟩

    103user1 = User("alice", "alice@email.com", 25)104user2→ ⟨User A⟩ = User("ab", "not-an-email", 200)105106print(f"User1 valid: {user1⟨User D⟩.is_valid()}")107print(f"User2 valid: {user2.is_valid()}")
  35. def is_valid(self): #?combinedvalid

    pass 1 of 2
    88def is_valid(self⟨User D⟩):  #?combinedvalid89    return (self._is_valid_username() and  #?callvalidators90            self._is_valid_email() and 91            self._is_valid_age())
  36. def _is_valid_username(self): #?validatemethod

    pass 1 of 3
    79def _is_valid_username(self⟨User D⟩):  #?validatemethod80    return len(self.usernamealice) >= 3
    All 3 passes — pass 1 is the card above
    passselfself.usernameself.emailself.ageerrors
    1⟨User D⟩alicealice@email.com25
    2⟨User A⟩ab
    3⟨User A⟩abnot-an-email200[] ['Username too short']
  37. def _is_valid_email(self):

    pass 1 of 2
    82def _is_valid_email(self⟨User D⟩):83    return "@" in self.emailalice@email.com and "." in self.email
  38. def _is_valid_age(self):

    pass 1 of 2
    85def _is_valid_age(self⟨User D⟩):86    return 0 <= self.age25 <= 150
  39. print(f"User1 valid: {user1.is_valid()}")

    106print(f"User1 valid: {user1⟨User D⟩.is_valid()}")107print(f"User2 valid: {user2⟨User A⟩.is_valid()}")108print(f"User2 errors: {user2.validation_errors()}")
    outputUser1 valid: True
  40. def is_valid(self): #?combinedvalid

    pass 2 of 2
    88def is_valid(self⟨User A⟩):  #?combinedvalid89    return (self._is_valid_username() and  #?callvalidators90            self._is_valid_email() and 91            self._is_valid_age())
  41. print(f"User2 valid: {user2.is_valid()}")

    106print(f"User1 valid: {user1.is_valid()}")107print(f"User2 valid: {user2⟨User A⟩.is_valid()}")108print(f"User2 errors: {user2⟨User A⟩.validation_errors()}")
    outputUser2 valid: False
  42. errors ← []

    93def validation_errors(self⟨User A⟩):  #?errorlist94    errors→ [] = []95    if not self._is_valid_username():
  43. errors ← ['Username too short']

    94errors = []95if not self._is_valid_username():96    errors→ ['Username too short'].append("Username too short")97if not self._is_valid_email():
  44. def _is_valid_email(self):

    pass 2 of 2
    82def _is_valid_email(self⟨User A⟩):83    return "@" in self.emailnot-an-email and "." in self.email
  45. errors ← ['Username too short', 'Invalid email format']

    96    errors.append("Username too short")97if not self._is_valid_email():98    errors→ ['Username too short', 'Invalid email format'].append("Invalid email format")99if not self._is_valid_age():
  46. def _is_valid_age(self):

    pass 2 of 2
    85def _is_valid_age(self⟨User A⟩):86    return 0 <= self.age200 <= 150
  47. errors ← ['Username too short', 'Invalid email format', 'Invalid age']

    98    errors.append("Invalid email format")99if not self._is_valid_age():100    errors→ ['Username too short', 'Invalid email format', 'Invalid age'].append("Invalid age")101return errors
  48. return errors

    100    errors.append("Invalid age")101return errors['Username too short', 'Invalid email format', 'Invalid age']
  49. print(f"User2 errors: {user2.validation_errors()}")

    107print(f"User2 valid: {user2.is_valid()}")108print(f"User2 errors: {user2⟨User A⟩.validation_errors()}")109110print("\n=== State Management ===")111112class Player:113    def __init__(self, name, health=100):114        self.name = name115        self.health = health116        self.alive = True117    118    def take_damage(self, amount):119        self.health -= amount120        print(f"{self.name} takes {amount} damage! Health: {self.health}")121        self._check_death()  #?checkstate122    123    def heal(self, amount):124        if self.alive:125            self.health = min(100, self.health + amount)126            print(f"{self.name} heals {amount}! Health: {self.health}")127    128    def _check_death(self):  #?statecheck129        if self.health <= 0 and self.alive:130            self.alive = False131            self._on_death()  #?callback132    133    def _on_death(self):  #?callbackmethod134        print(f"{self.name} has fallen!")135136hero = Player("Hero")137hero.take_damage(30)
    outputUser2 errors: ['Username too short', 'Invalid email format', 'Invalid age']
    
    === State Management ===
  50. self.name ← Hero, self.health ← 100, self.alive ← True

    112class Player:113    def __init__(self⟨Player E⟩, nameHero, health100=100):114        self.name→ Hero = nameHero115        self.health→ 100 = health100116        self.alive→ True = True
  51. hero ← ⟨Player E⟩

    136hero→ ⟨Player E⟩ = Player("Hero")137hero⟨Player E⟩.take_damage(30)138hero.take_damage(50)
  52. self.health ← 70

    pass 1 of 3
    118def take_damage(self⟨Player E⟩, amount30):119    self.health→ 70 -= amount30120    print(f"{self.nameHero} takes {amount30} damage! Health: {self.health70}")121    self._check_death()  #?checkstate
    outputHero takes 30 damage! Health: 70
    All 3 passes — pass 1 is the card above
    passamountself.healthself.alive
    130100 70
    25070 20
    33020 -10True False
  53. def _check_death(self): #?statecheck

    pass 1 of 3
    120    print(f"{self.name} takes {amount} damage! Health: {self.health}")121    self._check_death()  #?checkstate122123def heal(self, amount):124    if self.alive:125        self.health = min(100, self.health + amount)126        print(f"{self.name} heals {amount}! Health: {self.health}")127128def _check_death(self⟨Player E⟩):  #?statecheck129    if self.health <= 0 and self.alive:130        self.alive = False
    All 3 passes — pass 1 is the card above
    passself.healthself.nameself.alive
    1
    2
    3-10HeroTrue False
  54. hero.take_damage(30)

    136hero = Player("Hero")137hero⟨Player E⟩.take_damage(30)138hero⟨Player E⟩.take_damage(50)139hero.take_damage(30)  # Dies
  55. hero.take_damage(50)

    137hero.take_damage(30)138hero⟨Player E⟩.take_damage(50)139hero⟨Player E⟩.take_damage(30)  # Dies140hero.heal(20)  # Can't heal when dead
  56. self.alive ← False

    128def _check_death(self):  #?statecheck129    if self.health-10 <= 0 and self.aliveTrue:130        self.alive→ False = False131        self._on_death()  #?callback
  57. def _on_death(self): #?callbackmethod

    120    print(f"{self.name} takes {amount} damage! Health: {self.health}")121    self._check_death()  #?checkstate122123def heal(self, amount):124    if self.alive:125        self.health = min(100, self.health + amount)126        print(f"{self.name} heals {amount}! Health: {self.health}")127128def _check_death(self):  #?statecheck129    if self.health <= 0 and self.alive:130        self.alive = False131        self._on_death()  #?callback132133def _on_death(self⟨Player E⟩):  #?callbackmethod134    print(f"{self.nameHero} has fallen!")
    outputHero has fallen!
  58. hero.take_damage(30) # Dies

    138hero.take_damage(50)139hero⟨Player E⟩.take_damage(30)  # Dies140hero⟨Player E⟩.heal(20)  # Can't heal when dead
  59. def heal(self, amount):

    123def heal(self⟨Player E⟩, amount20):124    if self.alive:125        self.health = min(100, self.health + amount)
  60. hero.heal(20) # Can't heal when dead

    139hero.take_damage(30)  # Dies140hero⟨Player E⟩.heal(20)  # Can't heal when dead141142print("\n=== Method Delegation ===")143144class Report:145    def __init__(self, title, data):146        self.title = title147        self.data = data148    149    def generate(self, format="text"):  #?delegator150        if format == "text":151            return self._generate_text()  #?delegate1152        elif format == "html":153            return self._generate_html()  #?delegate2154        else:155            return self._generate_text()156    157    def _generate_text(self):  #?textimpl158        lines = [f"=== {self.title} ==="]159        for key, value in self.data.items():160            lines.append(f"{key}: {value}")161        return "\n".join(lines)162    163    def _generate_html(self):  #?htmlimpl164        html = [f"<h1>{self.title}</h1>", "<ul>"]165        for key, value in self.data.items():166            html.append(f"  <li>{key}: {value}</li>")167        html.append("</ul>")168        return "\n".join(html)169170report = Report("Sales", {"Q1": 1000, "Q2": 1500, "Q3": 1200})171print(report.generate("text"))
    output
    === Method Delegation ===
  61. self.title ← Sales, self.data ← {'Q1': 1000, 'Q2': 1500, 'Q3': 1200}

    144class Report:145    def __init__(self⟨Report F⟩, titleSales, data{'Q1': 1000, 'Q2': 1500, 'Q3': 1200}):146        self.title→ Sales = titleSales147        self.data→ {'Q1': 1000, 'Q2': 1500, 'Q3': 1200} = data{'Q1': 1000, 'Q2': 1500, 'Q3': 1200}
  62. report ← ⟨Report F⟩

    170report→ ⟨Report F⟩ = Report("Sales", {"Q1": 1000, "Q2": 1500, "Q3": 1200})171print(report⟨Report F⟩.generate("text"))172print()
  63. def generate(self, format="text"): #?delegator

    pass 1 of 2
    149def generate(self⟨Report F⟩, formattext="text"):  #?delegator150    if format == "text":151        return self._generate_text()  #?delegate1
  64. lines ← ['=== Sales ===']

    157def _generate_text(self⟨Report F⟩):  #?textimpl158    lines→ ['=== Sales ==='] = [f"=== {self.titleSales} ==="]159    for key, value in self.data.items():
  65. lines ← ['=== Sales ===', 'Q1: 1000']

    pass 1 of 3
    158lines = [f"=== {self.title} ==="]159for keyQ1, value1000 in self.data{'Q1': 1000, 'Q2': 1500, 'Q3': 1200}.items():160    lines→ ['=== Sales ===', 'Q1: 1000'].append(f"{keyQ1}: {value1000}")161return "\n".join(lines)
    All 3 passes — pass 1 is the card above
    passkeyvaluelines
    1Q11000['=== Sales ==='] ['=== Sales ===', 'Q1: 1000']
    2Q21500['=== Sales ===', 'Q1: 1000'] ['=== Sales ===', 'Q1: 1000', 'Q2: 1500']
    3Q31200['=== Sales ===', 'Q1: 1000', 'Q2: 1500'] ['=== Sales ===', 'Q1: 1000', 'Q2: 1500', 'Q3: 1200']
  66. return " ".join(lines)

    160    lines.append(f"{key}: {value}")161return "\n".join(lines['=== Sales ===', 'Q1: 1000', 'Q2: 1500', 'Q3: 1200'])
  67. print(report.generate("text"))

    170report = Report("Sales", {"Q1": 1000, "Q2": 1500, "Q3": 1200})171print(report⟨Report F⟩.generate("text"))172print()173print(report⟨Report F⟩.generate("html"))174#@help callself
    output=== Sales ===
    Q1: 1000
    Q2: 1500
    Q3: 1200
  68. def generate(self, format="text"): #?delegator

    pass 2 of 2
    149def generate(self⟨Report F⟩, formathtml="text"):  #?delegator150    if format == "text":151        return self._generate_text()  #?delegate1
  69. html ← ['<h1>Sales</h1>', '<ul>']

    163def _generate_html(self⟨Report F⟩):  #?htmlimpl164    html→ ['<h1>Sales</h1>', '<ul>'] = [f"<h1>{self.titleSales}</h1>", "<ul>"]165    for key, value in self.data.items():
  70. html ← ['<h1>Sales</h1>', '<ul>', ' <li>Q1: 1000</li>']

    pass 1 of 3
    164html = [f"<h1>{self.title}</h1>", "<ul>"]165for keyQ1, value1000 in self.data{'Q1': 1000, 'Q2': 1500, 'Q3': 1200}.items():166    html→ ['<h1>Sales</h1>', '<ul>', '  <li>Q1: 1000</li>'].append(f"  <li>{keyQ1}: {value1000}</li>")167html.append("</ul>")
    All 3 passes — pass 1 is the card above
    passkeyvaluehtml
    1Q11000['<h1>Sales</h1>', '<ul>'] ['<h1>Sales</h1>', '<ul>', ' <li>Q1: 1000</li>']
    2Q21500['<h1>Sales</h1>', '<ul>', ' <li>Q1: 1000</li>'] ['<h1>Sales</h1>', '<ul>', ' <li>Q1: 1000</li>', ' <li>Q2: 1500</li>']
    3Q31200['<h1>Sales</h1>', '<ul>', ' <li>Q1: 1000</li>', ' <li>Q2: 1500</li>'] ['<h1>Sales</h1>', '<ul>', ' <li>Q1: 1000</li>', ' <li>Q2: 1500</li>', ' <li>Q3: 1200</li>']
  71. html ← ['<h1>Sales</h1>', '<ul>', ' <li>Q1: 1000</li>', ' <li>Q2: 1500</li>', ' <li>Q3: 1200</li>', '</ul>']

    166    html.append(f"  <li>{key}: {value}</li>")167html→ ['<h1>Sales</h1>', '<ul>', '  <li>Q1: 1000</li>', '  <li>Q2: 1500</li>', '  <li>Q3: 1200</li>', '</ul>'].append("</ul>")168return "\n".join(html['<h1>Sales</h1>', '<ul>', '  <li>Q1: 1000</li>', '  <li>Q2: 1500</li>', '  <li>Q3: 1200</li>', '</ul>'])
  72. print(report.generate("html"))

    172print()173print(report⟨Report F⟩.generate("html"))174#@help callself
    output<h1>Sales</h1>
    <ul>
      <li>Q1: 1000</li>
      <li>Q2: 1500</li>
      <li>Q3: 1200</li>
    </ul>

self.helper() calls the object's own method. Useful for code organization.

Common mistakes

Errors beginners make with self.

common_mistakes.py
Replay: real traced execution (multi-file project)
# Common self-Related Mistakes

print("=== Mistake 1: Forgetting self Parameter ===\n")

class WrongCalculator:
    def __init__(self, value):
        self.value = value

    # WRONG: Missing self!
    # def add(n):
    #     self.value += n  # NameError: self not defined!

    # CORRECT:
    def add(self, n):
        self.value += n

calc = WrongCalculator(10)
calc.add(5)
print(f"Value: {calc.value}")

print("\n=== Mistake 2: Forgetting self. for Attributes ===")

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

    def greet_wrong(self):
        # WRONG: name without self.
        # print(f"Hello, I'm {name}")  # NameError: name not defined
        pass

    def greet_correct(self):
        # CORRECT:
        print(f"Hello, I'm {self.name}")

person = WrongPerson("Alice")
person.greet_correct()

print("\n=== Mistake 3: Using self Outside Methods ===")

class WrongCounter:
    # WRONG: Can't use self here!
    # count = self.something  # NameError!

    # Class attributes don't use self
    count = 0

    def __init__(self):
        # Instance attributes DO use self
        self.value = 0

print("Class attribute (no self):", WrongCounter.count)

print("\n=== Mistake 4: Calling Method Without self. ===")

class WrongHelper:
    def __init__(self):
        self.data = []

    def process(self, item):
        # WRONG: Calling without self.
        # validate(item)  # NameError: validate not defined

        # CORRECT:
        self._validate(item)
        self.data.append(item)

    def _validate(self, item):
        if item is None:
            raise ValueError("Item cannot be None")

helper = WrongHelper()
helper.process("test")
print(f"Data: {helper.data}")

print("\n=== Mistake 5: Confusing self and cls ===")

class ConfusingExample:
    class_data = []

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

    # Instance method uses self
    def add_to_instance(self, item):
        # self refers to THIS object
        pass

    # Class method uses cls
    @classmethod
    def add_to_class(cls, item):
        # cls refers to the CLASS
        cls.class_data.append(item)

    # Static method uses neither
    @staticmethod
    def utility_function(x):
        return x * 2

ConfusingExample.add_to_class("item1")
print(f"Class data: {ConfusingExample.class_data}")
print(f"Static result: {ConfusingExample.utility_function(5)}")

print("\n=== Mistake 6: Shadowing with Local Variable ===")

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

    def change_name_wrong(self, new_name):
        # WRONG: Creates local variable, not instance attr
        name = new_name  # This is a LOCAL variable!
        print(f"Inside method: name = {name}")
        print(f"self.name unchanged: {self.name}")

    def change_name_correct(self, new_name):
        # CORRECT:
        self.name = new_name
        print(f"self.name changed: {self.name}")

obj = WrongAssignment("original")
print(f"Before: {obj.name}")
obj.change_name_wrong("attempted")
print(f"After wrong: {obj.name}")  # Still "original"
obj.change_name_correct("successful")
print(f"After correct: {obj.name}")  # Changed!

print("\n=== Mistake 7: Returning Wrong Thing ===")

class WrongReturn:
    def __init__(self, items):
        self._items = items

    def get_items_wrong(self):
        # WRONG: Returns the internal list directly
        return self._items  # Caller can modify our data!

    def get_items_correct(self):
        # CORRECT: Return a copy
        return self._items.copy()

obj = WrongReturn([1, 2, 3])
wrong_items = obj.get_items_wrong()
wrong_items.append(999)  # Modifies original!
print(f"After wrong get: {obj._items}")  # Has 999!

obj2 = WrongReturn([1, 2, 3])
correct_items = obj2.get_items_correct()
correct_items.append(999)  # Only modifies copy
print(f"After correct get: {obj2._items}")  # No 999

print("\n=== Summary of Common Mistakes ===")
print("""
1. Missing 'self' in method parameter
   WRONG:  def method(x):
   RIGHT:  def method(self, x):

2. Missing 'self.' when accessing attributes
   WRONG:  print(name)
   RIGHT:  print(self.name)

3. Using 'self' outside methods
   WRONG:  class X: data = self.something
   RIGHT:  class X: data = 0  # Class attr, no self

4. Calling methods without 'self.'
   WRONG:  helper_method()
   RIGHT:  self.helper_method()

5. Creating local variable instead of attribute
   WRONG:  name = value  # Local variable
   RIGHT:  self.name = value  # Instance attribute
""")
  1. print("=== Mistake 1: Forgetting self Parameter === ")

    3print("=== Mistake 1: Forgetting self Parameter ===\n")45class WrongCalculator:6    def __init__(self, value):7        self.value = value8    9    # WRONG: Missing self!  #?missingself10    # def add(n):11    #     self.value += n  # NameError: self not defined!12    13    # CORRECT:  #?correctself14    def add(self, n):15        self.value += n1617calc = WrongCalculator(10)18calc.add(5)
    output=== Mistake 1: Forgetting self Parameter ===
  2. self.value ← 10

    5class WrongCalculator:6    def __init__(self⟨WrongCalculator A⟩, value10):7        self.value→ 10 = value10
  3. calc ← ⟨WrongCalculator A⟩

    17calc→ ⟨WrongCalculator A⟩ = WrongCalculator(10)18calc⟨WrongCalculator A⟩.add(5)19print(f"Value: {calc.value}")
  4. self.value ← 15

    13# CORRECT:  #?correctself14def add(self⟨WrongCalculator A⟩, n5):15    self.value→ 15 += n5
  5. calc.add(5)

    17calc = WrongCalculator(10)18calc⟨WrongCalculator A⟩.add(5)19print(f"Value: {calc.value15}")2021print("\n=== Mistake 2: Forgetting self. for Attributes ===")2223class WrongPerson:24    def __init__(self, name):25        self.name = name26    27    def greet_wrong(self):28        # WRONG: name without self.  #?missingselfdot29        # print(f"Hello, I'm {name}")  # NameError: name not defined30        pass31    32    def greet_correct(self):33        # CORRECT:  #?correctselfdot34        print(f"Hello, I'm {self.name}")3536person = WrongPerson("Alice")37person.greet_correct()
    outputValue: 15
    
    === Mistake 2: Forgetting self. for Attributes ===
  6. self.name ← Alice

    23class WrongPerson:24    def __init__(self⟨WrongPerson B⟩, nameAlice):25        self.name→ Alice = nameAlice
  7. person ← ⟨WrongPerson B⟩

    36person→ ⟨WrongPerson B⟩ = WrongPerson("Alice")37person⟨WrongPerson B⟩.greet_correct()
  8. def greet_correct(self): # CORRECT: #?correctselfdot

    32def greet_correct(self⟨WrongPerson B⟩):33    # CORRECT:  #?correctselfdot34    print(f"Hello, I'm {self.nameAlice}")
    outputHello, I'm Alice
  9. count ← (empty)

    36person = WrongPerson("Alice")37person⟨WrongPerson B⟩.greet_correct()3839print("\n=== Mistake 3: Using self Outside Methods ===")4041class WrongCounter:42    # WRONG: Can't use self here!  #?selfoutsidemethod43    # count = self.something  # NameError!44    45    # Class attributes don't use self  #?classattrnose46    count→ (empty) = 047    48    def __init__(self):49        # Instance attributes DO use self  #?instanceattrself50        self.value = 05152print("Class attribute (no self):", WrongCounter.count0)5354print("\n=== Mistake 4: Calling Method Without self. ===")5556class WrongHelper:57    def __init__(self):58        self.data = []59    60    def process(self, item):61        # WRONG: Calling without self.  #?callingwithoutself62        # validate(item)  # NameError: validate not defined63        64        # CORRECT:  #?correctcall65        self._validate(item)66        self.data.append(item)67    68    def _validate(self, item):69        if item is None:70            raise ValueError("Item cannot be None")7172helper = WrongHelper()73helper.process("test")
    output
    === Mistake 3: Using self Outside Methods ===
    Class attribute (no self): 0
    
    === Mistake 4: Calling Method Without self. ===
  10. self.data ← []

    56class WrongHelper:57    def __init__(self⟨WrongHelper C⟩):58        self.data→ [] = []
  11. helper ← ⟨WrongHelper C⟩

    72helper→ ⟨WrongHelper C⟩ = WrongHelper()73helper⟨WrongHelper C⟩.process("test")74print(f"Data: {helper.data}")
  12. def process(self, item): # WRONG: Calling without self. #?cal…

    60def process(self⟨WrongHelper C⟩, itemtest):61    # WRONG: Calling without self.  #?callingwithoutself62    # validate(item)  # NameError: validate not defined63    64    # CORRECT:  #?correctcall65    self._validate(itemtest)66    self.data.append(item)
  13. self.data ← ['test']

    64    # CORRECT:  #?correctcall65    self._validate(itemtest)66    self.data→ ['test'].append(itemtest)6768def _validate(self⟨WrongHelper C⟩, itemtest):69    if item is None:70        raise ValueError("Item cannot be None")
  14. class_data ← (empty)

    72helper = WrongHelper()73helper⟨WrongHelper C⟩.process("test")74print(f"Data: {helper.data['test']}")7576print("\n=== Mistake 5: Confusing self and cls ===")7778class ConfusingExample:79    class_data→ (empty) = []  #?classdata80    81    def __init__(self, value):82        self.value = value83    84    # Instance method uses self  #?instancemethod85    def add_to_instance(self, item):86        # self refers to THIS object87        pass88    89    # Class method uses cls  #?classmethodcls90    @classmethod91    def add_to_class(cls, item):92        # cls refers to the CLASS93        cls.class_data.append(item)94    95    # Static method uses neither  #?staticmethod96    @staticmethod97    def utility_function(x):98        return x * 299100ConfusingExample<class '__main__.ConfusingExample'>.add_to_class("item1")101print(f"Class data: {ConfusingExample.class_data}")
    outputData: ['test']
    
    === Mistake 5: Confusing self and cls ===
  15. cls.class_data ← ['item1']

    90@classmethod91def add_to_class(cls<class '__main__.ConfusingExample'>, itemitem1):92    # cls refers to the CLASS93    cls.class_data→ ['item1'].append(itemitem1)
  16. ConfusingExample.add_to_class("item1")

    100ConfusingExample<class '__main__.ConfusingExample'>.add_to_class("item1")101print(f"Class data: {ConfusingExample.class_data['item1']}")102print(f"Static result: {ConfusingExample<class '__main__.ConfusingExample'>.utility_function(5)}")
    outputClass data: ['item1']
  17. def utility_function(x):

    96@staticmethod97def utility_function(x5):98    return x5 * 2
  18. print(f"Static result: {ConfusingExample.utility_function(5)}")

    101print(f"Class data: {ConfusingExample.class_data}")102print(f"Static result: {ConfusingExample<class '__main__.ConfusingExample'>.utility_function(5)}")103104print("\n=== Mistake 6: Shadowing with Local Variable ===")105106class WrongAssignment:107    def __init__(self, name):108        self.name = name109    110    def change_name_wrong(self, new_name):111        # WRONG: Creates local variable, not instance attr  #?localvar112        name = new_name  # This is a LOCAL variable!113        print(f"Inside method: name = {name}")114        print(f"self.name unchanged: {self.name}")115    116    def change_name_correct(self, new_name):117        # CORRECT:  #?selfassign118        self.name = new_name119        print(f"self.name changed: {self.name}")120121obj = WrongAssignment("original")122print(f"Before: {obj.name}")
    outputStatic result: 10
    
    === Mistake 6: Shadowing with Local Variable ===
  19. self.name ← original

    106class WrongAssignment:107    def __init__(self⟨WrongAssignment D⟩, nameoriginal):108        self.name→ original = nameoriginal
  20. obj ← ⟨WrongAssignment D⟩

    121obj→ ⟨WrongAssignment D⟩ = WrongAssignment("original")122print(f"Before: {obj.nameoriginal}")123obj⟨WrongAssignment D⟩.change_name_wrong("attempted")124print(f"After wrong: {obj.name}")  # Still "original"
    outputBefore: original
  21. name ← attempted

    110def change_name_wrong(self⟨WrongAssignment D⟩, new_nameattempted):111    # WRONG: Creates local variable, not instance attr  #?localvar112    name→ attempted = new_nameattempted  # This is a LOCAL variable!113    print(f"Inside method: name = {nameattempted}")114    print(f"self.name unchanged: {self.nameoriginal}")
    outputInside method: name = attempted
    self.name unchanged: original
  22. obj.change_name_wrong("attempted")

    122print(f"Before: {obj.name}")123obj⟨WrongAssignment D⟩.change_name_wrong("attempted")124print(f"After wrong: {obj.nameoriginal}")  # Still "original"125obj⟨WrongAssignment D⟩.change_name_correct("successful")126print(f"After correct: {obj.name}")  # Changed!
    outputAfter wrong: original
  23. self.name ← successful

    116def change_name_correct(self⟨WrongAssignment D⟩, new_namesuccessful):117    # CORRECT:  #?selfassign118    self.name→ successful = new_namesuccessful119    print(f"self.name changed: {self.namesuccessful}")
    outputself.name changed: successful
  24. obj.change_name_correct("successful")

    124print(f"After wrong: {obj.name}")  # Still "original"125obj⟨WrongAssignment D⟩.change_name_correct("successful")126print(f"After correct: {obj.namesuccessful}")  # Changed!127128print("\n=== Mistake 7: Returning Wrong Thing ===")129130class WrongReturn:131    def __init__(self, items):132        self._items = items133    134    def get_items_wrong(self):135        # WRONG: Returns the internal list directly  #?returninternal136        return self._items  # Caller can modify our data!137    138    def get_items_correct(self):139        # CORRECT: Return a copy  #?returncopy140        return self._items.copy()141142obj = WrongReturn([1, 2, 3])143wrong_items = obj.get_items_wrong()
    outputAfter correct: successful
    
    === Mistake 7: Returning Wrong Thing ===
  25. self._items ← [1, 2, 3]

    pass 1 of 2
    130class WrongReturn:131    def __init__(self⟨WrongReturn E⟩, items[1, 2, 3]):132        self._items→ [1, 2, 3] = items[1, 2, 3]
  26. obj ← ⟨WrongReturn E⟩

    142obj→ ⟨WrongReturn E⟩ = WrongReturn([1, 2, 3])143wrong_items = obj⟨WrongReturn E⟩.get_items_wrong()144wrong_items.append(999)  # Modifies original!
  27. def get_items_wrong(self): # WRONG: Returns the internal list …

    134def get_items_wrong(self⟨WrongReturn E⟩):135    # WRONG: Returns the internal list directly  #?returninternal136    return self._items[1, 2, 3]  # Caller can modify our data!
  28. wrong_items ← [1, 2, 3]

    142obj = WrongReturn([1, 2, 3])143wrong_items→ [1, 2, 3] = obj⟨WrongReturn E⟩.get_items_wrong()144wrong_items→ [1, 2, 3, 999].append(999)  # Modifies original!145print(f"After wrong get: {obj._items[1, 2, 3, 999]}")  # Has 999!146147obj2 = WrongReturn([1, 2, 3])148correct_items = obj2.get_items_correct()
    outputAfter wrong get: [1, 2, 3, 999]
  29. self._items ← [1, 2, 3]

    pass 2 of 2
    130class WrongReturn:131    def __init__(self⟨WrongReturn D⟩, items[1, 2, 3]):132        self._items→ [1, 2, 3] = items[1, 2, 3]
  30. obj2 ← ⟨WrongReturn D⟩

    147obj2→ ⟨WrongReturn D⟩ = WrongReturn([1, 2, 3])148correct_items = obj2⟨WrongReturn D⟩.get_items_correct()149correct_items.append(999)  # Only modifies copy
  31. def get_items_correct(self): # CORRECT: Return a copy #?retur…

    138def get_items_correct(self⟨WrongReturn D⟩):139    # CORRECT: Return a copy  #?returncopy140    return self._items[1, 2, 3].copy()
  32. correct_items ← [1, 2, 3]

    147obj2 = WrongReturn([1, 2, 3])148correct_items→ [1, 2, 3] = obj2⟨WrongReturn D⟩.get_items_correct()149correct_items→ [1, 2, 3, 999].append(999)  # Only modifies copy150print(f"After correct get: {obj2._items[1, 2, 3]}")  # No 999151152print("\n=== Summary of Common Mistakes ===")153print("""1541. Missing 'self' in method parameter155   WRONG:  def method(x):156   RIGHT:  def method(self, x):1571582. Missing 'self.' when accessing attributes159   WRONG:  print(name)160   RIGHT:  print(self.name)1611623. Using 'self' outside methods163   WRONG:  class X: data = self.something164   RIGHT:  class X: data = 0  # Class attr, no self1651664. Calling methods without 'self.'167   WRONG:  helper_method()168   RIGHT:  self.helper_method()1691705. Creating local variable instead of attribute171   WRONG:  name = value  # Local variable172   RIGHT:  self.name = value  # Instance attribute173""")174#@help missingself
    outputAfter correct get: [1, 2, 3]
    
    === Summary of Common Mistakes ===
    
    1. Missing 'self' in method parameter
       WRONG:  def method(x):
       RIGHT:  def method(self, x):
    
    2. Missing 'self.' when accessing attributes
       WRONG:  print(name)
       RIGHT:  print(self.name)
    
    3. Using 'self' outside methods
       WRONG:  class X: data = self.something
       RIGHT:  class X: data = 0  # Class attr, no self
    
    4. Calling methods without 'self.'
       WRONG:  helper_method()
       RIGHT:  self.helper_method()
    
    5. Creating local variable instead of attribute
       WRONG:  name = value  # Local variable
       RIGHT:  self.name = value  # Instance attribute

Forgetting self in method signature. Forgetting self when accessing attributes.

Exercise: practical.py

Build a class demonstrating proper use of self