Classes
Python - A Quick Start for existing Programers
3 min read
Published Sep 16 2025, updated Aug 17 2026
Guide Sections
Guide Comments
Python supports OOP and classes:
- Defined by the
classkeyword. - Structure is indented.
- Top level variables are class attributes available on all instances of a class.
- Constructer is defined as a function with the name
__init__(self, ...). - Self object is used to set instance variables:
self.my_instance_variable = 4. - Constructer parameters can be passed in to the init function:
__init__(self, my_parameter1). - Inside the init function set
self.my_instance_variable = my_parameter1to set instance variables. - Top level functions are instance functions by default and can access
self.
Example:
class Dog: """ Doc string (multi-line comment) to define what the class does, shows up in intellisense in IDE """ # Class attribute (shared by all dogs) species = "Canis familiaris" # Constructor (initializer) def __init__(self, name, age): # instance attributes self.name = name self.age = age # Instance method def bark(self): return f"{self.name} says Woof!"# Create objects (instances)dog1 = Dog("Rocky", 5)dog2 = Dog("Luna", 3)print(dog1.name)# Rockyprint(dog1.age)# 5print(dog1.species)# Canis familiarisprint(dog1.bark()) # Rocky says Woof!print(dog2.name)# Lunaprint(dog2.age)# 3print(dog2.species)# Canis familiarisprint(dog2.bark()) # Luna says Woof!print(Dog.species)# Canis familiarisClass methods
Instance Method (default):
- Normal methods in a class.
- Take
selfas the first argument. - Can access instance attributes and class attributes.
- Is called on an individual instance of a class.
class Demo: def instance_method(self): return f"Called instance_method on {self}"Class Method (@classmethod):
- Declared with
@classmethoddecorator. - First parameter is
cls(the class itself, not an instance). - Can access/modify class attributes but not instance attributes (unless given an instance).
- Often used as factory methods (alternate constructors).
- Is called on the actual class record.
class Demo: counter = 0 def __init__(self, value): self.value = value Demo.counter += 1 @classmethod def how_many(cls): # cls refers to the class return f"There are {cls.counter} instances." @classmethod def from_string(cls, value_str): # factory method return cls(int(value_str))a = Demo(10)b = Demo.from_string("20") # create instance from stringprint(Demo.how_many()) # "There are 2 instances."Static Method (@staticmethod):
- Declared with
@staticmethoddecorator. - No
selforclsparameter → acts like a regular function, but lives inside the class for organisation. - Cannot access class or instance attributes (unless passed explicitly).
- Used for utility/helper functions related to the class.
- Is called on the actual class record.
class MathHelper: @staticmethod def add(a, b): return a + bprint(MathHelper.add(5, 7)) # 12Dunder methods:
- Special methods denoted with
____either side. - Has special purposes, such as the
__init__function. - See section below for more details.
Encapsulation (Attribute Access)
There is no true private attribute enforcement like in other languages, Instead a naming convention using _ and __ is used.
Public (no underscore):
- Normal attributes are fully accessible.
class Demo: def __init__(self): self.value = 42obj = Demo()print(obj.value) # Works fineProtected (_single):
- Just a convention: “for internal use, but you can access it if you really want.”
class Demo: def __init__(self): self._internal = "protected-ish"obj = Demo()print(obj._internal) # Works, but convention says "don’t"Private (__double):
- Triggers name mangling: Python renames it to
_ClassName__attr. - Makes accidental access harder, but still accessible if you know the mangled name.
class Demo: def __init__(self): self.__secret = "hidden"obj = Demo()# print(obj.__secret) # AttributeErrorprint(obj._Demo__secret) # Works as its manually using the mangled name, but not recommendedInheritance
Inheritance is supported in Python classes, allowing you to use inherited attributes and methods and override methods.
class Animal: def __init__(self, name): self.name = name def speak(self): return "..."class Dog(Animal): def speak(self): return "Woof!"dog = Dog("Rex")print(dog.name) # Inherited from Animalprint(dog.speak()) # Overridden in DogThe super() function
- Calls methods of the parent class.
- Commonly used in
__init__to ensure parent initialisation runs, see highlighted row in the example below where it calls the parents constructor first to initialise the common attributes and then sets the class specific attributes. - Avoids hardcoding the parent class name → important in multiple inheritance.
class Animal: def __init__(self, name): self.name = nameclass Dog(Animal): def __init__(self, name, breed): # Call parent constructor super().__init__(name) self.breed = breeddog = Dog("Rex", "Labrador")print(dog.name) # from Animalprint(dog.breed) # from DogExample: Calling parent methods:
class Animal: def speak(self): return "Some sound"class Cat(Animal): def speak(self): # Use parent method, then extend base_sound = super().speak() return base_sound + " Meow!"cat = Cat()print(cat.speak()) # "Some sound Meow!"Multiple Inheritance and super()
Python supports multiple inheritance. super() follows the MRO (Method Resolution Order).
class A: def greet(self): print("Hello from A")class B(A): def greet(self): print("Hello from B") super().greet()class C(A): def greet(self): print("Hello from C") super().greet()class D(B, C): def greet(self): print("Hello from D") super().greet()d = D()d.greet()# Output:# Hello from D# Hello from B# Hello from C# Hello from ADunder methods
Python has a lot of dunder (double underscore) methods, also called magic methods. They let your objects integrate seamlessly with Python’s built-in syntax and functions.
- Dunder methods = special hooks that let your class behave like built-in types.
- This allows you to be able to dfine how you class works with the
lenmethod, or how it will print when doing anf stingetc. - Categories:
- Initialisation (
__init__,__str__,__repr__) - Attribute access (
__getattr__,__setattr__) - Callable/context (
__call__,__enter__,__exit__) - Comparisons (
__eq__,__lt__, etc.) - Containers (
__len__,__getitem__, etc.) - Operators (arithmetic, bitwise, in-place, reflected)
- Misc (
__bool__,__index__, async methods)
Examples:
class MyCollection: def __init__(self, items): self.items = list(items) # String representation def __str__(self): return f"MyCollection({self.items})" def __repr__(self): return f"MyCollection({self.items!r})" # Length def __len__(self): return len(self.items) # Indexing / slicing def __getitem__(self, index): return self.items[index] def __setitem__(self, index, value): self.items[index] = value def __delitem__(self, index): del self.items[index] # Iteration def __iter__(self): return iter(self.items) # Membership test def __contains__(self, item): return item in self.items # Addition (combine two collections) def __add__(self, other): return MyCollection(self.items + other.items) # Equality def __eq__(self, other): return self.items == other.items # Boolean def __bool__(self): return bool(self.items) # Callable def __call__(self): print("This collection has", len(self), "items!")# ------------------------------------# Usage# ------------------------------------col1 = MyCollection([1, 2, 3])col2 = MyCollection([4, 5])# __str__print("String:", col1) # __repr__print("Representation:", repr(col1)) # __len__print("Length:", len(col1)) # __getitem__print("Indexing:", col1[1]) # __setitem__col1[1] = 20 print("After setitem:", col1)# __delitem__del col1[0] print("After delitem:", col1)# __iter__print("Iteration:", [x for x in col1]) # __contains__print("Membership 20 in col1?", 20 in col1) # __add__col3 = col1 + col2 print("Addition:", col3)# __eq__print("Equality col1==col3?", col1 == col3) # __bool__print("Boolean col1?", bool(col1)) # __call__col1()Full demonstration of classes
# Base classclass Animal: # Class attribute species = "Generic Animal" def __init__(self, name, age): # Instance attributes self.name = name self.age = age def speak(self): # Instance method return "Some generic sound" @classmethod def class_info(cls): # Class method return f"This is the {cls.__name__} class." @staticmethod def kingdom(): # Static method return "Animalia" def __str__(self): # String representation return f"{self.name} ({self.age} yrs)"# Derived class (inheritance)class Dog(Animal): species = "Canis familiaris" def speak(self): # Override parent method return f"{self.name} says Woof!"# Another derived classclass Cat(Animal): species = "Felis catus" def speak(self): # Override parent method return f"{self.name} says Meow!"# Custom class with operator overloadingclass Point: def __init__(self, x, y): self.x, self.y = x, y def __add__(self, other): # Overload + return Point(self.x + other.x, self.y + other.y) def __str__(self): return f"({self.x}, {self.y})"# ------------------------------------# Usage# ------------------------------------# Create objectsdog = Dog("Buddy", 5)cat = Cat("Lucy", 3)print("Dog:", dog)print("Cat:", cat)print("Dog speaks:", dog.speak())print("Cat speaks:", cat.speak())# Class method and static methodprint("Class method:", Dog.class_info())print("Static method:", Animal.kingdom())# Inheritance in actionanimals = [dog, cat, Animal("Generic", 1)]for a in animals: print(f"{a.name} is a {a.species} and says: {a.speak()}")# Operator overloadingp1, p2 = Point(1, 2), Point(3, 4)print("Points:", p1, "+", p2, "=", p1 + p2)