Python Protocol for Flexible Polymorphism

Resumen

Polymorphism lets different objects respond to the same message in their own way, and Python Protocol takes that idea further by defining contracts any class can fulfill without inheritance. If you're learning object oriented programming in Python, understanding how Protocol works will change how you design flexible, type safe code.

Think of walking into a restaurant and ordering the chef's special. Same request, different dish depending on who's cooking. That's polymorphism in action, and it's exactly what you'll model here.

What is polymorphism in Python and why does it matter?

Polymorphism is one of the four pillars of object oriented programming. It means a single method name can behave differently depending on the object that runs it.

In the previous lesson we built a solicitar_libro method that behaves differently when called from a Estudiante (student) or a Profesor (teacher). Same message, different outcome. That's pure polymorphism, and Python supports it natively thanks to duck typing: if it walks like a duck and quacks like a duck, then it's a duck. The class type doesn't matter, only that the object exposes the methods you need.

What is duck typing in Python? It's the principle that an object's suitability is determined by the methods it has, not by its class. If your object has the right method, Python treats it as valid.

How do you use Protocol from the typing module?

The typing.Protocol class lets you describe a structural contract: any object that implements the listed methods automatically satisfies the protocol, no inheritance required.

Here's the import and the contract you'll define:

python from typing import Protocol

class SolicitanteProtocol(Protocol): def solicitar_libro(self, titulo: str) -> str: """Solicita el préstamo de un libro por su título.""" ...

Three details matter here:

  • The class inherits from Protocol instead of from a base class of your own.
  • The method signature uses type hints (titulo: str, return str) so editors and type checkers can validate usage.
  • The body is just ... because a protocol describes shape, not behavior.

With this in place, any Estudiante, Profesor or future user class that exposes solicitar_libro automatically fulfills SolicitanteProtocol. No need to subclass it.

Why use Protocol instead of inheritance?

Traditional inheritance forces a rigid hierarchy. Protocol gives you the same guarantees with more flexibility:

  • You can validate objects from unrelated class trees.
  • You keep the freedom of duck typing while gaining static type checks.
  • You document the contract explicitly, so other developers know what methods to implement.

How do you apply Protocol to a list of users?

Once the protocol exists, you can use it to type a collection and enforce the contract at the editor level. Imagine your library app processes book requests from many users at once.

python estudiante1 = Estudiante("José", ...) profesor = Profesor(...)

usuarios: list[SolicitanteProtocol] = [estudiante1, profesor]

for usuario in usuarios: print(usuario.solicitar_libro("Cien años de soledad"))

When you run this, every user responds to solicitar_libro with its own logic. The student message differs from the teacher message, but the loop doesn't care. That's polymorphism doing the heavy lifting.

Now try adding a Libro object to that list:

python libro = Libro("Título de prueba", "Autor", "ISBN") usuarios: list[SolicitanteProtocol] = [estudiante1, profesor, libro]

Your editor immediately flags the error: Libro can't be assigned because it doesn't implement solicitar_libro. At runtime, Python raises an AttributeError for the same reason. The protocol catches the bug before it ships.

What happens if a class doesn't implement the protocol? Static type checkers mark it as an error, and at runtime Python throws an AttributeError when the missing method is called.

What does typing the list actually enforce?

Annotating usuarios: list[SolicitanteProtocol] does two things:

  • It tells your IDE and mypy to verify every item in the list exposes solicitar_libro.
  • It documents the intent of the collection so future contributors understand what belongs there.

If you later add the required method to Libro, the error disappears and the object joins the list seamlessly.

How can you practice Protocol with a real challenge?

The best way to internalize this pattern is to build one yourself. Take the Libro class from earlier lessons and design a protocol for it.

Your challenge:

  • Define LibroProtocol with methods like prestar and calcular_duracion.
  • Implement LibroFisico and LibroElectronico so each one fulfills the protocol with its own loan duration logic.
  • Create a list typed as list[LibroProtocol] and iterate it to confirm both classes work polymorphically.

You'll end up with interfaces without inheritance, flexible contracts, and static typing working together. That's the modern way to think about polymorphism in Python.

Share your LibroProtocol implementation in the comments and tell us how you handled the duration logic for each book type.