Repository: numba/numba

Issue:
Bugs in NUMBA_DISABLE_JIT
If you export NUMBA_DISABLE_JIT=1, all functions wrapped by @jit 
- can't be pickled anymore
```
_pickle.PicklingError: Can't pickle <function test1 at 0x7ff0d97491e0>: it's not the same object as mymodule.test1
```
- don't have a \_\_name\_\_ attribute
```
AttributeError: '_DisableJitWrapper' object has no attribute '__name__'
```
Bugs in NUMBA_DISABLE_JIT
If you export NUMBA_DISABLE_JIT=1, all functions wrapped by @jit 
- can't be pickled anymore
```
_pickle.PicklingError: Can't pickle <function test1 at 0x7ff0d97491e0>: it's not the same object as mymodule.test1
```
- don't have a \_\_name\_\_ attribute
```
AttributeError: '_DisableJitWrapper' object has no attribute '__name__'
```


Discussion:
Thanks for the report.
1.  I can reproduce, probably needs some custom serialisation.
2. The returned item from a `@jit` decorated function when jit is disabled is an instance of the`_DisableJitWrapper` class, this is a callable object that when called returns the result of running the decorated function with the supplied args. It therefore has no name, though I guess it can have one if there's a use case for it?
We had the problem of missing attributes with regular `@jit` functions long ago (`__doc__` was the most recent issue), so I think it makes sense to solve it here the same way.

However, stepping back, what are the tradeoffs to just making `@jit` a no-op when `NUMBA_DISABLE_JIT` is set?  Rather than return a wrapper, we return the original function, unmodified?  I imagine that would cause problems with code that assumes there methods like `inspect_asm()` on the compiled functions, so maybe sticking with the wrapper approach makes sense.
At present an instance of a `_DisableJitWrapper` class is what is returned, when `NUMBA_DISABLE_JIT` is set, in place of the typical dispatch object. This instance is a callable object that just forwards args to a reference to the original python function and returns the result of that. The `_DisableJitWrapper` class inherits from object and so is not part of the dispatcher hierarchy and therefore doesn't have any of the methods like the noted `inspect_asm()`.

I am now also wondering whether just returning the function might be the best thing to do. I looked in the blame log for the addition of the `_DisableJitWrapper` class, and it was for https://github.com/numba/numba/issues/1084. The addition of the class makes it easier to assert that the functionality provided by the `NUMBA_DISABLE_JIT` env var is working correctly, but I'm unclear as to what else it adds.
In that case, I think we can safely make NUMBA_DISABLE_JIT turn all the `@jit` decorators into no-op without breaking any existing code.
  
Thanks for the report.
1.  I can reproduce, probably needs some custom serialisation.
2. The returned item from a `@jit` decorated function when jit is disabled is an instance of the`_DisableJitWrapper` class, this is a callable object that when called returns the result of running the decorated function with the supplied args. It therefore has no name, though I guess it can have one if there's a use case for it?
We had the problem of missing attributes with regular `@jit` functions long ago (`__doc__` was the most recent issue), so I think it makes sense to solve it here the same way.

However, stepping back, what are the tradeoffs to just making `@jit` a no-op when `NUMBA_DISABLE_JIT` is set?  Rather than return a wrapper, we return the original function, unmodified?  I imagine that would cause problems with code that assumes there methods like `inspect_asm()` on the compiled functions, so maybe sticking with the wrapper approach makes sense.
At present an instance of a `_DisableJitWrapper` class is what is returned, when `NUMBA_DISABLE_JIT` is set, in place of the typical dispatch object. This instance is a callable object that just forwards args to a reference to the original python function and returns the result of that. The `_DisableJitWrapper` class inherits from object and so is not part of the dispatcher hierarchy and therefore doesn't have any of the methods like the noted `inspect_asm()`.

I am now also wondering whether just returning the function might be the best thing to do. I looked in the blame log for the addition of the `_DisableJitWrapper` class, and it was for https://github.com/numba/numba/issues/1084. The addition of the class makes it easier to assert that the functionality provided by the `NUMBA_DISABLE_JIT` env var is working correctly, but I'm unclear as to what else it adds.
In that case, I think we can safely make NUMBA_DISABLE_JIT turn all the `@jit` decorators into no-op without breaking any existing code.
  

Resolving patch:
diff --git a/numba/decorators.py b/numba/decorators.py
--- a/numba/decorators.py
+++ b/numba/decorators.py
@@ -27,14 +27,6 @@ def autojit(*args, **kws):
     return jit(*args, **kws)
 
 
-class _DisableJitWrapper(object):
-    def __init__(self, py_func):
-        self.py_func = py_func
-
-    def __call__(self, *args, **kwargs):
-        return self.py_func(*args, **kwargs)
-
-
 _msg_deprecated_signature_arg = ("Deprecated keyword argument `{0}`. "
                                  "Signatures should be passed as the first "
                                  "positional argument.")
@@ -185,7 +177,7 @@ def wrapper(func):
             from . import cuda
             return cuda.jit(func)
         if config.DISABLE_JIT and not target == 'npyufunc':
-            return _DisableJitWrapper(func)
+            return func
         disp = dispatcher(py_func=func, locals=locals,
                           targetoptions=targetoptions,
                           **dispatcher_args)


Test patch:
diff --git a/numba/tests/test_jitmethod.py b/numba/tests/test_jitmethod.py
--- a/numba/tests/test_jitmethod.py
+++ b/numba/tests/test_jitmethod.py
@@ -4,7 +4,6 @@
 
 from numba import config, jit, types
 from numba.compiler import compile_isolated
-from numba.decorators import _DisableJitWrapper
 from numba.tests.support import override_config
 
 
@@ -50,38 +49,23 @@ def method(self):
 class TestDisabledJIT(unittest.TestCase):
     def test_decorated_function(self):
         with override_config('DISABLE_JIT', True):
-            @jit
             def method(x):
-                return x
+                return x          
+            jitted = jit(method)
 
-        self.assertIsInstance(method, _DisableJitWrapper)
-        self.assertIsNotNone(method.py_func)
+        self.assertEqual(jitted, method)
         self.assertEqual(10, method(10))
+        self.assertEqual(10, jitted(10))
 
     def test_decorated_function_with_kwargs(self):
-        with override_config('DISABLE_JIT', True):
-            @jit(nopython=True)
-            def method(x):
-                return x
-
-        self.assertIsInstance(method, _DisableJitWrapper)
-        self.assertIsNotNone(method.py_func)
-        self.assertEqual(10, method(10))
-
-    def test_py_func(self):
-        with override_config('DISABLE_JIT', True):
-            def method(x):
-                return x
-            jitted = jit(method)
-        self.assertEqual(jitted.py_func, method)
-
-    def test_py_func_with_kwargs(self):
         with override_config('DISABLE_JIT', True):
             def method(x):
                 return x
             jitted = jit(nopython=True)(method)
-        self.assertEqual(jitted.py_func, method)
 
+        self.assertEqual(jitted, method)
+        self.assertEqual(10, method(10))
+        self.assertEqual(10, jitted(10))
 
 if __name__ == '__main__':
     unittest.main()