s3generics

R-style generic function dispatch for Python.

s3generics brings R’s S3 generic dispatch model to Python: a set of top-level functions (summary, head, predict, fit, …) that dispatch to the right implementation based on the type of the first argument.

from s3generics import summary, head, predict

summary(my_dataframe)   # dispatches to the DataFrame method
summary(my_model)       # dispatches to your custom model method
head(arr, 10)           # works on lists, numpy arrays, pandas objects, …

Why

R Python (s3generics)
summary(x) dispatches on class(x) summary(x) dispatches on type(x)
predict(model, newdata) predict(model, newdata)
head(df, 6) head(df, 6)
UseMethod("foo") @generic decorator
foo.myclass <- function(x, …) @foo.register(MyClass)

The dispatch order mirrors R’s S3 and Python’s functools.singledispatch:

  1. Exact type match in the registry
  2. MRO walk — first base class found wins
  3. Default — the function body of the @generic-decorated stub

Installation

pip install s3generics              # core + built-in type methods
pip install s3generics[numpy]       # + numpy ndarray methods
pip install s3generics[pandas]      # + pandas DataFrame/Series methods
pip install s3generics[all]         # everything

Quick start

Using built-in methods

from s3generics import *
import pandas as pd
import numpy as np

df = pd.DataFrame({"x": [3, 1, 4, 1, 5], "y": list("abcde")})
summary(df)             # describe() output
head(df, 3)             # first 3 rows
x y
0 3 a
1 1 b
2 4 c
nrow(df)                # 5
5
names(df)               # ['x', 'y']
['x', 'y']
sort(df, by="x")        # sort by column
x y
1 1 b
3 1 d
0 3 a
2 4 c
4 5 e
subset(df, df.x > 2)    # filter rows
x y
0 3 a
2 4 c
4 5 e
transform(df, z=df.x*2) # add column
x y z
0 3 a 6
1 1 b 2
2 4 c 8
3 1 d 2
4 5 e 10
a = np.zeros((4, 3))
a
array([[0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.],
       [0., 0., 0.]])
dim(a)                  # (4, 3)
t(a)                    # transpose
array([[0., 0., 0., 0.],
       [0., 0., 0., 0.],
       [0., 0., 0., 0.]])
solve(np.eye(2) * 2)    # matrix inverse
array([[0.5, 0. ],
       [0. , 0.5]])

Registering methods for your own classes

Create olsmodel.py

from s3generics import predict, coef, summary, fit

class OLSModel:
    def __init__(self):
        self.coef_ = None

@fit.register(OLSModel)
def _fit_ols(model, X, y):
    import numpy as np
    model.coef_ = np.linalg.lstsq(X, y, rcond=None)[0]
    return model

@predict.register(OLSModel)
def _predict_ols(model, X):
    return X @ model.coef_

@coef.register(OLSModel)
def _coef_ols(model):
    return model.coef_

@summary.register(OLSModel)
def _summary_ols(model):
    return f"OLS coefficients: {model.coef_}"

Usage:

from olsmodel import *
import numpy as np
X = np.column_stack([np.ones(5), np.arange(5)])
y = np.array([1.0, 2.1, 3.0, 4.2, 5.0])

m = fit(OLSModel(), X, y)
predict(m, X)
array([1.04, 2.05, 3.06, 4.07, 5.08])
summary(m)
'OLS coefficients: [1.04 1.01]'

Creating a brand-new generic

from s3generics.core import generic

@generic
def knots(obj, *args, **kwargs):
    """Return the knots of a spline object."""
    raise NotImplementedError(f"No knots() method for {type(obj).__name__}.")

# Later, in your spline module:
@knots.register(CubicSpline)
def _knots_cubic(obj):
    return obj.x

# Later when you want to use knots on you object
model = CubicSpline(x = ..., y = ...)
n = knots(model)

Getting the list of methods for a generic (e.g. the predict generic)

from s3generics import predict, summary
predict.methods()
{'OLSModel': <function __main__._predict_ols(model, X)>}
repr(predict)
"<S3Generic 'predict' [OLSModel]>"

Available generics

Category Functions
Display print_, summary, str_, format_
Subsetting head, tail, subset
Modelling fit, predict, residuals, coef, fitted, logLik, AIC, BIC
Transformation transform, convert, scale
Linear algebra t, solve, crossprod
Plotting plot, hist, boxplot
Shape / metadata dim, nrow, ncol, length, names
Sorting sort, order, rank
I/O read, write
Aggregation aggregate, apply_
Binding / merging rbind, cbind, merge

Design

s3generics/
├── core.py          # S3Generic class + @generic decorator
├── generics.py      # Canonical generic stubs (no implementations)
├── methods/
│   ├── builtins.py  # list, tuple, dict, str, int, float
├── numpy_/
│   ├── numpy_.py    # numpy.ndarray   (imported if numpy present)
├── pandas/
│   └── pandas_.py   # pd.DataFrame / pd.Series (imported if pandas present)
└── __init__.py      # Re-exports everything; lazy optional imports

S3Generic class — stores a per-type registry and walks the MRO on each call. Fully introspectable: summary.methods() returns all registered types.

@generic decorator — wraps a plain function as a S3Generic, using its body as the default implementation.

Extending from third-party code — just import the S3Generic object and call .register(). No monkey-patching or metaclass magic required.