TL;DR
More informative debugging messages, better IDE support.
Answer
patch
and patch_to
are decorators in the fastcore basics
module that are helpful to make the monkey_patched method to look more like as if it was a method originally placed inside the Class, the classical way (pun intended).
If you create a function outside a class and then monkey-patch it, the outsider method typically has different attributes, such as its name, module, and documentation, compared to the original function. This can be confusing and unhelpful when debugging or working with the "outsider" function.
Source: Official documentation: https://github.com/fastai/fastcore/blob/master/nbs/01_basics.ipynb
Usage suggestion
Consider using patch
instead of patch_to
, because this way you can add type annotations.
from fastcore.basics import patch
class _T3(int):
pass
@patch
def func1(self: _T3, a):
return self + a
What if I don't want to use the library?
Credits: Kai Lichtenberg
fastcore itself is extremely low weight: The only external library used is numpy (and dataclasses if your python is < 3.7).
But if you really want to not use it, here's an 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)
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!"