How typing.SupportsInt and SupportsIndex Work in Python
Structural protocols like typing.SupportsInt and
typing.SupportsIndex enable static duck typing in Python by
verifying whether an object implements specific conversion methods.
Instead of checking whether an object inherits directly from the
int class, type checkers and runtime guards inspect an
object's structural capabilities. This article explains how these two
protocols work, how they map to Python's special dunder methods, the
critical functional differences between them, and how to apply them
effectively in typed code.
The Concept of Structural Protocols
Python's typing system uses structural subtyping (defined in PEP 544)
to evaluate an object's type based on its operations and attributes
rather than its explicit class hierarchy. The typing module
provides several pre-defined protocols representing standard Python data
model behaviors.
Both SupportsInt and SupportsIndex are
runtime-checkable protocols. They allow static analyzers (like mypy or
Pyright) and runtime functions (via isinstance()) to verify
whether an arbitrary object can safely produce an integer
representation.
typing.SupportsInt
The typing.SupportsInt protocol checks whether an object
implements the __int__() special method. Objects satisfying
this protocol can be passed directly to the built-in int()
constructor for explicit type conversion.
from typing import SupportsInt
class StringLength:
def __init__(self, text: str):
self.text = text
def __int__(self) -> int:
return len(self.text)
def to_integer(val: SupportsInt) -> int:
return int(val)
item = StringLength("hello")
print(to_integer(item)) # Outputs: 5
print(isinstance(item, SupportsInt)) # Outputs: TrueThis protocol accepts types where conversion to an integer may
involve rounding, truncation, or transformation. For example, standard
Python float instances satisfy SupportsInt
because float defines __int__().
typing.SupportsIndex
The typing.SupportsIndex protocol checks whether an
object implements the __index__() special method (defined
in PEP 357). This method is used when Python requires a lossless,
unambiguous integer value, specifically for operations like sequence
indexing, slicing, and bitwise manipulation.
from typing import SupportsIndex
class CustomIndex:
def __init__(self, position: int):
self.position = position
def __index__(self) -> int:
return self.position
def get_first_elements(data: list[str], count: SupportsIndex) -> list[str]:
return data[:count]
names = ["Alice", "Bob", "Charlie", "Diana"]
idx = CustomIndex(2)
print(get_first_elements(names, idx)) # Outputs: ['Alice', 'Bob']
print(isinstance(idx, SupportsIndex)) # Outputs: TrueUnlike int(), sequence indexing does not invoke
__int__(). Python requires __index__() to
prevent unintentional or lossy conversions from types like
float.
Key Differences Between SupportsInt and SupportsIndex
The primary difference lies in the intended semantics of truncation versus lossless integer representation:
| Feature | SupportsInt |
SupportsIndex |
|---|---|---|
| Required Method | __int__(self) -> int |
__index__(self) -> int |
| Primary Use Case | Explicit conversion via
int(x) |
Slicing, indexing, bin(),
hex() |
Includes
float? |
Yes (via truncation) | No (prevents fractional indexing) |
| Equivalent Function | int(x) |
operator.index(x) |
Because float implements __int__ but does
not implement __index__, it satisfies
SupportsInt but fails SupportsIndex:
from typing import SupportsInt, SupportsIndex
value = 3.14
print(isinstance(value, SupportsInt)) # True
print(isinstance(value, SupportsIndex)) # FalseWhen to Use Each Protocol
Use SupportsInt when writing functions that convert
general numerical or custom types into an integer using
int(), and where truncation or explicit type casting is
acceptable.
Use SupportsIndex when writing functions that accept
integer-like inputs for slicing, sequence lookup, byte manipulations, or
memory offsets. Using SupportsIndex guarantees that the
caller provides a type that behaves strictly as an integer without
silent truncation bugs.