3

Here is an example of something I want to do in code:

class MyClass(object):

    @classmethod
    def test_func(cls, x):

        try:
            return cls.cache[x]

        except AttributeError:
            cls.cache = {}

        except (AttributeError, KeyError):
            cls.cache[x] = "CACHE STORE"
            return cls.cache[x]

The idea here being my class will cache some results based on an input x. However I don't want to start creating the cache until it's needed. So the first time I pass any x into this, I want it to create the cache, and fill it with something. Is there a way to make it hit both except blocks? Right now it currently only hits the first

sedavidw
  • 9,023
  • 12
  • 44
  • 76
  • Sounds like you want a [singleton](http://stackoverflow.com/questions/6760685/creating-a-singleton-in-python)... – AlG May 14 '15 at 19:36
  • Creating an empty dict is going to add negligible overhead to the creation of your class. Just put `cache = {}` as a class attribute and be done with it. – chepner May 14 '15 at 19:49
  • I think you can adapt an answer I wrote earlier: http://stackoverflow.com/a/30235268/3001761 – jonrsharpe May 14 '15 at 19:53

3 Answers3

3

As I suggested in a comment, just make cache a class attribute when you create the class. Unless you have a really good reason not to, you're just needlessly complicating your implementation

class MyClass(object):

    cache = {}

    @classmethod
    def test_func(cls, x):
        try:
            return cls.cache[x]
        except KeyError:
            cls.cache[x] = "CACHE STORE"
            return cls.cache[x]

Once you do that, you don't even need try statement; you can just use setdefault.

@classmethod
def test_func(cls, x):
    return cls.cache.setdefault(x, "CACHE STORE")
chepner
  • 389,128
  • 51
  • 403
  • 529
2

I would go for recursion here:

class MyClass(object):

    @classmethod
    def test_func(cls, x):

        try:
            return cls.cache[x]

        except AttributeError:
            cls.cache = {}
            return cls.test_func(x)  # Add this line

        except KeyError:
            cls.cache[x] = "CACHE STORE"
            return cls.cache[x]
Kevin
  • 25,251
  • 7
  • 54
  • 78
1

Use a defaultdict:

from collections import defaultdict as dd

class MyClass(object):
    cache = dd(lambda: "CACHE STORE")
    @classmethod
    def test_func(cls, x):
        return cls.cache[x]

From the docs:

Returns a new dictionary-like object. defaultdict is a subclass of the built-in dict class. It overrides one method and adds one writable instance variable. The remaining functionality is the same as for the dict class and is not documented here.

The first argument provides the initial value for the default_factory attribute; it defaults to None. All remaining arguments are treated the same as if they were passed to the dict constructor, including keyword arguments.

I agree with the others that you lose nothing by just adding it as a class attribute from the beginning. But if you really insist on not creating the cache until it's needed, you can do it like this:

from collections import defaultdict as dd

class MyClass(object):
    @classmethod
    def test_func(cls, x):
        try:
            return cls.cache[x]
        except AttributeError:
            cls.cache = dd(lambda: "CACHE STORE")
            return cls.test_func(x)
Community
  • 1
  • 1
Rick supports Monica
  • 33,838
  • 9
  • 54
  • 100