Skip to content

Duck typing

Duck typing lets unrelated objects share a calculation through the attributes and operations they support, while leaving validation and financial meaning explicit.

Duck typing is a programming style in which code uses an object's available attributes and operations without requiring a particular class or inheritance relationship. In a quant calculation, this can let an internal position object and an imported record pass through the same valuation function, provided both supply the interface that function actually uses. Python's glossary describes this emphasis on behavior rather than concrete type.

One calculation, two unrelated classes

Suppose two fictional share positions provide quantity and price, with price measured in USD per share. The calculation only needs to read those attributes and multiply their values:
from dataclasses import dataclass @dataclass class DeskPosition: quantity: int price: float class ImportedPosition: def __init__(self, quantity: int, price: float) -> None: self.quantity = quantity self.price = price def market_value(position): """Return quantity times price; the caller supplies valid share data.""" return position.quantity * position.price print(market_value(DeskPosition(2, 100.0))) # 200.0 print(market_value(ImportedPosition(3, 50.0))) # 150.0
The first position is worth USD 200; the second is worth USD 150. Neither class inherits from the other, and market_value does not branch on the class name. Their shared protocol here is informal: readable quantity and price attributes whose multiplication produces the intended market value. The classes may have quite different methods and internal storage beyond that small interface.
This is one way to achieve interchangeable behavior in object-oriented programming. A common custom base class is not required. The example deliberately leaves the position parameter unannotated so the runtime mechanism is visible.
DeskPosition with 2 shares at USD 100 and ImportedPosition with 3 shares at USD 50 both use market_value, producing USD 200 and USD 150. The required operations are attribute access and multiplication.
DeskPosition with 2 shares at USD 100 and ImportedPosition with 3 shares at USD 50 both use market_value, producing USD 200 and USD 150. The required operations are attribute access and multiplication.
Two unrelated position classes use the same calculation. Fictional inputs are in shares and USD per share; outputs are in USD.

The operation can fail—or succeed with the wrong meaning

Duck typing does not manufacture a missing interface. Continuing the example:

from types import SimpleNamespace for position in (SimpleNamespace(quantity=2), SimpleNamespace(quantity=2, price=None)): try: market_value(position) except (AttributeError, TypeError) as error: print(type(error).__name__) # AttributeError # TypeError print(market_value(SimpleNamespace(quantity=3, price="50"))) # 505050
The first object has no price, so attribute access raises AttributeError. The second has both attributes, but multiplying an integer by None raises TypeError. The third is subtler: Python repeats the string "50" three times. It produces a string, not a USD value, without raising either error. Merely checking that both attribute names exist would miss both the unusable None and the financially meaningless string result.
Keep the structural interface separate from the economic contract. The caller or an input-validation layer must still establish numeric values, price units, currency, mark time and the quantity convention. This particular formula values shares; a futures contract may require a contract multiplier. Replacing an object is safe only when its behavior and meaning meet the caller's needs. When using exception handling, catch failures where a useful recovery or clear diagnostic is possible; silently substituting a zero valuation hides the broken input.

Duck typing and Protocol answer different questions

Dynamic duck typing concerns what happens when operations execute. Python's typing.Protocol instead lets a type annotation describe a structural interface for a static type checker. For example, a position protocol could declare numeric quantity and price attributes; conforming classes need not explicitly inherit from that protocol. The official typing documentation explains this structural subtyping.
Annotations alone do not validate an object when a function is called. Even a protocol decorated with @runtime_checkable checks attribute presence rather than the declared attribute types or method signatures. Neither mechanism proves that a price is in the right currency or that a valuation formula fits the instrument.
For a larger quant design using market-data objects and composition, see Object-Oriented Model Design. Its public preview outlines the lesson; the full lesson requires membership.

Sources

Last updated
  • python
  • software design
  • interfaces
← Back to the glossaryBack to top ↑