Chapter Seven Python Program/Notes

Chapter 7 - Class, Methods, and Constructors

  • Constructors can be object factories, which are objects that make more objects.

  • The class object is a Python callable.

  • Basically it’s just a keyword to make a custom class object.

    class myClass:
        myThing = 2
    
  • In this example you can now use myClass as an object (cuz everything in Python is an object, duh).

    myClass.myThing = 1
    
  • If you wanted to make a bunch of MyClasses for some reason you can just set another object equal to it and it’ll do it. Neat.

  • When you do this, all attributes or objects inside the class are the same as the OG unless you specify otherwise

  • Methods call the function object using “self”, the instance, as the first paramater. What this means is that you write function attributes with an explicit “self” paramater, but when you call a method you do not specify self.

  • Methods are bound to their instance when first accessed.

  • The initializer __init__ can be used to add attributes to an instance and to initialize them.

Code Example Thing

# -------------------------------------------------------------------
# 1. Basic class — the class object itself is usable right away
# -------------------------------------------------------------------
class Dragon:
    # Class-level attribute shared by all instances (until overridden)
    element = "fire"
    population = 0

    # -------------------------------------------------------------------
    # 2. __init__ — initializer that adds & sets per-instance attributes
    # -------------------------------------------------------------------
    def __init__(self, name, age):
        self.name = name        # unique to this instance
        self.age = age          # unique to this instance
        Dragon.population += 1  # mutate the shared class attribute

    # -------------------------------------------------------------------
    # 3. Methods — 'self' is written in the def but NOT passed when calling
    # -------------------------------------------------------------------
    def roar(self):
        return f"{self.name} roars with {self.element} breath!"

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

    def __repr__(self):
        return f"Dragon(name={self.name!r}, age={self.age}, element={self.element!r})"


# -------------------------------------------------------------------
# 4. The class as an object — you can read/write its attributes directly
# -------------------------------------------------------------------
print("=== Class-level attribute access ===")
print(f"Default element for all Dragons: {Dragon.element}")

Dragon.element = "ice"          # change it on the class itself
print(f"After class-level change:        {Dragon.element}")
Dragon.element = "fire"         # reset for the demo below


# -------------------------------------------------------------------
# 5. Object factory in action — making a bunch of Dragons
# -------------------------------------------------------------------
print("\n=== Creating instances (Dragon is the factory) ===")

smaug = Dragon("Smaug", 300)
toothless = Dragon("Toothless", 20)
mushu = Dragon("Mushu", 1500)

for d in [smaug, toothless, mushu]:
    print(d)

print(f"\nTotal dragons created: {Dragon.population}")


# -------------------------------------------------------------------
# 6. Shared vs. overridden attributes
#    All share Dragon.element UNLESS a specific instance overrides it
# -------------------------------------------------------------------
print("\n=== Shared vs. per-instance attributes ===")
toothless.element = "plasma"    # only Toothless is affected

print(f"Smaug's element:     {smaug.element}")      # still "fire" (class default)
print(f"Toothless's element: {toothless.element}")  # "plasma" (instance override)
print(f"Mushu's element:     {mushu.element}")      # still "fire" (class default)


# -------------------------------------------------------------------
# 7. Calling methods — self is implicit, don't pass it yourself
# -------------------------------------------------------------------
print("\n=== Calling methods ===")
print(smaug.roar())
print(toothless.roar())
print(mushu.birthday())


# -------------------------------------------------------------------
# 8. Bonus: object factory pattern — a class whose job is making others
# -------------------------------------------------------------------
class DragonHatchery:
    """An object factory: its whole purpose is producing Dragon instances."""

    def __init__(self, default_element="fire"):
        self.default_element = default_element
        self.hatched = []

    def hatch(self, name, age=0):
        baby = Dragon(name, age)
        baby.element = self.default_element   # stamp with hatchery's element
        self.hatched.append(baby)
        return baby

    def roster(self):
        return "\n".join(f"  {d}" for d in self.hatched)


print("\n=== DragonHatchery (object factory) ===")
ice_hatchery = DragonHatchery(default_element="ice")

frostbite = ice_hatchery.hatch("Frostbite", 1)
blizzard  = ice_hatchery.hatch("Blizzard",  2)

print(f"Ice Hatchery roster:\n{ice_hatchery.roster()}")
print(f"\n{frostbite.roar()}")
print(f"Total Dragon population now: {Dragon.population}")