2

I have this:

class MySession:

    def __init__(self, session):
        session['my-data'] = {}  # my data is here
        self._session = session

    def __getattr__(self, name):
        return self._session['my-data'][name]

    def __setattr__(self, name, value):
        my_data = self._session['my-data']
        my_data[name] = value
        self._session['my-data'] = my_data


obj = MySession({})
obj.x = 3

Basically I want to encapsulate access to the session (sub-)dictionary with an object attribute access. But I can not do it, since this causes infinite recursion, I guess because doing this:

self._session = session

calls setattr, which in turn calls getattr, which in turn calls getattr, etc

How can I pre-initialize some (normal) attributes in a class implementing getattr / setattr?

blueFast
  • 41,341
  • 63
  • 198
  • 344

2 Answers2

2

The __getattr__ method is only called for attributes that don't exist in the normal attribute dictionary. __setattr__ however is called unconditionally (it's mirror is really __getattribute__ rather than __getattr__). If you can get your _session attribute set up properly in __init__, you won't need to worry about anything in the other methods.

To add an attribute without running into any recursion, use super(MySession, self).__setattr__ to call the version of the method you inherited from object (you should always inherit from object in Python 2, to make your class a new-style class, in Python 3, it's the default). You could also call object.__setattr__ directly, but using super is better if you ever end up using multiple inheritance.

class MySession(object):
    def __init__(self, session):
        session['my-data'] = {}
        super(MySession, self).__setattr__("_session", session) # avoid our __setattr__

    def __getattr__(self, name):
        return self._session['my-data'][name] # this doesn't recurse if _session exists

    def __setattr__(self, name, value):
        my_data = self._session['my-data']
        my_data[name] = value
        self._session['my-data'] = my_data
Blckknght
  • 100,903
  • 11
  • 120
  • 169
  • Great, thanks. Regarding my implementation of setattr: I know it is awkward, but that is related to how the session works in django: setting a subkey does not update the session. It is a common django gotcha. https://docs.djangoproject.com/en/1.8/topics/http/sessions/ – blueFast Dec 01 '15 at 17:10
  • `TypeError: super() takes at least 1 argument (0 given)`. I am using python 2.7 – blueFast Dec 01 '15 at 17:26
  • Ah, I was assuming you were in Python 3 due to the lack of `object` as a base for your class. In Python 2, you definitely want that (or `super` won't ever work no matter how you call it). You'll also need to provide arguments to the `super` call, since Python 2's version can't deduce the class automatically like Python 3 can. I've updated the answer to work on Python 2, and put the `__setattr__` implementation back to the way you say it needs to be. – Blckknght Dec 01 '15 at 21:34
1

You could initialize first and change the setters getters later on:

def __init__(self, session):
    session['my-data'] = {}
    self._session = session
    self.__setattr__ = self._setattr
    self.__getattr__ = self._getattr

assuming self._setattr and self._getattr are implemented of course :)

eugecm
  • 1,189
  • 8
  • 14