Python Refresher: The Essentials
Intermediate Python worth revisiting — comprehensions, iterators, context managers, and the small patterns that make code feel Pythonic.
Object-Oriented Programming (OOP) in Action
Python is an object-oriented language where every piece of data and all functions are considered objects. Each object is an instance of a class, which you can create using the class keyword.
class MyClass:
x = 5
y = "Why?"
p1 = MyClass()
print(p1.x)
print(p1.y)
There are four main principles in OOP: encapsulation, inheritance, polymorphism, and abstraction.
Encapsulation
Encapsulation hides data inside a class to prevent direct modification. Python uses naming conventions for private variables:
- A single underscore prefix (
_var) indicates internal use but doesn’t prevent access - A double underscore prefix (
__var) makes Python alter the variable name, making direct access harder
class MyClass:
__hiddenVariable = 0 # double underscore for a stronger private hint
def add(self, increment):
self.__hiddenVariable += increment
print(self.__hiddenVariable)
myObject = MyClass()
myObject.add(5)
# Accessing directly would lead to AttributeError: 'MyClass' object has no attribute '__hiddenVariable'
Direct access is still possible with the mangled name, but it’s discouraged:
print(myObject._MyClass__hiddenVariable) # prints 5, but don't do this!
Inheritance
A new class can inherit details from an existing class without altering it. The new class is called a derived (or child) class, and the source class is 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()
print(c.parentAttr)
Polymorphism
Polymorphism allows you to define methods in the child class with the same name as those in the parent class, enabling the same interface for different data types.
class Animal:
def type(self):
pass
class Dog(Animal):
def type(self):
return "Dog"
class Cat(Animal):
def type(self):
return "Cat"
animals = [Dog(), Cat()]
for animal in animals:
print(animal.type())
Abstraction
Abstraction hides complex implementation details and displays only essential functionality to the user.
from abc import ABC, abstractmethod
class AbstractVehicle(ABC):
@abstractmethod
def speed(self):
pass
@abstractmethod
def type_of_vehicle(self):
pass
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. Python provides the try, except, and finally keywords to handle these unexpected events gracefully.
try:
print(10/0)
except ZeroDivisionError:
print("You can't divide by zero!")
finally:
print("This will run no matter what.")
Iterators
Iterators return one item at a time, maintaining their position and advancing forward.
my_tuple = ("apple", "banana", "cherry")
my_iter = iter(my_tuple)
print(next(my_iter))
print(next(my_iter))
print(next(my_iter))
A Python iterator is an object implemented via classes:
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
else:
raise StopIteration
myclass = MyNumbers()
myiter = iter(myclass)
for x in myiter:
print(x)
Generators
Generators are a special type of iterable like lists or tuples. Unlike lists, they don’t support indexing with arbitrary indices, but you can iterate through them with for loops.
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():
print(item)
Decorators
Decorators enhance your functions without permanently altering them. They add functionality at compile time through metaprogramming. A decorator takes in a function, enhances its functionality, and returns it.
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper
def say_hi():
print("Hi!")
say_it = my_decorator(say_hi)
say_it()
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')
file.close()
# read the file
file = open('file.txt', 'r')
print(file.read())
file.close()
# read line by line
file = open('file.txt', 'r')
for line in file:
print(line, end='')
file.close()
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
os.mkdir('new_directory')
# rename the directory
os.rename('new_directory', 'old_directory')
# remove the directory
os.rmdir('old_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
os.remove("new_file_name.txt")
Context Managers
Context managers manage resources automatically. The context manager protocol involves the __enter__ and __exit__ methods.
An example is the with statement, which ensures resources are correctly managed without manual release.
with open('file.txt', 'r') as file:
print(file.read())
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]