from s3generics import *
import pandas as pd
import numpy as np
df = pd.DataFrame({"x": [3, 1, 4, 1, 5], "y": list("abcde")})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:
- Exact type match in the registry
- MRO walk — first base class found wins
- 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] # everythingQuick start
Using built-in methods
summary(df) # describe() output
head(df, 3) # first 3 rows| x | y | |
|---|---|---|
| 0 | 3 | a |
| 1 | 1 | b |
| 2 | 4 | c |
nrow(df) # 55
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))
aarray([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
dim(a) # (4, 3)
t(a) # transposearray([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
solve(np.eye(2) * 2) # matrix inversearray([[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.