0

While learning about decorator and decorator factories, I checked the source code of the @functools.lru_cache since it allows both usages using one single implementation. And I spotted something that is intriguing me. In the following statement wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed} extracted from this piece of code from the CPython implementation:

def lru_cache(maxsize=128, typed=False):
    """<docstring ommited for brevity>"""
    if isinstance(maxsize, int):
        # Negative maxsize is treated as 0
        if maxsize < 0:
            maxsize = 0
    elif callable(maxsize) and isinstance(typed, bool):
        # The user_function was passed in directly via the maxsize argument
        user_function, maxsize = maxsize, 128
        wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
        wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
        return update_wrapper(wrapper, user_function)
    elif maxsize is not None:
        raise TypeError(
            'Expected first argument to be an integer, a callable, or None')
    
    def decorating_function(user_function):
        wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
        wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
        return update_wrapper(wrapper, user_function)
    
    return decorating_function

What's the purpose of creating a lambda that only returns a dictionary instead of setting the dictionary directly to the attribute?

1 Answer 1

3

After thinking a bit on the subject I came to this conclusion. But if there's another reason or you have a more complete explanation, please post an answer so I can accept it :)

Dictionaries are mutable objects in Python. This means that anyone that has a reference to a dictionary can change it. So I think that the usage of the lambda here is a trick to make this a read-only attribute. Take a look at the following example:

def f():
    pass

f.a = lambda: {"a": 1}

ref = f.a()
ref

Outputs:

{'a': 1}

And if you change the returned dictionary, it will have no impact on the following calls to the lambda because a new dictionary is created in every call:

ref["b"] = 2
f.a()

Outputs:

{'a': 1}

As you can see in the example, the returned dictionary is obviously still mutable, but changing it has no effect in the returned value by the lambda itself.

In the commit that added this line, I also found the following note that supports my theory:

The wrapped function is instrumented with a :func:cache_parameters function that returns a new :class:dict showing the values for maxsize and typed. This is for information purposes only. Mutating the values has no effect.

Sign up to request clarification or add additional context in comments.

2 Comments

@RichardNeumann that's not what Hemerson said though.
@RichardNeumann I think you're mixing things. You are able to replace wrapper.cache_parameters with anything else because wrapper holds a mutable object. I never said that the dictionary becomes immutable. I said that the attribute provides access to read those values but it's impossible (I think) to change the specific dictionary that this specific lambda returns. I have added an example to try to clarify what I said.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.