I have an experimental library that I really want to finalise and publish using nbdev. However, the library is currently a single .py file with one main class, and most of the library functionalities are implemented as methods of this class.
Is there any natural way to do something like this in nbdev? AFAIK, the class needs to be defined within a single cell, which means I can’t really “interleave” the text, explanations, examples etc, nbdev-style between methods. Is there any way to use nbdev for a project that is mostly built around a single class?
Thanks for your answers! And sorry for the long delay, this is a side project and it got neglected for a while…
@patch seems to be indeed very helpful. Does this mean I need to add fastcore as a dependency to my package, and export from fastcore.foundation import patch to foo.py?
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).
I had the same problem (long classes in one cell) and found this forum post, but I don’t see the patch function in fastcore anymore. Has it moved, or been renamed?
[EDIT] I went through the fastcore history on GitHub and found it has recently been moved to fastcore.basics. I’m all set then. FYI, the search function at fastcore.fast.ai can’t find it in a search for “patch”: