Skip to content

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 float and complex type promotions
  • Annotate object() sentinels with Just[object]
  • Reject bool in functions that accept int

See: Just Types Documentation

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:

  • CanAbs implements __abs__
  • CanAdd implements __add__
  • CanGetitem implements __getitem__

See: Core Types sections for different operation categories

3. Has* Protocols

optype.Has* is the analogue of Can*, but for special attributes:

  • HasName has a __name__ attribute
  • HasDict has a __dict__ attribute
  • HasDoc has a __doc__ attribute

See: Attributes Documentation

4. Does* Types

optype.Does* types describe the type of operators.

  • DoesAbs is the type of the abs() builtin function
  • DoesPos is the type of the + unary prefix operator
  • DoesAdd is 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: DoesAbs is a typed alias of abs()
  • do_pos: DoesPos is a typed version of operator.pos

The optype.do_* operators are:

  • More complete than operator module
  • 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:

Standard Library Modules

These modules provide protocols for Python's standard library:

NumPy Support

NumPy-specific typing utilities (requires NumPy):

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:

  1. Cache protocol checks: Store the result of isinstance() for reuse
  2. Check early: Validate types at function entry rather than in loops