Reference
Overview
The API of optype is flat; a single import optype as opt is all you need (except for optype.numpy).
All typing protocols in optype are runtime-checkable, which means you can use isinstance() to check whether an object implements a protocol:
import optype as op
isinstance('hello', op.CanAdd) # True
isinstance(42, op.CanAbs) # True
isinstance([1], op.CanGetitem) # True
Unlike collections.abc, optype's protocols aren't abstract base classes (they don't extend abc.ABC, only typing.Protocol). This allows the protocols to be used as building blocks for .pyi type stubs.
The Five Flavors
There are five categories of types in optype:
1. Just[T] Types
The optype.Just[T] type and its subtypes (JustInt, JustFloat, JustComplex, etc.) only accept instances of the type itself, rejecting instances of strict subtypes.
This can be used to:
- Work around
floatandcomplextype promotions - Annotate
object()sentinels withJust[object] - Reject
boolin functions that acceptint
2. Can* Protocols
optype.Can* types describe what can be done with an object.
For instance, any CanAbs[T] type can be used as an argument to the abs() builtin function with return type T. Most Can* protocols implement a single special method whose name directly matches that of the type:
CanAbsimplements__abs__CanAddimplements__add__CanGetitemimplements__getitem__
See: Core Types sections for different operation categories
3. Has* Protocols
optype.Has* is the analogue of Can*, but for special attributes:
HasNamehas a__name__attributeHasDicthas a__dict__attributeHasDochas a__doc__attribute
4. Does* Types
optype.Does* types describe the type of operators.
DoesAbsis the type of theabs()builtin functionDoesPosis the type of the+unary prefix operatorDoesAddis the type of the+binary infix operator
See: Individual operation documentation pages
5. do_* Functions
optype.do_* are correctly-typed implementations of Does*. For each do_* there is a Does*, and vice-versa:
do_abs: DoesAbsis a typed alias ofabs()do_pos: DoesPosis a typed version ofoperator.pos
The optype.do_* operators are:
- More complete than
operatormodule - Have runtime-accessible type annotations
- Have names you don't need to know by heart
See: Individual operation documentation pages
Core Types
These are the fundamental protocols for Python's builtin operations:
- Just: Exact type matching
- Type Conversion:
int(),float(),str(),bool(), etc. - Rich Relations:
==,!=,<,<=,>,>= - Binary Operations:
+,-,*,/,@,%,**, etc. - Reflected Operations:
__radd__,__rmul__, etc. - Inplace Operations:
+=,-=,*=, etc. - Unary Operations:
+,-,~,abs(), etc. - Rounding:
round(),trunc(),floor(),ceil() - Callables:
__call__, function protocols - Iteration:
iter(),next(),__iter__,__next__ - Awaitables:
await,__await__ - Async Iteration:
async for,__aiter__,__anext__ - Containers:
len(),[],in,reversed(), etc. - Attributes:
__name__,__dict__,__doc__, etc. - Context Managers:
with,async with - Descriptors:
__get__,__set__,__delete__ - Buffer Types: Memory views and buffer protocol
Standard Library Modules
These modules provide protocols for Python's standard library:
- optype.copy: Shallow and deep copying
- optype.dataclasses: Dataclass protocols
- optype.inspect: Introspection protocols
- optype.io: File I/O protocols
- optype.json: JSON serialization
- optype.pickle: Pickling protocols
- optype.string: String formatting
- optype.typing: Typing utilities and aliases
- optype.dlpack: DLPack protocol for array interchange
NumPy Support
NumPy-specific typing utilities (requires NumPy):
- Introduction: Overview of NumPy support
- Shape Typing: Array shapes and dimensions
- Array-likes: Array-like protocols
- Literals: Literal types for NumPy
- Compatibility: Cross-version compatibility
- Random: Random number generators
- DType: Data type objects
- Scalar: NumPy scalar types
- UFunc: Universal functions
- Type Aliases: Common type aliases
- Low-level: Low-level NumPy interfaces
Type Variance Notation
In the reference docs we use a fictional notation to describe the variance of generic type parameters:
~T: invariant+T: covariant-T: contravariant
See the typing spec for an explanation
Performance Considerations
isinstance() with Protocols
All optype protocols are runtime-checkable, which allows them to be used with isinstance(). However, it's important to understand the performance implications:
Performance Caveat
isinstance() checks against typing.Protocol types have O(n) time complexity, where n is the number of methods/attributes defined in the protocol.
This is significantly slower than checking against regular classes (which is O(1)).
Impact on Common Patterns:
import optype as op
# This is efficient - checks only the protocol once
if isinstance(obj, op.CanAdd):
result = obj + 1
# This is less efficient - checks the protocol repeatedly
for item in items:
if isinstance(item, op.CanAdd): # O(n) check each iteration
process(item + 1)
Recommendations:
- Cache protocol checks: Store the result of
isinstance()for reuse - Check early: Validate types at function entry rather than in loops