Python Refresher: The Essentials

Exploring Python's Landscape: Object-Oriented Principles, Exception Handling, and Beyond

Python Refresher: The Essentials

If you need a quick refresher on the basics - you can head over to "Python Refresher: The Basics" post, where I've gone through the basic stuff from syntax and variables to data structures. But if you're ready, let's explore some object-oriented features and more!

Object-Oriented Programming (OOP) in Action:

Python is an object-oriented language. Every piece of data and all functions in Python are considered objects. Let's break down what this means...

Each object is an instance of a class. You can create your classes using the class keyword and form methods within that class.

class MyClass:
    x = 5
    y = "Why?"

p1 = MyClass()

There are four main principles in OOP: encapsulation, inheritance, polymorphism, and abstraction.


Encapsulation is like a magician's hat, hiding the bunny - in this case, data - inside. Python can restrict access to methods and variables. This prevention of direct data modification is encapsulation. You can't see it, but it's happening.

Python uses a naming convention for private variables with a single underscore prefix (_var) or double underscore prefix (__var).

A single underscore prefix is a convention used to indicate that a variable or method is intended for internal use within the class, module, or function. It doesn't prevent external access or modification, but it serves as a hint to the programmer that it's considered to be internal.

A double underscore prefix is a stronger indication that a variable or method should not be accessed directly. Python alters the name of any variable or method that starts with two underscores, which makes it harder (but not impossible) to access it directly.

Here is an example of encapsulation using private variables:

class MyClass:
    __hiddenVariable = 0  # double underscore for a stronger private hint

    def add(self, increment):
        self.__hiddenVariable += increment

myObject = MyClass()      
# Accessing directly would lead to AttributeError: 'MyClass' object has no attribute '__hiddenVariable'

Note that direct access to __hiddenVariable is still possible with the mangled name, but it's generally discouraged:

print(myObject._MyClass__hiddenVariable)  # prints 5, but don't do this!


Inheritance is like the phrase "like father, like son." A new class can adopt details from an existing class, without altering it. The new class is referred to as a derived (or child) class, and the class from which it inherits is known as the base (or parent) class.

class Parent:        # define parent class
    parentAttr = 100
    def __init__(self):
        print("Calling parent constructor")

class Child(Parent): # define child class
    def __init__(self):
        print("Calling child constructor")

c = Child()


Polymorphism lets you define methods in the child class with the same name as those defined in their parent class. It enables us to utilize the same interface for different data types.

class Animal:
    def type(self):

class Dog(Animal):
    def type(self):
        return "Dog"

class Cat(Animal):
    def type(self):
        return "Cat"

animals = [Dog(), Cat()]

for animal in animals:


Abstraction is like a magician's cloak – it hides the complicated parts and shows only what's essential. It is a process of hiding the implementation details and displaying only functionality to the user.

from abc import ABC, abstractmethod

class AbstractVehicle(ABC):
    def speed(self):

    def type_of_vehicle(self):

class Car(AbstractVehicle):
    def speed(self):
        return "100 km/h"

    def type_of_vehicle(self):
        return "Land vehicle"

class Plane(AbstractVehicle):
    def speed(self):
        return "900 km/h"

    def type_of_vehicle(self):
        return "Air vehicle"

vehicles = [Car(), Plane()]

for vehicle in vehicles:
    print(f'Type: {vehicle.type_of_vehicle()} - Speed: {vehicle.speed()}')

Exception Handling: Turning Obstacles into Opportunities

When your code encounters an error, it throws an exception. However, Python equips you with the try, except, and finally keywords to handle these unexpected events gracefully.

except ZeroDivisionError:
    print("You can't divide by zero!")
    print("This will run no matter what.")


Iterators return one item at a time, maintaining their position and advancing forward.

my_tuple = ("apple", "banana", "cherry")
my_iter = iter(my_tuple)


A Python iterator is an object and is implemented via classes. Here is an example of an iterator:

class MyNumbers:
  def __iter__(self):
    self.a = 1
    return self

  def __next__(self):
    if self.a <= 7:
      x = self.a
      self.a += 1
      return x
      raise StopIteration

myclass = MyNumbers()
myiter = iter(myclass)

for x in myiter:


Generators, like lists or tuples, are a special type of iterable. Unlike lists, they don't support indexing with arbitrary indices, but you can still iterate through them with for loops. Here is an example of a generator:

def my_gen():
  n = 1
  print('This is printed first')
  yield n

  n += 1
  print('This is printed second')
  yield n

  n += 1
  print('This is printed last')
  yield n

for item in my_gen():


Ever wondered how to enhance your functions without permanently altering them? Decorators have got you covered! They add a dash of magic to your functions at compile time, a process also known as metaprogramming. A decorator takes in a function, enhances its functionality, and returns it. Here is an example of a decorator:

def my_decorator(func):
  def wrapper():
    print("Something is happening before the function is called.")
    print("Something is happening after the function is called.")
  return wrapper

def say_hi():

say_it = my_decorator(say_hi)

File I/O:

Python comes with basic functions and methods necessary to manipulate files by default. Most file manipulations can be performed using a file object.

# write to a file
file = open('file.txt', 'w')
file.write('Hello, world! \nThis is the 2nd line')

# read the file
file = open('file.txt', 'r')

# read line by line
file = open('file.txt', 'r')
for line in file:
    print(line, end='')

The os and shutil modules provide methods for file and directory operations like creating, deleting, moving files or directories, etc.

import os
import shutil

# create a new directory

# rename the directory
os.rename('new_directory', 'old_directory')

# remove the directory

# copy a file
shutil.copy('file.txt', 'new_file.txt')

# move a file
shutil.move('new_file.txt', 'new_file_name.txt')

# remove the file

Context Managers:

The unseen hero, known as context managers, takes care of managing resources for you. The context manager protocol involves the __enter__ and __exit__ methods.

An example of a context manager is the with statement. It's used to ensure that resources are correctly managed and that you don't have to manually release them.

with open('file.txt', 'r') as file:

List Comprehensions: Shortcut to New Lists

List comprehensions provide a concise way to create lists based on existing lists.

nums = [1, 2, 3, 4]
squared = [n ** 2 for n in nums]
print(squared) # Outputs: [1, 4, 9, 16]


  1. Python Official Documentation

  2. W3Schools Python