AlgoMaster Logo

Abstract Classes

Medium Priority15 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

Abstraction is the act of saying "here is what every payment method must do" without spelling out how any particular method does it. Python's abc module turns that idea into an enforceable contract: a base class can declare methods that subclasses are required to implement, and Python will refuse to instantiate the base class itself. This lesson covers what abstraction means in OOP, why duck typing alone leaves holes, and how ABC and @abstractmethod close them.

What Abstraction Means in OOP

Abstraction is one of the four pillars of object-oriented programming, alongside encapsulation, inheritance, and polymorphism. The everyday meaning is the one that matters: hide the messy details, expose a clean shape. When you say processor.charge(99.99), you don't want to know whether the call talks to a credit card system or a PayPal account or a store credit balance. You want one method name, one set of arguments, one predictable return.

The class that defines the shape (PaymentProcessor with a charge method) is the abstraction. The classes that fill in the details (CreditCardProcessor, PayPalProcessor, StoreCreditProcessor) are the concrete implementations. Code that uses these processors doesn't care which one it has; it just calls charge and trusts the contract.

The cyan box is the abstract class. It declares the shape (which methods exist and what they look like) but doesn't implement them. The orange boxes are concrete subclasses that fill in the implementation. Code elsewhere in the program can hold a reference typed as PaymentProcessor and not care which orange box it actually got.

The benefit shows up the moment you add a fourth payment method. If every caller talks to processor.charge(amount), adding a BankTransferProcessor is a matter of writing one class. No caller changes. That's the payoff abstraction is buying you.

The Problem With Just Relying on Duck Typing

Python has duck typing, and for small programs that's often enough. If something walks like a payment processor and quacks like a payment processor (it has a charge method that takes an amount), you can pass it where a payment processor is expected. No formal contract required.

This works. Neither class inherits from anything special; checkout just trusts that whatever it received has a charge method. The trouble starts when someone writes a new processor and forgets one of the methods, or names it slightly wrong:

The bug only shows up when checkout actually runs, at the moment of the call. If checkout only runs in a rare branch (a refund flow, a promotional credit, a failed retry), the error can hide for weeks. Duck typing trusts the developer to remember the contract. The contract isn't written down anywhere, so it relies on convention and memory.

This is the gap abc fills. By declaring "every PaymentProcessor must have a charge method", Python can catch a missing method at class creation time, not at call time. The bug surfaces when you write the class, not when an unlucky customer hits checkout.

abc.ABC and @abstractmethod

The abc module (short for "abstract base classes") lives in the standard library. To declare an abstract class, inherit from abc.ABC and decorate the methods you want subclasses to implement with @abc.abstractmethod:

Two things are happening here. First, PaymentProcessor inherits from ABC, which marks the class as abstract. Second, charge and refund are decorated with @abstractmethod, which marks those specific methods as abstract. The body of each method is just pass because the base class isn't supposed to do the work; that's the subclass's job.

A class becomes "abstract" the moment it has at least one method marked with @abstractmethod. Inheriting from ABC is what wires Python's metaclass machinery in so the abstractness is actually enforced. Skip the ABC part and the decorator becomes a polite suggestion with no teeth.

You can still write code inside an abstract method's body if you want. A common pattern is to put shared logic there and ask subclasses to call it through super():

The @abstractmethod decorator says "subclasses must override this", it doesn't say "the body has to be empty". Most of the time the body is pass or a docstring, but super().charge(amount) from the subclass remains a valid way to reuse logic.

Trying to Instantiate an Abstract Class Raises TypeError

The first thing ABC enforces is that you can't instantiate the abstract class directly. The whole point is that PaymentProcessor is a shape, not a usable object:

Python checks the abstract-method registry at the moment of construction and refuses. The error names the class and the methods that are still abstract. If you were hoping to use PaymentProcessor as a placeholder for "I'll figure out the details later", you can't; the class only becomes usable once a concrete subclass fills in every abstract method.

This guard is the main reason to bother with abc at all. Duck typing trusts you to never instantiate the base class; abc makes it a runtime error. Mistakes that would have silently produced a half-working object now fail loudly at the spot you tried to make one.

Subclass Must Implement All Abstract Methods

A subclass becomes concrete only when every abstract method from its parent has been overridden. Miss even one, and the subclass is still abstract and still can't be instantiated:

CreditCardProcessor implemented charge but forgot refund. Python knows because the abstract-method set propagates down the inheritance chain. Add the missing method and the class becomes instantiable:

The same rules apply to deeper hierarchies. If CreditCardProcessor itself only fills in charge and a further subclass PremiumCardProcessor(CreditCardProcessor) fills in refund, then PremiumCardProcessor is concrete and CreditCardProcessor is not. Abstractness is "any unimplemented abstract method in the chain", concrete is "every abstract method has been overridden somewhere on the path from object down to this class".

There's also no rule that says a concrete subclass has to mark anything as abstract itself. You can have a concrete subclass that adds new methods, new attributes, or stays a faithful implementation of the parent's shape. The only requirement is that no abstract method is left hanging.

@abstractmethod Combined With @property

Abstract methods cover behavior. Sometimes the contract is about state instead: every shippable item must expose a weight value, every discountable item must expose a price. Python lets you mark a @property as abstract by stacking the two decorators:

The order of the decorators matters: @property goes on top, @abstractmethod goes underneath. Python reads decorators bottom-up, so @abstractmethod runs first and marks the function as abstract, then @property wraps it as a property descriptor. Reverse the order and you get a property whose getter happens to be abstract but isn't tracked correctly in the abstract-method set, which means the enforcement breaks.

If Book had forgotten to define weight, trying to instantiate it would fail with the same TypeError you saw for abstract methods. The check is uniform: any name left abstract anywhere on the inheritance path blocks construction.

The point here is that @abstractmethod composes with @property to express "subclasses must expose this as a read-only attribute".

ABCMeta and Why Inheriting From ABC Matters

Under the hood, abstract classes use a special metaclass called ABCMeta. A metaclass is the class of a class, the thing that controls how a class is built. ABCMeta is what tracks the set of abstract methods and intercepts construction to raise TypeError when any of them are unimplemented.

You don't usually deal with ABCMeta directly because ABC is a tiny helper class defined in the abc module that does it for you:

Inheriting from ABC is the modern, readable way to get the metaclass wired up. The older way, still legal Python, is to set the metaclass directly:

Both styles produce an equivalent abstract class. class Foo(ABC) reads better and is the recommended form in any code written today. The metaclass=ABCMeta form is mostly useful when a class already needs a different base and you want to layer abstractness on top, or when you're working with code that predates ABC (added in Python 3.4).

The catch worth knowing: @abstractmethod only enforces anything when the class has ABCMeta somewhere in its metaclass chain. Decorate a method with @abstractmethod on a plain class that doesn't inherit from ABC and doesn't set metaclass=ABCMeta, and the decorator becomes a no-op. The class is freely instantiable and the abstract method is just a regular method with a confusing decorator on it:

The instantiation succeeds because PaymentProcessor doesn't use ABCMeta, and without ABCMeta there's nothing watching for unimplemented abstract methods. The decorator is still there as documentation, but it's not enforced. Always pair @abstractmethod with ABC or ABCMeta, or you've built a contract with no signatures on it.

When to Use ABC vs typing.Protocol

Python has two ways to describe "an object that has these methods". abc.ABC is one. The other is typing.Protocol (added in Python 3.8), and it's worth knowing the difference.

ABC is a nominal contract: a class is a PaymentProcessor because it inherits from PaymentProcessor. Inheritance is explicit, instantiation is checked at runtime, and forgetting a method is a TypeError the moment you try to create an instance.

Protocol is a structural contract: a class is a payment processor because it has the methods, regardless of what it inherits from. Static type checkers (like mypy or Pyright) verify the shape at type-check time. There's no runtime enforcement by default, and no inheritance required.

Aspectabc.ABCtyping.Protocol
StyleNominal (explicit inheritance)Structural (duck-typed)
Enforced atRuntime, on instantiationType-check time (mypy, Pyright)
Subclass relationshipRequired (class C(P))Not required
Caught errorTypeError for missing methodsType-check warning
Best forLibrary authors who want to lock the contractAPI surfaces that should accept anything matching the shape

The short rule: reach for ABC when you control the subclasses and want runtime guarantees that every implementation is complete. Reach for Protocol when you want to type-check that some object out there satisfies an interface without forcing it to inherit from anything. The two aren't mutually exclusive, plenty of codebases use both, and the structural-vs-nominal distinction is the lever that decides which fits.

For this lesson, the takeaway is that ABC isn't the only option and the two solve slightly different problems.

A Worked E-Commerce Example: Abstract PaymentProcessor

Pulling the pieces together, here's a small payment system using ABC:

PaymentProcessor declares the contract: every subclass must implement charge and refund. It also provides a concrete describe method that all subclasses inherit for free. Abstract classes can mix abstract and concrete methods; only the abstract ones are required overrides.

run_checkout takes any PaymentProcessor and uses it polymorphically. The function doesn't know or care which subclass it received. If a new payment method appears (Apple Pay, store credit, bank transfer), it slots in by inheriting from PaymentProcessor and implementing the two abstract methods. Every existing caller keeps working.

The same pattern fits other contracts. A Shippable abstract class with weight and dimensions abstract properties lets a shipping calculator handle books, electronics, clothing, anything that exposes the right two attributes. A Discountable abstract class with an abstract apply_discount(percent) method lets a promotion engine work on any item type without knowing which one it has.

Quiz

Abstract Classes Quiz

10 quizzes