Search code examples
pythonpython-3.xpython-2.7functools

Any changes in 'functools.lru_cache' from python 2 and python 3?


My code is here works fine in python2.7 but fails inpython 3 functools.lru_cache(maxsize=32) any change in from python 2 to python 3.

The error I am getting is for my configparser object while caching in functools.lru_cacheit says

TypeError: unhashable type: 'ConfigParser'

Want to understand the changes in 'functools.lru_cache' from python 2 and python 3?

#CONFI FILE
[translate]
api_url = https://url
api_version = version_num
api_key = key_value


#code goes here
import functools
from configparser import ConfigParser as SafeConfigParser
config = SafeConfigParser()
path ="./conf/services.ini"
config.read(path)


@functools.lru_cache(maxsize=32)
def build_api_params_key(config):
    """Build the api url and return with key."""
    api_url = config.get('translate', 'api_url')
    api_version = config.get('translate', 'api_version')
    api_key = config.get('translate', 'api_key')
    full_api_url = api_url + api_version

    return api_key

Solution

  • The issue here is not functools.lru_cache, it is actually the ConfigParser. ConfigParser inherits from RawConfigParser, which in Python 3x, inherits from collections.abc.MutableMapping. The MutableMapping abstract class is not hashable, as it is mutable and does not implement the __hash__ magic method.

    Since the ConfigParser instance is not hashable, it cannot be used as a key to the cache dictionary within the functools.lru_cache decorator.

    For further reference, see this section of the configparser docs.

    Assuming that it is necessary to cache the contents of the config file, another option would be to read the the contents of the file and then pass the contents string to the cached function, like so

    import functools
    from configparser import ConfigParser as SafeConfigParser
    path = "./conf/services.ini"
    config_contents = open(path).read()
    
    @functools.lru_cache(maxsize=32)
    def build_api_params_key(config_contents: str):
        """Build the api url and return with key."""
        config = SafeConfigParser()
        config.read_string(config_contents)
        api_url = config.get('translate', 'api_url')
        api_version = config.get('translate', 'api_version')
        api_key = config.get('translate', 'api_key')
        full_api_url = api_url + api_version
    
        return api_key
    

    In the solution above, the config file is read to get string containing its contents. Since strings are hashable, this can be passed to the cached function. You can also do something similar with the file pointer if you would rather read the contents of the file within the function. However, these solutions are not compatible to Python 2.7 as read_string is not defined.