Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesAbstraction in Python means exposing the operations a caller needs while hiding implementation details that do not belong in the caller’s decision-making. Calling coffee_machine.brew("latte") is useful without knowing how water is heated, pressure is controlled, or beans are ground. Python applies the same idea through functions, modules, duck typing, abstract base classes, protocols, and composition.
The best abstraction is not the most elaborate one. It is the smallest stable boundary that reduces coupling and keeps legitimate implementation choices replaceable.
What abstraction means in Python
An abstraction has two sides:
- Interface: the operations and results that callers may rely on.
- Implementation: the steps, algorithms, state, and dependencies used to provide those operations.
For example, a payment service can expose charge(amount) while hiding HTTP requests, authentication, retries, and vendor-specific response parsing. Callers depend on the charging behavior rather than those accidental details.
Abstraction reduces cognitive load, isolates changes, permits substitute implementations, supports test doubles, and lets teams work against an agreed boundary. It is not automatically beneficial: unnecessary layers can obscure control flow and make simple code harder to understand.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallAbstraction and encapsulation are different
| Concept | Main question | Python example |
|---|---|---|
| Abstraction | What essential behavior should callers see? | payment_processor.charge(amount) |
| Encapsulation | How are state and implementation details controlled? | _balance, properties, and methods |
| Inheritance | What behavior or type relationship is reused? | class StripeProcessor(PaymentProcessor) |
| Polymorphism | Can different objects respond to one operation? | processor.charge(100) |
| Composition | Can behavior be assembled from collaborators? | A service containing a repository |
Python has no Java-style universal interface keyword or enforced private fields. A leading underscore communicates non-public intent; double leading underscores invoke name mangling mainly to avoid collisions, not to provide security. Encapsulation is therefore largely a matter of API design, conventions, properties, and controlled access.
Abstraction without abstract classes
Function-level abstraction
def send_welcome_email(user):
template = load_template("welcome.html")
body = render(template, user)
return smtp_client.send(user.email, body)
A caller uses one meaningful operation and does not depend on template loading, rendering, or SMTP details.
Module-level abstraction
from app.storage import save_user
save_user(user)
The rest of the application can use SQLite, PostgreSQL, or a file behind the same module API.
Duck typing
def export_report(writer, report):
writer.write(report)
Any object with a suitable write() operation can work at runtime; no base class is required. The trade-off is that an incompatible object may fail only when this code runs unless tests or static analysis detect the mismatch earlier.
Abstract base classes with ABC
The abc module provides ABC and @abstractmethod for explicit, nominal contracts. A class with unresolved abstract members cannot normally be instantiated through the ABC machinery.
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
@abstractmethod
def charge(self, amount: float) -> str:
"""Charge the amount and return a transaction ID."""
raise NotImplementedError
class StripeProcessor(PaymentProcessor):
def charge(self, amount: float) -> str:
return f"stripe-{amount:.2f}"
class TestProcessor(PaymentProcessor):
def charge(self, amount: float) -> str:
return f"test-{amount:.2f}"
def complete_purchase(processor: PaymentProcessor, amount: float) -> str:
return processor.charge(amount)
transaction_id = complete_purchase(StripeProcessor(), 49.99)
PaymentProcessor() raises TypeError while charge remains abstract; exact error wording can vary by Python version. The ABC design rationale and documentation explain that ABC uses ABCMeta to provide this machinery.
Abstract properties and class methods
from abc import ABC, abstractmethod
class Serializer(ABC):
@property
@abstractmethod
def media_type(self) -> str:
raise NotImplementedError
@classmethod
@abstractmethod
def from_bytes(cls, data: bytes):
raise NotImplementedError
Abstract methods may contain reusable code and a subclass may call super(). The documented decorator order matters: for properties, class methods, and static methods, place @abstractmethod closest to the function.
What ABCs do not guarantee
- They do not make methods private or secure.
- They do not prove semantic correctness. A method can return a fake success without charging money.
register()can mark an unrelated class as a virtual subclass for subclass checks, but does not inject methods or add the ABC to its method-resolution order.
Protocols and structural typing
A protocol describes the members an object must provide. A class can satisfy it without inheriting from it, which is structural subtyping. Type checkers such as mypy and Pyright use the declared members and signatures to check compatibility.
from typing import Protocol
class SupportsWrite(Protocol):
def write(self, text: str) -> int:
...
def save_message(target: SupportsWrite, message: str) -> int:
return target.write(message)
class FileWriter:
def write(self, text: str) -> int:
print(text)
return len(text)
FileWriter need not inherit from SupportsWrite. A static checker can recognize its compatible write method. The protocol proposal and typing reference define this behavior.
@runtime_checkable is shallow
from typing import Protocol, runtime_checkable
@runtime_checkable
class SupportsClose(Protocol):
def close(self) -> None:
...
isinstance(resource, SupportsClose)
Runtime protocol checks inspect whether required attributes exist; they do not fully verify signatures or behavior. Use them as limited capability checks, not as substitutes for tests or input validation. Protocols primarily provide value through static analysis and type-aware IDEs.
ABC or Protocol?
| Need | Better fit |
|---|---|
| Prevent incomplete instantiation at runtime | ABC |
| Share default implementation | ABC |
| Allow existing unrelated classes to conform | Protocol |
| Document a narrow capability for static checking | Protocol |
| Require an explicit nominal hierarchy | ABC |
Use runtime isinstance()/issubclass() checks |
ABC; protocols require careful @runtime_checkable use |
ABCs and protocols are not competing replacements. Choose an ABC when you own the implementations, need shared behavior, or require runtime instantiation enforcement. Choose a protocol when callers should depend on capability rather than inheritance and independent or third-party classes should qualify.
A practical notification abstraction
from typing import Protocol
class Notifier(Protocol):
def send(self, recipient: str, message: str) -> bool:
...
class EmailNotifier:
def send(self, recipient: str, message: str) -> bool:
print(f"Email to {recipient}: {message}")
return True
class SmsNotifier:
def send(self, recipient: str, message: str) -> bool:
print(f"SMS to {recipient}: {message}")
return True
class NotificationService:
def __init__(self, notifier: Notifier):
self.notifier = notifier
def notify(self, recipient: str, message: str) -> bool:
return self.notifier.send(recipient, message)
The service depends only on send(), not on a vendor class. An ABC version would add a shared nominal hierarchy and runtime prevention of incomplete subclasses, but would couple implementations to inheritance.
Recommended Free Tools
Composition: abstraction without inheritance
class OrderService:
def __init__(self, repository, payment_processor):
self.repository = repository
self.payment_processor = payment_processor
def place_order(self, order):
self.payment_processor.charge(order.total)
self.repository.save(order)
Composition is appropriate when a service needs replaceable capabilities rather than an “is-a” relationship. A protocol can document the repository or processor methods, while real implementations, fakes, and mocks can be supplied independently. Avoid creating an abstract base class solely because dependency injection is present.
Testing an abstraction
Test both the concrete behavior and the boundary used by its consumer. A small fake makes the service test independent of an email provider or network:
class FakeNotifier:
def __init__(self):
self.messages = []
def send(self, recipient: str, message: str) -> bool:
self.messages.append((recipient, message))
return True
fake = FakeNotifier()
service = NotificationService(fake)
assert service.notify("a@example.com", "Welcome") is True
assert fake.messages == [("a@example.com", "Welcome")]
Also test failure handling, invalid data, retries, and side effects that the contract promises. Neither an ABC nor a protocol can determine whether an implementation actually sends a message, preserves transaction boundaries, or returns truthful status.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Common mistakes and over-abstraction
Assuming every abstraction needs an ABC
Functions, modules, duck typing, protocols, properties, and composition often provide a clearer boundary with less ceremony.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →Best Value
Confusing NotImplementedError and NotImplemented
raise NotImplementedError signals that an abstract operation was called without an implementation. NotImplemented is a special return value used by certain binary and comparison methods during dispatch; returning it indiscriminately from ordinary abstract methods is incorrect.
Making an interface too broad
A twenty-method “god interface” forces implementations to depend on operations they do not need. Prefer small, role-specific contracts.
Treating annotations as runtime validation
Type hints and protocols do not automatically validate arbitrary runtime input. Perform explicit checks or use a validation library where external data requires it.
Hiding important behavior
An abstraction should not conceal consequential facts such as network calls, database writes, retries, caching, or transaction boundaries. Simplify the caller’s job without making side effects surprising.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Overusing inheritance
Deep hierarchies make behavior and method resolution difficult to trace. Use inheritance for a meaningful substitutable type relationship; use composition for a capability or collaborator.
Warning signs include interfaces created before any real variation exists, one-method forwarding classes, layers that change whenever internals change, and names such as BaseManager or AbstractFactoryProvider without a precise contract. Abstract stable variation, not hypothetical variation.
Choosing the least formal useful boundary
- Use a function or module for one coherent operation or a stable public API.
- Use duck typing for small, local interactions where behavior is obvious and tests cover the contract.
- Use an ABC for owned implementations, shared code, nominal relationships, or runtime instantiation checks.
- Use a Protocol for narrow capability contracts, independent implementations, and static type checking.
- Use composition when an object needs replaceable collaborators and no genuine subtype relationship exists.
Abstraction checklist
- What implementation detail is being hidden?
- What exact behavior does the caller need?
- Are there two or more legitimate implementations?
- Should the contract be informal, statically checked, or enforced at runtime?
- Would a function or composition be simpler than inheritance?
- Can the boundary and its failure behavior be tested independently?
The Bottom Line
Use the least formal abstraction that protects a stable boundary. Add an ABC when runtime enforcement or shared implementation matters; add a protocol when capability-based static typing matters; otherwise, a function, module, duck-typed collaborator, or composition may be the clearest Python design.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

