2

For Caching/Persistence reasons I'm looking to get unique hashable address for an arbitrarily large tree of subclasses of a base class (which is never created itself). Here's what things look like at the moment:

OBJECT_CACHE = dict()

class Base(object):
    def __new__(cls, *args, **kwargs):
        # calculate class_addr here?
        obj = OBJECT_CACHE.get(class_addr)
        if obj is None:
            obj = super(Base, cls).__new__(cls, *args, **kwargs)
            OBJECT_CACHE[class_addr] = obj
        return obj

But I'm not sure what the best way to go about getting such an id would be. My concept is that it would look something like the following:

Base:          # no id
    F          # id = 'f'
    A:         # id = 'a'
        E      # id = 'a.e'
        B:     # id = 'a.b'
            C  # id = 'a.b.c'
            D  # id = 'a.b.d'

I thought about trying something with properties and super() but ideally the subclasses would only contain a single line like my_id = 'e'. Thanks in advance!

Violet
  • 507
  • 4
  • 15
  • Why not just use the class object itself? –  Mar 17 '13 at 17:26
  • @delnan If I understand what you mean, largely because the default hash values are linked to object memory addresses - which would change if the script is stopped and started again. (The `OBJECT_CACHE` could be pickled and saved) – Violet Mar 17 '13 at 19:47
  • No, it stores the class name and module name and retrieves whatever is stored in the module of that name with that identifier. Try it out: `class C(object): pass` followed by `import pickle; s = pickle.dumps({C: 1}); C = 'foo'; print pickle.loads(s)` –  Mar 17 '13 at 20:13
  • ah, good point. I did want to be able to reference the objects in the notation that I noted though (and I'm not ending up using the class.__name__, but rather a different attribute) – Violet Mar 17 '13 at 22:50

1 Answers1

2

What you are looking for is the cls.mro():

OBJECT_CACHE = dict()
class Base(object):
    def __new__(cls, *args, **kwargs):
        class_addr = cls.name()
        obj = OBJECT_CACHE.get(class_addr)
        if obj is None:
            obj = super(Base, cls).__new__(cls, *args, **kwargs)
            OBJECT_CACHE[class_addr] = obj
        return obj
    @classmethod
    def name(cls):
        names = [k.__name__.lower() for k in cls.mro() 
                if k != Base and issubclass(k, Base)]
        return '.'.join(names[::-1])


class F(Base): pass
class A(Base): pass
class E(A): pass
class B(A): pass
class C(B): pass
f = F()
a = A()
e = E()
b = B()
c = C()
print(OBJECT_CACHE.keys())

yields

['a', 'a.e', 'a.b.c', 'a.b', 'f']
Community
  • 1
  • 1
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • `mro()` does seem like the way to go, but what if one of the subs inherits from another class that doesn't inherit from Base? e.g. `class A(Base, str)` Can I check if the classes have a shared base? – Violet Mar 17 '13 at 17:28
  • The mro includes all bases, and each base will be included only once. So I think it will work just fine. If you add `str` as a base, then `basestring` is also part of the mro. I don't know if you want to include that or not. If not, just add `basestring` to the tuple of classes to be excluded: `if k not in (Base, object, basestring)`. – unutbu Mar 17 '13 at 17:29
  • I think we can negate this by checking `issubclass()` for each loop. I'll edit your answer for clarity then accept it. – Violet Mar 17 '13 at 17:35