Using nbdev for a library with a single class

There’s a ton of useful stuff in fastcore, so I’d consider using it! But if you really just want to use patching it’s actually a very nice and slim implementation with only two built-in dependencies:

import functools
from types import FunctionType

def copy_func(f):
    "Copy a non-builtin function (NB `copy.copy` does not work for this)"
    if not isinstance(f,FunctionType): return copy(f)
    fn = FunctionType(f.__code__, f.__globals__, f.__name__, f.__defaults__, f.__closure__)
    fn.__dict__.update(f.__dict__)
    return fn

def patch_to(cls, as_prop=False):
    "Decorator: add `f` to `cls`"
    if not isinstance(cls, (tuple,list)): cls=(cls,)
    def _inner(f):
        for c_ in cls:
            nf = copy_func(f)
            # `functools.update_wrapper` when passing patched function to `Pipeline`, so we do it manually
            for o in functools.WRAPPER_ASSIGNMENTS: setattr(nf, o, getattr(f,o))
            nf.__qualname__ = f"{c_.__name__}.{f.__name__}"
            setattr(c_, f.__name__, property(nf) if as_prop else nf)
        return f
    return _inner

def patch(f):
    "Decorator: add `f` to the first parameter's class (based on f's type annotations)"
    cls = next(iter(f.__annotations__.values()))
    return patch_to(cls)(f)

That’s the only code needed:

class MyClass():
    def __init__(self):
        pass
    
@patch
def new_fun(self:MyClass):
    print("I'm a patched function!")
    
MyInstance = MyClass()
MyInstance.new_fun()
"I'm a patched function!"

EDIT: Oh I forgot to mention that fastcore itself is extremely low weight: The only external library used is numpy (and dataclasses if your python is < 3.7).

9 Likes