Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Python  Module 9 – Object-oriented Python and the data model

Abstract base classes and duck typing

Define interfaces with abc.ABC and abstractmethod, check capabilities with collections.abc, and type-check duck typing with typing.Protocol.

  • Intermediate
  • 20 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free core, Pro depth: 7 of 20 minutes free
Free: Duck typing judges an object by what it can do · An abstract base class refuses objects that miss a method · collections.abc names the kinds of container · 2 more sections. With a pass: 2 in-depth parts and 3 interview answers, about 13 minutes.

What you will learn

  • Define interfaces with abc.ABC and @abstractmethod, and read the error for a missing method
  • Check what a value can do with the abstract base classes of collections.abc
  • Explain structural typing with typing.Protocol and what @runtime_checkable checks
  • Choose between duck typing, an abstract base class and a protocol for a design

Before you start

What you’ll learn · Before you start

What you will learn

  • Define interfaces with abc.ABC and @abstractmethod, and read the error for a missing method
  • Check what a value can do with the abstract base classes of collections.abc
  • Explain structural typing with typing.Protocol and what @runtime_checkable checks
  • Choose between duck typing, an abstract base class and a protocol for a design

Before you start

On this page

A function that sends an alert calls channel.send(message), and it works with an email sender, a text-message sender or a test stand-in: three classes with nothing in common but that one method. That is duck typing: Judging an object by what it can do rather than by its class: code calls the methods it needs, and any object that has them works. A wrong object is found only when the call runs.: Python decides what an object can do when the code runs, not by the class it belongs to, so a wrong object is found only when the call reaches it. When a design needs a stated interface, Python offers two tools. abstract base class (ABC): A class, usually built on abc.ABC, that declares methods its subclasses must implement. Python refuses to create an object of a class that still lacks one of them, and the error names the missing method., built with the abc module, declare methods that subclasses must implement, and Python refuses to create an object of a class that is missing one. protocol (typing.Protocol): A class from typing that lists the methods an interface needs. Any class with those methods matches it without inheriting anything, and a type checker verifies the match before the program runs., from typing, describe an interface by its methods alone: any class with those methods matches without inheriting anything, and a type checker verifies the match before the program runs.

The collections.abc module adds ready-made abstract base classes, such as Iterable and Mapping, for the kinds of container. Below you will build the same interface three ways and compare them.

Duck typing judges an object by what it can do

broadcast() sends one message through every notifier it is given, and its second call is given a string:

One function, three kinds of notifier, no common base class Python · duck_typing.py
class EmailNotifier:
    def send(self, message):
        return f"email: {message}"


class SmsNotifier:
    def send(self, message):
        return f"sms: {message[:20]}"


class RecordingNotifier:  # a stand-in for tests: it only remembers what it was sent
    def __init__(self):
        self.sent = []

    def send(self, message):
        self.sent.append(message)
        return "recorded"


def broadcast(notifiers, message):
    return [notifier.send(message) for notifier in notifiers]  # asks no question about the class


recorder = RecordingNotifier()
print(broadcast([EmailNotifier(), SmsNotifier(), recorder], "Your order has shipped"))
print(recorder.sent)
try:
    broadcast(["not a notifier"], "hello")
except AttributeError as error:
    print("AttributeError:", error)

Output

['email: Your order has shipped', 'sms: Your order has shipp', 'recorded']
['Your order has shipped']
AttributeError: 'str' object has no attribute 'send'

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 duck_typing.py

broadcast() never asks what class an object is; it calls send() and uses the answer. The three notifiers share no base class, and the recording stand-in, which a test would use, works as well as the real ones. Python’s glossary calls this style duck typing, after the saying about a bird that looks and quacks like a duck: an object is accepted for what it does. Step through both calls:

Interactive · stage by stage broadcast() asks each object for send() Step through duck_typing.py. Each stage is one object that broadcast() is given; it calls send() on each without asking its class.
broadcast() asks each object for send(), step 4 of 4. Showing EmailNotifier() (returned an email line), SmsNotifier() (returned 20 characters), RecordingNotifier() (sent: 1 message), 'not a notifier' (AttributeError). In focus: 'not a notifier'.EmailNotifier()returned an email lineSmsNotifier()returned 20 charactersRecordingNotifier()sent: 1 message'not a notifier'AttributeErrorfound only when the call runs

The drawing shows the whole scene; its 4 steps are listed below.

Every step as text
  1. An email notifier. broadcast() calls send() on the first object without asking what it is. EmailNotifier returns 'email: Your order has shipped'.
  2. A text-message notifier. SmsNotifier shares no base class with EmailNotifier, but it has a send() method, which keeps the first 20 characters of the message.
  3. A stand-in for tests. RecordingNotifier only remembers the message and returns 'recorded'. A test can hand it to broadcast() and then read what it was sent from recorder.sent.
  4. A string, found out late. The second call passes a string. broadcast() calls send() on it like any other object, and only then does Python find that a str has no send() and raise AttributeError.

The price is paid when something is wrong. A string has no send(), and the mistake surfaces only when broadcast() reaches it, as an AttributeError, possibly after the first messages have gone out. Python code often goes one step further and simply tries an operation, handling the error if it fails, a style that the glossary calls EAFP: Easier to ask for forgiveness than permission: trying an operation and handling the exception if it fails, instead of checking first. The Python documentation calls it the common Python style. More in “Handling exceptions with try and except”: it is easier to ask for forgiveness than permission.

Checkpoint

Check: Duck typing

  1. Question 1 of 2 What happens with broadcast([EmailNotifier(), "oops", SmsNotifier()], "Hi")?

    Choose one answer.

    A hint

    broadcast() calls send() on each object in turn. When does Python notice that a string has none?

    Show the answer: Question 1 of 2

    Answer: The email is sent, then the string raises AttributeError, and the text message is never sent

    Duck typing calls the method and finds out then. The email notifier has already sent its message when the call on the string raises AttributeError, and SmsNotifier is never reached.

  2. Question 2 of 2 Why does RecordingNotifier work in broadcast(), although it inherits from nothing?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It has a send() method, and that is all broadcast() calls

    broadcast() calls notifier.send(message) and uses the result. Any object with that method works, which is why a recording stand-in can replace the real notifiers in a test.

An abstract base class refuses objects that miss a method

Here the interface is a class, and one of its three implementations forgets a method:

A payment interface, two implementations and one that forgot a method Python · payment_abc.py
from abc import ABC, abstractmethod


class PaymentMethod(ABC):
    @abstractmethod
    def pay(self, amount):
        """Charge amount, in the smallest unit of the currency, and return a reference."""

    @abstractmethod
    def refund(self, reference):
        """Give back the money of an earlier payment."""

    def charge(self, amount):  # a concrete method, shared by every payment method
        if amount <= 0:
            raise ValueError("the amount must be more than 0")
        return self.pay(amount)


class InstantTransfer(PaymentMethod):  # money moves from one bank account to another at once
    def __init__(self, address):
        self.address = address

    def pay(self, amount):
        return f"IT-{self.address}-{amount}"

    def refund(self, reference):
        return f"refunded {reference}"


class Card(PaymentMethod):
    def __init__(self, last4):
        self.last4 = last4

    def pay(self, amount):
        return f"CARD-{self.last4}-{amount}"

    def refund(self, reference):
        return f"refunded {reference}"


class GiftVoucher(PaymentMethod):  # forgot refund()
    def pay(self, amount):
        return f"VOUCHER-{amount}"


for method in (InstantTransfer("asha@bank"), Card("4242")):
    reference = method.charge(49900)
    print(reference, "|", method.refund(reference))
for cls in (PaymentMethod, GiftVoucher):
    try:
        cls()
    except TypeError as error:
        print("TypeError:", error)
print(sorted(PaymentMethod.__abstractmethods__), sorted(GiftVoucher.__abstractmethods__))

Output

IT-asha@bank-49900 | refunded IT-asha@bank-49900
CARD-4242-49900 | refunded CARD-4242-49900
TypeError: Can't instantiate abstract class PaymentMethod without an implementation for abstract methods 'pay', 'refund'
TypeError: Can't instantiate abstract class GiftVoucher without an implementation for abstract method 'refund'
['pay', 'refund'] ['refund']

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 payment_abc.py

PaymentMethod inherits from ABC and marks pay() and refund() with @abstractmethod, which makes each an abstract method: A method marked with @abstractmethod in an abstract base class. A subclass must override it before Python creates objects of the subclass; __abstractmethods__ lists the ones still missing.. Each concrete class implements both and inherits charge(), an ordinary method that checks the amount and then calls the subclass’s own pay(). PaymentMethod itself cannot be instantiated, and neither can GiftVoucher, which forgot refund(): the error names the missing method at the moment someone creates the object, long before a customer asks for a refund. __abstractmethods__ holds the names that a class still lacks. Step through the three classes and see when Python checks each of them:

Interactive · step by step When Python checks an abstract base class Step through payment_abc.py. Arrows point from each subclass to the abstract class it inherits from.
When Python checks an abstract base class, step 6 of 6. Showing PaymentMethod (TypeError: 2 missing), GiftVoucher (TypeError: refund missing), InstantTransfer (created), Card (created). In focus: GiftVoucher.inheritsinheritsPaymentMethodTypeError: 2 missingGiftVoucherTypeError: refund missingInstantTransfercreatedCardcreated

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Two abstract methods. PaymentMethod inherits from ABC and marks pay and refund with @abstractmethod. charge() is an ordinary method, which every subclass inherits.
  2. An instant transfer. InstantTransfer implements both abstract methods, so nothing is missing and Python creates the object.
  3. charge(49900). charge() is found in PaymentMethod. It checks that the amount is more than 0 and calls self.pay(), which runs the subclass's own pay and returns IT-asha@bank-49900.
  4. A card. Card implements both methods as well, so it is created, and its charge() returns CARD-4242-49900.
  5. The interface itself. PaymentMethod still has both methods missing, so creating one fails with TypeError, and the message names pay and refund.
  6. A voucher without refund. GiftVoucher has pay but no refund. Python refuses it the moment someone tries to create one, and the message names refund, long before any customer asks for a refund.

Abstract base classes suit interfaces that other people implement, such as plugins, because the rule is enforced in one place, for every implementation.

Checkpoint

Check: Abstract base classes

  1. Question 1 of 2 When does Python report that GiftVoucher lacks refund()?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: When code tries to create a GiftVoucher object

    class GiftVoucher(PaymentMethod) is accepted, but GiftVoucher() raises TypeError: Can't instantiate abstract class GiftVoucher without an implementation for abstract method 'refund'.

  2. Question 2 of 2 class Partial(PaymentMethod) defines only pay(), and class Full(Partial) defines only refund(). Which of the two can be created?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Only Full, because it inherits pay() from Partial and defines refund(), so nothing is missing

    Partial.__abstractmethods__ holds refund, so Partial() is refused. Full inherits pay() from Partial and adds refund(), its set of abstract methods is empty, and Full() is created.

collections.abc names the kinds of container

The program asks which of six values, and of one class of its own, count as each kind of container:

Which values count as which kind of container Python · container_abcs.py
from collections.abc import Callable, Iterable, Mapping, Sequence, Sized


class Countdown:  # inherits from nothing; it only has __iter__
    def __init__(self, start):
        self.start = start

    def __iter__(self):
        return iter(range(self.start, 0, -1))


checks = [Iterable, Sized, Sequence, Mapping, Callable]
values = {"[1, 2]": [1, 2], "'ab'": "ab", "{'k': 1}": {"k": 1}, "{1, 2}": {1, 2}, "Countdown(3)": Countdown(3), "len": len}
print(f"{'':13}" + "".join(f"{abc.__name__:>9}" for abc in checks))
for label, value in values.items():
    print(f"{label:13}" + "".join(f"{'yes' if isinstance(value, abc) else '-':>9}" for abc in checks))
print(list(Countdown(3)), Iterable in Countdown.__mro__)

Output

              Iterable    Sized Sequence  Mapping Callable
[1, 2]             yes      yes      yes        -        -
'ab'               yes      yes      yes        -        -
{'k': 1}           yes      yes        -      yes        -
{1, 2}             yes      yes        -        -        -
Countdown(3)       yes        -        -        -        -
len                  -        -        -        -      yes
[3, 2, 1] False

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 container_abcs.py

The module defines abstract base classes for the kinds of container: Iterable, Sized, Sequence, Mapping, Callable and many more. A list and a string are sequences, a dictionary is a mapping, and a set is neither. Countdown inherits from nothing, yet it counts as Iterable: the simplest of these classes recognise a class by the one method they need, here __iter__, although Iterable is not in its MRO. Sequence and Mapping recognise only classes that inherit from them or are registered with them, because __getitem__ and __len__ alone cannot tell a sequence from a mapping. Checking isinstance(value, Mapping) instead of isinstance(value, dict) lets a function accept every mapping, not only dictionaries.

Checkpoint

Check: The kinds of container

  1. Question 1 of 2 Countdown inherits from nothing and defines __iter__. Why is isinstance(Countdown(3), Iterable) true?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Iterable recognises any class that has an __iter__ method

    Iterable, Sized and Callable each recognise a class by the one method they describe. Sequence and Mapping do not, because __getitem__ and __len__ alone cannot tell a sequence from a mapping.

  2. Question 2 of 2 os.environ behaves like a dictionary of strings but is not a dict. A function must accept it as well as a plain dictionary. Which test fits?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: isinstance(value, Mapping)

    Checking the abstract base class instead of one concrete type lets a function accept every mapping, os.environ included, not only dictionaries.

A protocol is duck typing that a type checker verifies

The same idea can be written down as a class that lists the methods:

A Sender protocol that Pager matches without inheriting it Python · protocol_send.py
import io
from typing import Protocol, runtime_checkable


@runtime_checkable
class Sender(Protocol):
    def send(self, message: str) -> str: ...


class Pager:  # no base class, no registration: it just has send()
    def send(self, message: str) -> str:
        return f"page: {message}"


def alert(channel: Sender, message: str) -> str:
    return channel.send(message)


print(alert(Pager(), "disk 91% full"))
print(isinstance(Pager(), Sender), Sender in Pager.__mro__, isinstance("text", Sender))


def first_line(source: io.Reader[str]) -> str:  # io.Reader is new in Python 3.14
    return source.read().splitlines()[0]


print(first_line(io.StringIO("line one\nline two")), isinstance(io.StringIO(), io.Reader))

Output

page: disk 91% full
True False False
line one True

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 protocol_send.py

Sender is a protocol: a class whose body lists the methods an interface needs. Pager never mentions it and still matches it, because it has a send() method of the right shape. A static type checker, such as mypy or pyright, checks every call of alert() against the protocol before the program runs, signatures included. That is structural typing: Matching a type by its shape, the methods and attributes it has, as typing.Protocol does, instead of by the classes it inherits from., matching by shape, where abstract base classes use nominal typing: Matching a type by name: an object fits only if its class inherits from the named class or is registered with it, as with abstract base classes., matching by the inherited name. @runtime_checkable also allows isinstance() checks against a protocol, which test only that the methods exist: isinstance(Pager(), Sender) is true, while Sender is not in the MRO of Pager.

Version note

New in Python 3.14: io.Reader and io.Writer are protocols for the simplest streams, an object with read() and one with write(). first_line() accepts any object that can read() text, such as an open file or an io.StringIO.

The diagram below sets the three ways side by side, each panel named after the moment a missing method is found.

Duck typing calls send() on any object; a class inherits an ABC and is checked when made; a class matches a Protocol by its methods.Duck typing: at the callABC: when an object is madeProtocol: by a type checkerbroadcast()any objectwith send()CardPaymentMethod (ABC)PagerSender (Protocol)calls send()inheritsmatches

Three ways to state an interface

Text description of the diagram

The diagram has three panels, one above the other, each named after when a missing method is found.

  • Duck typing, at the call: broadcast() calls send() on each item, and any object with a send() method works. A missing method shows up only when the call is made.
  • ABC, when an object is made: Card inherits from PaymentMethod, an abstract base class. Python refuses to create an object of a class that has not implemented every abstract method.
  • Protocol, by a type checker: Pager does not inherit from Sender, a Protocol. A dashed arrow labelled "matches" shows that it fits the protocol because it has the methods the protocol describes; a static type checker verifies the match before the program runs.
Three ways to state an interface
Criterion Duck typingABCProtocol
Stated by the calls aloneabstract methodsa list of methods
Classes have the methodsinherit or registerhave the methods
Caught when the call runswhen an object is madeby a type checker
When to choose Small code that calls a method or twoInterfaces that other people implementClasses you cannot change, checked statically

Checkpoint

Check: Protocols and structural typing

  1. Question 1 of 2 Pager never mentions Sender. Why does a type checker accept alert(Pager(), "disk 91% full")?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Pager has a send() method of the right shape, and a protocol is matched by shape

    A static type checker compares the methods of Pager with those that Sender lists, signatures included. They match, so the call is accepted without any inheritance.

  2. Question 2 of 2 first_line() is annotated with io.Reader[str]. Which arguments does a type checker accept?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: An open text file, an io.StringIO, or any object whose read() returns text

    New in Python 3.14, io.Reader and io.Writer are protocols for the simplest streams. An open file and an io.StringIO both have read(), so both match io.Reader[str].

Check yourself

Practice that adapts to you

Practise this lesson

12 questions about 12 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 12 In duck typing, what decides whether a function accepts an object?

    Choose one answer.

    Show the answer: Question 1 of 12

    Answer: Whether the object has the methods the function calls

    Python's glossary describes duck typing as judging an object by what it does. The cost is that a wrong object is found only when the call reaches it.

  2. Question 2 of 12 What does this print?

    Read the code, then choose one answer.

    def total_length(items):
        try:
            return sum(len(x) for x in items)
        except TypeError:
            return None
    
    print(total_length(["ab", (1, 2, 3)]), total_length([5]))
    Show the answer: Question 2 of 12

    Answer: 5 None

    This is the EAFP style: try the operation and handle the failure, instead of checking each item's type first. Anything with a length counts, whatever its class.

  3. Question 3 of 12 What does payment_abc.py print?

    What does this program print? Choose one answer.

    from abc import ABC, abstractmethod
    
    
    class PaymentMethod(ABC):
        @abstractmethod
        def pay(self, amount):
            """Charge amount, in the smallest unit of the currency, and return a reference."""
    
        @abstractmethod
        def refund(self, reference):
            """Give back the money of an earlier payment."""
    
        def charge(self, amount):  # a concrete method, shared by every payment method
            if amount <= 0:
                raise ValueError("the amount must be more than 0")
            return self.pay(amount)
    
    
    class InstantTransfer(PaymentMethod):  # money moves from one bank account to another at once
        def __init__(self, address):
            self.address = address
    
        def pay(self, amount):
            return f"IT-{self.address}-{amount}"
    
        def refund(self, reference):
            return f"refunded {reference}"
    
    
    class Card(PaymentMethod):
        def __init__(self, last4):
            self.last4 = last4
    
        def pay(self, amount):
            return f"CARD-{self.last4}-{amount}"
    
        def refund(self, reference):
            return f"refunded {reference}"
    
    
    class GiftVoucher(PaymentMethod):  # forgot refund()
        def pay(self, amount):
            return f"VOUCHER-{amount}"
    
    
    for method in (InstantTransfer("asha@bank"), Card("4242")):
        reference = method.charge(49900)
        print(reference, "|", method.refund(reference))
    for cls in (PaymentMethod, GiftVoucher):
        try:
            cls()
        except TypeError as error:
            print("TypeError:", error)
    print(sorted(PaymentMethod.__abstractmethods__), sorted(GiftVoucher.__abstractmethods__))
    Show the answer: Question 3 of 12

    Answer: it prints

    IT-asha@bank-49900 | refunded IT-asha@bank-49900
    CARD-4242-49900 | refunded CARD-4242-49900
    TypeError: Can't instantiate abstract class PaymentMethod without an implementation for abstract methods 'pay', 'refund'
    TypeError: Can't instantiate abstract class GiftVoucher without an implementation for abstract method 'refund'
    ['pay', 'refund'] ['refund']

    An abstract base class cannot be instantiated, and neither can a subclass that leaves an abstract method unimplemented: GiftVoucher has pay() but not refund(), so creating it fails with a message that names the missing method. __abstractmethods__ holds the names still missing in each class.

  4. Question 4 of 12 What does this print?

    Read the code, then choose one answer.

    from abc import ABC, abstractmethod
    
    class Shape(ABC):
        @abstractmethod
        def area(self): ...
    
        def describe(self):
            return f"area {self.area()}"
    
    class Square(Shape):
        def __init__(self, side):
            self.side = side
    
        def area(self):
            return self.side ** 2
    
    print(Square(3).describe())
    Show the answer: Question 4 of 12

    Answer: area 9

    Square implements the only abstract method, so it can be instantiated, and it inherits the concrete method describe(), which calls the subclass's area().

  5. Question 5 of 12 Which statements are true? Choose every right answer.

    Choose every answer that is right.

    Show the answer: Question 5 of 12

    Answer:

    • A type checker accepts any object whose methods match a Protocol, without inheritance
    • A subclass of an ABC that leaves an abstract method unimplemented cannot be instantiated

    An ABC is checked when an object is created, and a protocol is matched by its methods alone. Duck typing needs no common base at all. A class that misses an abstract method is accepted until someone creates an object of it, which is the moment Python names the missing method.

  6. Question 6 of 12 A payment class may be written without refund(). Which design reports that mistake earliest?

    Choose one answer.

    Show the answer: Question 6 of 12

    Answer: An abstract base class with refund() marked @abstractmethod

    An abstract base class turns a missing method into an error at the moment an object is created, with the method named in the message. The other designs wait until the method is called.

  7. Question 7 of 12 A class inherits from nothing and defines only __iter__. What is isinstance(obj, collections.abc.Iterable) for one of its objects?

    Choose one answer.

    Show the answer: Question 7 of 12

    Answer: True, because Iterable recognises any class with an __iter__ method

    Simple abstract base classes of collections.abc, such as Iterable, Sized and Callable, count a class as a subclass when it has the method they describe, without inheritance or registration.

  8. Question 8 of 12 What does this print?

    Read the code, then choose one answer.

    from collections.abc import Sequence, Sized
    
    print(isinstance({1, 2}, Sequence), isinstance({1, 2}, Sized))
    Show the answer: Question 8 of 12

    Answer: False True

    The row of the set in the program's table says the same: Iterable and Sized yes, Sequence and Mapping no. One value can belong to several kinds.

  9. Question 9 of 12 A class inherits from nothing and defines __getitem__ and __len__. Is one of its objects a Sequence for isinstance()?

    Choose one answer.

    Show the answer: Question 9 of 12

    Answer: No, because Sequence recognises only classes that inherit from it or are registered with it

    Sequence and Mapping recognise only their subclasses and registered classes, because the same two methods could belong to a mapping. Inherit from Sequence, or register the class, to make the answer yes.

  10. Question 10 of 12 What does @runtime_checkable add to a typing.Protocol class?

    Choose one answer.

    Show the answer: Question 10 of 12

    Answer: isinstance() checks that the protocol's members exist, without checking their signatures

    Without the decorator, a protocol cannot be used with isinstance() at all. With it, isinstance() makes a shallow check that the members are present. Type checkers verify protocols with or without it, signatures included, before the program runs.

  11. Question 11 of 12 What does this print?

    Read the code, then choose one answer.

    from typing import Protocol, runtime_checkable
    
    @runtime_checkable
    class Closer(Protocol):
        def close(self) -> None: ...
    
    class Door:
        def close(self) -> None:
            print("shut")
    
    print(isinstance(Door(), Closer), Closer in Door.__mro__)
    Show the answer: Question 11 of 12

    Answer: True False

    That is structural typing: the check looks at what Door has, not at what it inherits, as with Pager and Sender in the lesson's program.

  12. Question 12 of 12 A function must accept objects from a library whose classes you cannot change, and the project runs a type checker on every change. How do you state the interface?

    Choose one answer.

    Show the answer: Question 12 of 12

    Answer: As a typing.Protocol that lists the methods the function calls

    A protocol states the interface without asking anything of the classes that match it, which is what classes you cannot change need. The type checker then verifies the calls before the program runs.

How abstract base classes decide, with four traps reproduced Pro: opens with a Pro, Premium or Ultimate pass

An isinstance() check against an abstract base class can come out true for three different reasons, and only one of them involves checking that methods exist. This part takes the three apart, reproduces four traps of the abc module, counts the work that collections.abc does for a class that inherits from it, and gives an abstract method a body.

In this part:

  • Inherit, register or hook: three reasons for isinstance to say yes
  • Four traps reproduced, from silent iterables to ignored decorators
  • Two methods written, five inherited: the mixin methods of Sequence, counted
  • Abstract does not mean empty: a method with a body, reused through super()

4 runnable examples (Python) · about 7 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Protocols in practice: what each check covers, measured, and how to choose Pro: opens with a Pro, Premium or Ultimate pass

A protocol is checked thoroughly by a type checker before the program runs, and only shallowly by isinstance() while it runs. This part shows exactly what the run-time check covers, measures what it costs, tests the ready-made protocols of typing, inherits a protocol on purpose, and ends with a decision table and an exercise.

In this part:

  • What a run-time protocol check lets through, reproduced
  • The cost of a run-time protocol check, measured
  • Ready-made protocols in typing, and what they really test
  • Inheriting a protocol on purpose, to get its default methods
  • Choosing between duck typing, an ABC and a protocol
  • Exercise: notifiers, an ABC and anything with send()
    • A notifier interface, and a broadcast that takes anything with send()

4 runnable examples (Python) · 1 exercise with sample tests · about 14 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Interview questions

  1. Fresher · Warm-up

    What is duck typing?

    Duck typing is the Python habit of judging an object by what it can do rather than by its class. A function that calls source.read() accepts a file, a network stream or a test stand-in, as long as each has a read method, and nothing has to inherit from a common base class. The name comes from the saying about a bird that looks and quacks like a duck. It goes with the EAFP style: try the operation and handle the exception if it fails, instead of checking types first. The cost is that a wrong object is found only when the call runs. Abstract base classes and typing.Protocol add structure on top: an ABC enforces an interface when an object is created, and a protocol lets a type checker verify duck typing before the program runs.

  2. Mid to senior

    Your library's StorageBackend abstract base class gained a new abstract method, delete_many(), in a minor release. A dozen backends written by other teams now fail with TypeError: Can't instantiate abstract class. How should you have added the method, and how do you roll out the change now?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    You are designing the cache interface of an open-source library that other projects will implement and call. Would you publish it as an abstract base class or as a typing.Protocol? Defend your choice, and say what you would give implementers either way.

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    Code checks isinstance(conn, SupportsClose), a runtime-checkable Protocol, before calling conn.close(timeout=5), and it still crashes with TypeError for some connection objects. Why did the check pass, what does it actually test, and what would you do instead?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Duck typing accepts any object that has the methods a function calls; mistakes show up when the call runs.
  • Abstract base classes declare methods with @abstractmethod, and Python refuses to create objects of classes that have not implemented them.
  • collections.abc describes the kinds of container: check isinstance(value, Mapping) rather than a concrete type.
  • A typing.Protocol is matched by its methods alone; a type checker verifies it before the program runs, and @runtime_checkable adds a shallow isinstance() check.
  • Use abstract base classes for interfaces that others implement, and protocols for accepting classes you cannot change.

Terms in this lesson

abstract base class (ABC)
A class, usually built on abc.ABC, that declares methods its subclasses must implement. Python refuses to create an object of a class that still lacks one of them, and the error names the missing method.
abstract method
A method marked with @abstractmethod in an abstract base class. A subclass must override it before Python creates objects of the subclass; __abstractmethods__ lists the ones still missing.
duck typing
Judging an object by what it can do rather than by its class: code calls the methods it needs, and any object that has them works. A wrong object is found only when the call runs.
nominal typing
Matching a type by name: an object fits only if its class inherits from the named class or is registered with it, as with abstract base classes.
protocol (typing.Protocol)
A class from typing that lists the methods an interface needs. Any class with those methods matches it without inheriting anything, and a type checker verifies the match before the program runs.
structural typing
Matching a type by its shape, the methods and attributes it has, as typing.Protocol does, instead of by the classes it inherits from.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.