Abstract Base Classes (ABCs) provide a way to define interfaces in Python. They ensure that derived classes implement specific methods or properties, acting as a blueprint and helping to avoid errors by catching missing implementations at runtime or even statically with type checkers. The abc
module provides the necessary tools.
import abc
class Vehicle(abc.ABC):
"""An abstract base class for vehicles."""
@abc.abstractmethod
def start(self):
"""Abstract method: Start the vehicle."""
pass
@abc.abstractmethod
def stop(self):
"""Abstract method: Stop the vehicle."""
pass
@property
@abc.abstractmethod
def fuel_type(self):
"""Abstract property: Get the fuel type."""
pass
def honk(self):
"""Concrete method: Vehicles can honk."""
print("Honk! Honk!")
class Car(Vehicle):
"""A concrete class implementing the Vehicle ABC."""
def __init__(self, fuel):
self._fuel = fuel # Private attribute to store fuel type
def start(self):
print("Car engine started.")
def stop(self):
print("Car engine stopped.")
@property
def fuel_type(self):
return self._fuel
# --- Usage ---
my_car = Car("Gasoline")
my_car.start()
my_car.honk()
print(f"Fuel type: {my_car.fuel_type}")
my_car.stop()
# Attempting to instantiate a class that doesn't implement all methods will raise a TypeError
# class Motorcycle(Vehicle):
# def start(self):
# print("Motorcycle started.")
# # This would raise: TypeError: Can't instantiate abstract class Motorcycle with abstract methods fuel_type, stop
# my_motorcycle = Motorcycle("Electric")
The itertools
module is a powerful standard library for creating fast, memory-efficient iterators. It provides a collection of building blocks that can be combined to form complex iterators, suitable for functional programming paradigms and optimizing loop performance.
import itertools
# --- Infinite Iterators ---
# count(start=0, step=1) - Creates an iterator that returns evenly spaced values
# print("Counting from 10 with step 2:")
# for number in itertools.count(10, 2):
# if number > 20:
# break
# print(number, end=" ") # Output: 10 12 14 16 18 20
# print("\n")
# cycle(iterable) - Creates an iterator that repeats the elements of iterable infinitely
# print("Cycling through colors:")
# colors = ['red', 'green', 'blue']
# for i, color in zip(range(7), itertools.cycle(colors)):
# print(color, end=" ") # Output: red green blue red green blue red
# print("\n")
# repeat(object[, times]) - Creates an iterator that repeats object times (or infinitely)
print("Repeating 'hello' 3 times:")
for _ in itertools.repeat('hello', 3):
print(_, end=" ") # Output: hello hello hello
print("\n")
# --- Terminatinng Iterators ---
# chain(*iterables) - Chains multiple iterables together
print("Chaining lists:")
list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']
chained = list(itertools.chain(list1, list2))
print(chained) # Output: [1, 2, 3, 'a', 'b', 'c']
print("\n")
# combinations(iterable, r) - r-length subsequences of elements from the input iterable
print("Combinations of [1, 2, 3] of length 2:")
combinations = list(itertools.combinations([1, 2, 3], 2))
print(combinations) # Output: [(1, 2), (1, 3), (2, 3)]
print("\n")
# permutations(iterable, r=None) - r-length permutations of elements from the input iterable
print("Permutations of ['A', 'B', 'C'] of length 2:")
permutations = list(itertools.permutations(['A', 'B', 'C'], 2))
print(permutations) # Output: [('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'C'), ('C', 'A'), ('C', 'B')]
print("\n")
# product(*iterables, repeat=1) - Cartesian product of input iterables
print("Product of [1, 2] and ['a', 'b']:")
product = list(itertools.product([1, 2], ['a', 'b']))
print(product) # Output: [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]
print("\n")
Identifying and fixing performance bottlenecks is crucial for efficient software. Python's standard library provides tools like timeit
for timing small code snippets and cProfile
for profiling larger applications to understand where time is being spent.
import cProfile
import timeit
def slow_function():
"""A function that simulates doing some work."""
total = 0
for i in range(10000):
for j in range(100):
total += i * j
return total
def fast_function():
"""A more efficient version of the same task."""
total = 0
# Optimized calculation (sum of i * sum of j)
sum_i = 10000 * (9999) // 2
sum_j = 100 * (99) // 2
total = sum_i * sum_j
return total
# --- Using timeit ---
# Measure the execution time of a small snippet
print("Timing slow_function:")
time_slow = timeit.timeit("slow_function()", setup="from __main__ import slow_function", number=1)
print(f"Slow function took: {time_slow:.4f} seconds\n")
print("Timing fast_function:")
time_fast = timeit.timeit("fast_function()", setup="from __main__ import fast_function", number=1)
print(f"Fast function took: {time_fast:.8f} seconds\n")
# --- Using cProfile ---
# Profile a function to see where time is spent
print("Profiling slow_function:")
cProfile.run('slow_function()')
# cProfile output shows calls per function, time spent in each, etc.
# Look for functions consuming the most cumulative time (tottime or cumtime)
While threading is suitable for I/O-bound tasks (like waiting for network responses), Python's Global Interpreter Lock (GIL) limits true parallelism for CPU-bound tasks. The multiprocessing
module bypasses the GIL by using separate processes, allowing you to leverage multiple CPU cores for parallel computation.
import multiprocessing
import time
import os
def cpu_bound_task(n):
"""A function that performs a CPU-intensive calculation."""
print(f"Process {os.getpid()}: Starting task with {n}")
result = 0
for i in range(n):
result += i * i
print(f"Process {os.getpid()}: Finished task with {n}")
return result
if __name__ == "__main__":
# The if __name__ == "__main__": guard is essential for multiprocessing
# to avoid issues with process creation on some operating systems.
print("Starting multiprocessing example...")
inputs = [1_000_000, 1_500_000, 2_000_000, 1_200_000]
start_time = time.time()
# Create a Pool of worker processes
# The default number of processes is os.cpu_count()
with multiprocessing.Pool() as pool:
# Map the function to the inputs, distributing work across processes
results = pool.map(cpu_bound_task, inputs)
end_time = time.time()
print("\nAll tasks completed.")
for i, result in enumerate(results):
print(f"Result for input {inputs[i]}: {result}")
print(f"\nTotal execution time: {end_time - start_time:.2f} seconds")
# Compare this to running the tasks sequentially:
# print("\nStarting sequential execution...")
# start_time_seq = time.time()
# sequential_results = [cpu_bound_task(n) for n in inputs]
# end_time_seq = time.time()
# print(f"Sequential execution time: {end_time_seq - start_time_seq:.2f} seconds")
Type hints, introduced in PEP 484, are annotations that indicate the expected types of variables, function arguments, and return values. They don't enforce types at runtime but significantly improve code readability, help static analysis tools (like MyPy) catch errors before execution, and aid in code maintenance.
from typing import List, Dict, Optional, Union, Tuple
# Variable type hints
age: int = 30
name: str = "Alice"
is_active: bool = True
# Function with type hints
def add_numbers(a: int, b: int) -> int:
"""Adds two integers and returns an integer."""
return a + b
# Function with list and return type hints
def greet_all(names: List[str]) -> None:
"""Greets all names in the list."""
for name in names:
print(f"Hello, {name}")
# Function with dictionary and optional return type
def find_person(people: Dict[str, int], search_name: str) -> Optional[int]:
"""Finds a person's age in a dictionary, returns age or None."""
return people.get(search_name)
# Function with Union type hint (accepts multiple types)
def process_input(value: Union[str, int]) -> str:
"""Processes a string or an integer."""
if isinstance(value, str):
return f"Processed string: {value.upper()}"
else:
return f"Processed integer: {value * 2}"
# Function returning a Tuple
def get_coordinates() -> Tuple[float, float]:
"""Returns a tuple of latitude and longitude."""
return (48.8566, 2.3522)
# --- Usage ---
print(add_numbers(5, 3))
# greet_all(["Alice", "Bob", "Charlie"]) # Uncomment to see output
my_people = {"Alice": 30, "Bob": 25}
alice_age = find_person(my_people, "Alice")
print(f"Alice's age: {alice_age}")
non_existent_age = find_person(my_people, "David")
print(f"David's age: {non_existent_age}") # Output: None
print(process_input("hello"))
print(process_input(123))
coords = get_coordinates()
print(f"Coordinates: {coords}")
# Running a static type checker (like MyPy) on this code would highlight potential type issues,
# e.g., calling add_numbers("a", "b") would be flagged as an error.
Introspection is the ability of a program to examine the type or properties of an object at runtime. Reflection allows a program to modify its own structure or behavior at runtime. Python offers built-in functions and modules (like inspect
) to perform introspection and reflection, enabling dynamic code, debugging, and serialization.
import inspect
class MyClass:
def __init__(self, name):
self.name = name
self._private_var = 10
def public_method(self, x, y=1):
"""A public method."""
return x + y
# --- Basic Introspection ---
obj = MyClass("Test Object")
print(f"Type of obj: {type(obj)}")
print(f"Is obj an instance of MyClass? {isinstance(obj, MyClass)}")
print(f"Has obj attribute 'name'? {hasattr(obj, 'name')}")
print(f"Value of attribute 'name': {getattr(obj, 'name')}")
# Using dir() to list attributes and methods
print("\nAttributes and methods of obj:")
print(dir(obj))
# Using getattr() to get methods and call them
method_to_call = getattr(obj, 'public_method')
print(f"\nResult of calling 'public_method': {method_to_call(5)}")
# Using setattr() to modify attributes
setattr(obj, 'new_attribute', 'dynamic value')
print(f"New attribute added dynamically: {obj.new_attribute}")
# --- Using the inspect module ---
print("\nInspecting public_method:")
print(f"Is public_method a function? {inspect.isfunction(obj.public_method)}")
print(f"Is public_method a method? {inspect.ismethod(obj.public_method)}")
# Get signature (parameters) of a function/method
signature = inspect.signature(obj.public_method)
print(f"Signature: {signature}")
print("Parameters:")
for name, param in signature.parameters.items():
print(f" {name}: {param.kind} (default: {param.default})")
# Get documentation string
print(f"\nDocstring: {inspect.getdoc(obj.public_method)}")
# Get source code (if available)
# try:
# print(f"\nSource code:\n{inspect.getsource(MyClass)}")
# except TypeError:
# print("\nSource code not available (e.g., for built-in types).")
Unlock a wealth of tutorials and software downloads available across our platform.