I'm trying to understand the value-added of using fastai
's fastcore.basics.patch_to
decorator. Here's the fastcore
way:
from fastcore.basics import patch_to
class _T3(int):
pass
@patch_to(_T3)
def func1(self, a):
return self + a
And here's the simple monkey-patching approach:
class simple_T3(int):
pass
def func1(self, a):
return self + a
simple_T3.func1 = func1
Inspecting the two classes does not reveal any differences. I understand that simple monkey-patching might cause problems in more complex cases, so it would be great to know what such cases are? In other words, what's the value-added of fastcore.basics.patch_to
?
More informative debugging messages, better IDE support.
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
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
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 copy import copy
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!"