# Classes and Objects: The Blueprint and the Thing
# class, __init__, self, methods — the foundation of OOP
# redhorndev.com

# ─────────────────────────────────────────────
# A minimal class
# ─────────────────────────────────────────────

class Animal:
    pass

lassie   = Animal()     # creating an object from the class
whiskers = Animal()     # another object — independent

print(type(lassie))     # <class '__main__.Animal'>

# ─────────────────────────────────────────────
# __init__ — the constructor
# ─────────────────────────────────────────────

class Animal:
    def __init__(self, name, species, age):
        self.name    = name      # stored on this specific object
        self.species = species
        self.age     = age

lassie = Animal("Lassie", "dog", 4)

print(lassie.name)      # Lassie
print(lassie.species)   # dog
print(lassie.age)       # 4

# ─────────────────────────────────────────────
# Multiple objects — same class, own data
# ─────────────────────────────────────────────

lassie   = Animal("Lassie",   "dog", 4)
whiskers = Animal("Whiskers", "cat", 2)
rex      = Animal("Rex",      "dog", 6)

print(lassie.name)      # Lassie
print(whiskers.name)    # Whiskers
print(rex.age)          # 6

# each object is independent — changing one doesn't affect others
lassie.age = 5
print(lassie.age)       # 5
print(rex.age)          # 6 — unchanged

# ─────────────────────────────────────────────
# Methods — functions that live inside a class
# ─────────────────────────────────────────────

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

    def describe(self):
        print(f"{self.name} is a {self.age} year old {self.species}.")

lassie   = Animal("Lassie",   "dog", 4)
whiskers = Animal("Whiskers", "cat", 2)

lassie.describe()       # Lassie is a 4 year old dog.
whiskers.describe()     # Whiskers is a 2 year old cat.

# ─────────────────────────────────────────────
# Quick reference
# ─────────────────────────────────────────────

# class ClassName:              — define a class (CapitalizedWords)
#     def __init__(self, ...):  — constructor, runs at creation
#         self.attr = value     — store data on the object
#
#     def method(self):         — method, function inside a class
#         ...
#
# obj = ClassName(args)         — create an object
# obj.attr                      — access attribute
# obj.method()                  — call method
#
# self — refers to the specific object being used
# __init__ — never called directly, runs automatically
