A metaclass is the class of a class. Just as a class defines how instances behave, a metaclass defines how classes behave. The default metaclass is type.
class Meta(type):
def __new__(cls, name, bases, dct):
dct["version"] = 1.0
return super().__new__(cls, name, bases, dct)
class MyClass(metaclass=Meta):
pass
print(MyClass.version) # 1.0
You can also create classes dynamically using type:
DynamicClass = type("DynamicClass", (), {"x": 10})
obj = DynamicClass()
print(obj.x)
Context managers manage resources using the with statement. Implement __enter__ and __exit__ methods, or use contextlib.contextmanager.
class File:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
self.file.close()
with File("test.txt", "w") as f:
f.write("Hello, context manager!")
# Using contextlib
from contextlib import contextmanager
@contextmanager
def temporary_file(name):
f = open(name, "w")
try:
yield f
finally:
f.close()
Asynchronous programming in Python uses async def and await with the asyncio library for concurrent I/O.
import asyncio
async def fetch_data(url):
print(f"Fetching {url}...")
await asyncio.sleep(1) # Simulate network I/O
return f"Data from {url}"
async def main():
urls = ["url1", "url2", "url3"]
tasks = [fetch_data(url) for url in urls]
results = await asyncio.gather(*tasks)
return results
results = asyncio.run(main())
print(results)
Descriptors are objects that define __get__, __set__, or __delete__ methods. They control attribute access on classes.
class PositiveNumber:
def __get__(self, obj, objtype=None):
return obj.__dict__.get(self.name, 0)
def __set__(self, obj, value):
if value < 0:
raise ValueError("Must be positive")
obj.__dict__[self.name] = value
def __set_name__(self, owner, name):
self.name = f"_{name}"
class Order:
quantity = PositiveNumber()
order = Order()
order.quantity = 10
print(order.quantity)
Python uses reference counting and a generational garbage collector (gc module). Objects are freed when their reference count reaches zero.
import gc
gc.enable()
print(gc.get_count())
# Circular references
class Node:
def __init__(self):
self.ref = None
a = Node()
b = Node()
a.ref = b
b.ref = a
del a, b
gc.collect() # Force garbage collection
from typing import List, Dict, Optional, Tuple, Union
def process_items(items: List[str]) -> Dict[str, int]:
result: Dict[str, int] = {}
for item in items:
result[item] = len(item)
return result
def find_user(user_id: int) -> Optional[Dict]:
# Returns None if not found
pass
def combine(a: Union[int, str], b: Union[int, str]) -> str:
return str(a) + str(b)
Monkey patching dynamically modifies classes or modules at runtime. Use it sparingly as it can make code harder to debug.
class Dog:
def bark(self):
return "Woof!"
def howl(self):
return "Howl!"
Dog.howl = howl
d = Dog()
print(d.howl()) # Howl!
# Patching a module
import math
math.original_sqrt = math.sqrt
math.sqrt = lambda x: math.original_sqrt(x) if x >= 0 else 0
asyncio.gather and measures total time. Then create a custom descriptor that validates that a value is within a specified range.