def mu_cache(func,maxsize=128):
    "Most Used Cache decorator"
    cname = "__"+func.__name__+"_Cache"
    rname = "__"+func.__name__+"_Rankings"
    exec("global "+cname)
    exec(cname+" = {}") #here
    exec("global "+rname)
    exec(rname+" = {}")
    def wrap(*args,**kwargs):
            cache = eval(cname)
            rankings = eval(rname)
            try:
                    res = cache[(args,kwargs)]
            except:
                    res = func(*args,**kwargs)
                    cache[(args,kwargs)] = res
            else:
                    try:
                            res = rname[(args,kwargs)]
                    except:
                            res=0
                    rname[(args,kwargs)] = res+1
            def delete_smallest():
                global rankings
                smallest=None
                todelete=None
                for args in rankings:
                    r = rankings[args]
                    if (smallest == None) or r<=smallest:
                        smallest=r
                        todelete=args
                del cache[todelete]
            while len(cache)> maxsize:
                delete_smallest()
    return wrap
SyntaxError: unqualified exec is not allowed in function 'mu_cache' it contains a nested function with free variables (<pyshell#10>, line 3)

I have no idea what this means; and I am trying to make a copy of functools.lru_cache but with Most Used instead of Least Recently Used.

If a function is nested in another function, you're not allowed to use unqualified exec in either of the two functions. Unqualified in this case means "without in". An explanation of why this limitation exists can be found here.

In your code you don't really need exec. To set and get global variables by name, you can just use globals()[name], but you don't even need global variables either. You can just define cache and rankings as local variables in mu_cache and then access them in wrap.

Another error in your code is that you use global rankings in delete_smallest. This will cause an error message about there being no global variable named rankings because rankings is a local variable in the wrap function, not a global variable. If you just remove that line, the error will disappear and you will be correctly able to access the rankings variable.

Comments
very good remarks

I still need to set a global variable because It carrys over function calls

Use a single global object

class _MuCacheData(object):
    def __init__(self):
        # define here arbitrary containers that you need
        pass

    def mu_cache(self, func, maxsize = 128):
        # write and return your wrapper here
        # Access persistent data through self
        pass


# A single instance is used for the decorator
mu_cache = _MuCacheData().mu_cache

Don't use exec() nor the global statement.

Edited 3 Years Ago by Gribouillis

I still need to set a global variable because It carrys over function calls

No, you don't; local variables that are closed over from an outer function do carry over between function calls. Look at this:

def memoize(f):
    cache = {}

    def wrap(x):
        print("Contents of cache: {}".format(cache))
        if x not in cache:
            print("Calculating {}({})".format(f.__name__, x))
            cache[x] = f(x)
        return cache[x]

    return wrap

def double(x):
    return 2 * x

def square(x):
    return x*x

mdouble = memoize(double)        
msquare = memoize(square)

print("Double:")
mdouble(3)
mdouble(3)

print("\nSquare:")
msquare(3)
msquare(3)

Output:

Double:
Contents of cache: {}
Calculating double(3)
Contents of cache: {3: 6}

Square:
Contents of cache: {}
Calculating square(3)
Contents of cache: {3: 9}

As you see both msquare and mdouble have their own cache and those caches persist between calls to msquare and mdouble respectively. So this acts exactly like you want without any globals.

Edited 3 Years Ago by sepp2k

This question has already been answered. Start a new discussion instead.