Mastering the Adapter Pattern in Python: From Objects to Functional Elegance
Bridging Incompatible Interfaces
The
Prerequisites
To get the most out of this tutorial, you should have a firm grasp of

Key Libraries & Tools
- Beautiful Soup: A powerful library for parsing XML and HTML documents. It serves as our "Adaptee" in the XML examples.
- lxml: A high-performance XML parser that works as a backend for Beautiful Soup.
functools.partial: A built-in Python tool used to create "partial" functions by pre-filling some arguments of an existing function.typing.Protocol: A structural subtyping tool that allows us to define interfaces based on behavior rather than explicit inheritance.
Code Walkthrough: The Object-Based Adapter
The most robust version of this pattern uses composition. We define a Experiment class.
from typing import Protocol, Any
class Config(Protocol):
def get(self, key: str, default: Any = None) -> Any:
...
Next, we build the XmlConfig adapter. This class takes an instance of find and get_text methods into the standardized get method our application expects.
class XmlConfig:
def __init__(self, bs: BeautifulSoup):
self.bs = bs
def get(self, key: str, default: Any = None) -> Any:
value = self.bs.find(key)
return value.get_text() if value else default
By passing this adapter into the experiment, the core logic remains blissfully unaware of whether the data originated from a JSON dictionary or an XML file.
Syntax Notes: The Power of Partial
While classes are great, they can be overkill for a single-method interface. Python's functools.partial allows for a more concise functional approach. By "freezing" the Beautiful Soup instance into a general-purpose getter function, we create an adapter without the boilerplate of a class.
from functools import partial
def get_from_bs(bs: BeautifulSoup, key: str) -> Any:
# Implementation logic
...
# Create the functional adapter
config_getter = partial(get_from_bs, bs_instance)
This pattern is particularly elegant because it reduces the mental overhead for developers reading the code later. It targets the specific behavior needed without adding unnecessary layers of abstraction.
Tips & Gotchas
Avoid the class-based (inheritance) adapter whenever possible. In Python, inheriting from a complex library like get method that behaves differently than your expected get method, you will break the library's internal logic. Always prefer composition—wrapping the object—to ensure a clean separation of concerns and avoid side effects that are notoriously difficult to debug.

Fancy watching it?
Watch the full video and context