# Caching redefined functions

**URL:** <https://numba.discourse.group/t/caching-redefined-functions/3057>\
**Category:** Community Support\
**Created:** [September 11, 2025, 10:55am UTC](https://numba.discourse.group/t/caching-redefined-functions/3057 "2025-09-11T10:55:50Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![ricardoV94](https://yyz2.discourse-cdn.com/free1/user_avatar/numba.discourse.group/ricardov94/32/1215_2.png) [@ricardoV94](https://numba.discourse.group/u/ricardoV94)\
**Post date:** [September 11, 2025, 10:55am UTC](https://numba.discourse.group/t/caching-redefined-functions/3057/1 "2025-09-11T10:55:50Z")

</div>

In the following example, the cache fails for the first function, but not the second:

```python
from numba import njit

@njit(cache=True)
def incr_basic(x):
    return x + 1

def create_foo():
    @njit(cache=True)
    def incr_basic(x):
        return x + 1

    @njit(cache=True)
    def foo(x):
        return incr_basic(x)

    return foo

def create_foo_with_global():
    @njit(cache=True)
    def foo(x):
        return incr_basic(x)

    return foo

assert create_foo()(3) == create_foo_with_global()(3)
%timeit create_foo()(3) # 32.8 ms ± 2.69 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit create_foo_with_global()(3) # 4.27 ms ± 353 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)

```

This is an issue for us at pytensor as we compile functions on demand, using a pattern that looks like `create_foo`. AFAICT this happens because the cache key hashes the closure of `foo` and that is always new for a newly defined function. The relevant line is in: [numba/numba/core/caching.py at 5e0ceb077fb219a21ac8a50e5234ca643aaf96f9 · numba/numba · GitHub](https://github.com/numba/numba/blob/5e0ceb077fb219a21ac8a50e5234ca643aaf96f9/numba/core/caching.py#L773-L776)

Is there a way for us to easily override how the key is defined? We are already using a custom CacheLocator for handling string-generated/compiled functions, but numba will still refuse to use the cached files because of the way the key is defined.

PS: The timings were done in ipython for ease of the `%timeit` magic, but the issue persists (and is perhaps only relevant) in a regular python script.

---

<div class="post-metadata">

**Author:** ![milton](https://avatars.discourse-cdn.com/v4/letter/m/87869e/32.png) [@milton](https://numba.discourse.group/u/milton)\
**Post date:** [September 12, 2025, 11:18am UTC](https://numba.discourse.group/t/caching-redefined-functions/3057/2 "2025-09-12T11:18:45Z")

</div>

Have you considered using [compile](https://docs.python.org/3/library/functions.html#compile) + [exec](https://docs.python.org/3/library/functions.html#exec) sequence to compile the function’s code text in the module’s file and then executing it in the module’s namespace?

---

<div class="post-metadata">

**Author:** ![ricardoV94](https://yyz2.discourse-cdn.com/free1/user_avatar/numba.discourse.group/ricardov94/32/1215_2.png) [@ricardoV94](https://numba.discourse.group/u/ricardoV94)\
**Post date:** [September 14, 2025, 8:58am UTC](https://numba.discourse.group/t/caching-redefined-functions/3057/3 "2025-09-14T08:58:09Z")

</div>

Not sure that makes sense for us? We’re compiling custom functions for users on demand, not a finite set of predefined functions. We already do compile + exec for the user defined functions, the issue is not that, but the inner functions in the closure not hashing to the same when we redefine them.

---

<div class="post-metadata">

**Author:** ![milton](https://avatars.discourse-cdn.com/v4/letter/m/87869e/32.png) [@milton](https://numba.discourse.group/u/milton)\
**Post date:** [September 16, 2025, 12:04pm UTC](https://numba.discourse.group/t/caching-redefined-functions/3057/4 "2025-09-16T12:04:27Z")

</div>

The key to utilizing ‘compile + exec’ for the purposes such as the one you described is to compile and execute the code in the module’s file and the global namespace, which is what addresses the issue of the closure you have encountered. Try this (a somewhat laborious example just to illustrate the point, feel free to shape it into something more streamlined for your needs),

```auto
from inspect import getfile, getmodule
from numba import njit # noqa: F401

def _anchor():
    pass

def create_foo_exec():
    incr_basic_txt = """
@njit(cache=True)
def incr_basic_(x):
    return x + 1  
"""
    inc_basic_code = compile(incr_basic_txt, getfile(_anchor), mode="exec")
    ns = getmodule(_anchor). __dict__
    exec(inc_basic_code, ns)

    foo_txt = """
@njit(cache=True)
def foo_(x):
    return incr_basic_(x)
"""
    foo_code = compile(foo_txt, getfile(_anchor), mode="exec")
    exec(foo_code)

    return locals()["foo_"]

if __name__ == " __main__":
    print(create_foo_exec()(3))

```

---

<div class="post-metadata">

**Author:** ![ricardoV94](https://yyz2.discourse-cdn.com/free1/user_avatar/numba.discourse.group/ricardov94/32/1215_2.png) [@ricardoV94](https://numba.discourse.group/u/ricardoV94)\
**Post date:** [September 23, 2025, 9:26am UTC](https://numba.discourse.group/t/caching-redefined-functions/3057/5 "2025-09-23T09:26:41Z")

</div>

Thanks a lot, I’ll give it some thought

---

<div class="post-metadata">

**Author:** ![ricardoV94](https://yyz2.discourse-cdn.com/free1/user_avatar/numba.discourse.group/ricardov94/32/1215_2.png) [@ricardoV94](https://numba.discourse.group/u/ricardoV94)\
**Post date:** [September 30, 2025, 12:18pm UTC](https://numba.discourse.group/t/caching-redefined-functions/3057/6 "2025-09-30T12:18:20Z")

</div>

Another option was to use `register_jitable` instead of `njit` for the inner function. That way the original function is used for the cache key which serializes the same way across multiple definitions.

---

<div class="post-metadata">

**Author:** ![milton](https://avatars.discourse-cdn.com/v4/letter/m/87869e/32.png) [@milton](https://numba.discourse.group/u/milton)\
**Post date:** [October 2, 2025, 11:12am UTC](https://numba.discourse.group/t/caching-redefined-functions/3057/7 "2025-10-02T11:12:59Z")

</div>

Interesting observation - right, the original python function ‘incr\_basic’ is given back by the ‘register\_jitable’ (which does the ‘overload’ for the jitted context behind the scenes), and having python functions in the closure doesn’t invalidate the cache (it would have been quite inconvenient indeed if it did!). Thanks for sharing!

P.S. I have noticed ‘register\_jitable‘ to be less flexible in some cases, for instance, one cannot directly compile the function with it for the desired signature.
