Python isn't purely an object-oriented language the way some others insist you be, but classes are everywhere in real Python code, and at some point you'll need to write your own rather than just using ones from a library.

A basic class

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

    def bark(self):
        print(f"{self.name} says woof!")

my_dog = Dog("Rex")
my_dog.bark()  # Rex says woof!

__init__ is the constructor — it runs automatically the moment you create a new object, and its job is usually just to set up the object's starting values. self refers to the specific object being worked with, and yes, you've to write it as the first parameter of every method, even though you never actually pass it in yourself; Python fills it in automatically behind the scenes.

Why self trips people up

Honestly, self confuses almost everyone the first week they see it, mostly because it looks like it shouldn't be necessary. Just remember: inside a class, self.name means "this particular object's name," as opposed to some other Dog object that might exist elsewhere with a different name. Once that clicks, the rest of Python's classes feel a lot less mysterious.

Adding more behaviour

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

    def bark(self):
        print(f"{self.name} says woof!")

    def is_senior(self):
        return self.age >= 8

Creating several objects

dog1 = Dog("Rex", 3)
dog2 = Dog("Bella", 9)
print(dog2.is_senior())  # True

dog1 and dog2 are two entirely separate objects, each with its own name and age, even though both were built from the exact same Dog class. Changing one never touches the other.

A note on inheritance

class Puppy(Dog):
    def is_senior(self):
        return False

Here Puppy reuses everything from Dog but overrides is_senior with its own answer, since presumably no puppy is a senior dog. Same idea as inheritance in other object-oriented languages, just with noticeably less ceremony around it.

Do you always need classes?

Not really, and that's fine. Plenty of good Python code is just functions operating on dictionaries and lists, with no classes anywhere in sight. Reach for a class once you notice the same group of data and the functions that operate on it keep travelling together everywhere — that's usually the sign it's time to wrap them up into one.