`@patch` removes the function it precedes from the global namespace by overriding its reference

When I run this code

from fastcore.basics import patch, patch_to
from dataclasses import dataclass
@dataclass
class TestOne:
    a:int
#----------------------
def f1_test(x):
    print(x)
#----------------------
f1_test("before patch")  # returns before patch
#----------------------
@patch
def f1_test(self: TestOne,  x  ):

    x="88"
    f1_test(x)

    x = "99"
    f1_test(x)
#----------------------
f1_test("after patch")  # fails with TypeError: 'NoneType' object is not callable

#----------------------
t1=TestOne(123)
#----------------------
t1.f1_test("abc")

I get

before patch
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[657], line 24
     22     f1_test(x)
     23 #----------------------
---> 24 f1_test("after patch")
     25 #----------------------
     26 t1=TestOne(123)

TypeError: 'NoneType' object is not callable

The same happens if I use

@patch
def f1_test(self:TestOne, x):

or

@patch_to(TestOne)
def f1_test(self, x):

Can someone explain why the original function gets deleted? (is the explanation below true?)

The original function f1_test gets deleted because the @patch decorator from fastcore dynamically injects the function into the class (TestOne) as a method and returns None. This overrides the global f1_test reference, effectively replacing it with None, which makes the original function no longer callable.

Why the Original Function Gets Deleted

The @patch decorator dynamically injects f1_test into the TestOne class as a method. During this process, @patch overrides the global f1_test name by reassigning it internally. Even though it may not explicitly return None, it effectively removes the original standalone function from the global namespace by binding f1_test exclusively to the class. Thus, the global f1_test becomes unusable.

The explanation you’ve provided is largely correct. When you use the @patch decorator from fastcore, it modifies the function in such a way that it becomes a method of the class TestOne. Here’s a detailed breakdown of what’s happening: wellnow com

  1. Decorator Usage: The @patch decorator is intended to add methods to classes. When you use @patch on f1_test, it converts f1_test into a method of TestOne.
  2. Global Function Replacement: During this process, the global reference to f1_test is replaced by the modified function intended for the class. As a result, the original standalone function f1_test is effectively removed from the global namespace.
  3. Resulting in NoneType Error: Since the global f1_test reference now points to None (due to the internal workings of the @patch decorator), attempting to call f1_test("after patch") results in a TypeError: 'NoneType' object is not callable.

Here’s how you might correct your approach to avoid this issue:

  1. Rename the Method: To avoid confusion, you can rename the patched method so it doesn’t conflict with the global function name.
  2. Explicit Method Call: Ensure you are calling the correct method associated with the class instance.

Here’s an example:

from fastcore.basics import patch
from dataclasses import dataclass

@dataclass
class TestOne:
    a: int

# Original standalone function
def f1_test(x):
    print(x)

# Test the standalone function
f1_test("before patch")  # prints "before patch"

# Patch method with a different name to avoid conflict
@patch
def patched_f1_test(self: TestOne, x):
    x = "88"
    f1_test(x)

    x = "99"
    f1_test(x)

# Test the standalone function again
f1_test("after patch")  # prints "after patch"

# Create an instance of TestOne and call the patched method
t1 = TestOne(123)
t1.patched_f1_test("abc")

This way, you maintain the original standalone function f1_test while also adding a method patched_f1_test to the TestOne class without causing name conflicts.

1 Like

Hello!
I also faced same issue but now it solved for me. Thank you for the help.

Best Regards,
michelle

Using this I avoid overwriting while keeping the original name. (There are other ways but this is just very simple).

#|export
#------------
def  f_original(...):
    ....
#------------
@patch_to(MyClass)
def _f_original(...):
   ....
#------------
#|export

# Simple alias!
MyClass.f_original = MyClass._f_original   

Better ideas are welcome.