3

I have the following class which is meant to imitate a dict without actually being a subclass:

class DB:

    def __getattribute__(self, name):
        print('__getattribute__("%s")' % name)
        if name.startswith('__') and name not in ['__init__', '__new__',
                '__dict__', '__getattribute__', '__getattr__', '__setattr__',
                '__delattr__', '__doc__', '__module__', '__del__', '__hash__']:
            return eval('self._d.%s' % name)
        print('it is not being diverted to self._d')
        raise AttributeError(name)

    def __getattr__(self, name):
        print('__getattr__("%s")' % name)
        if name.startswith('__'):
            raise AttributeError(name)
        return eval('self._d.%s' % name)

    def __setattr__(self, name, val):
        if name == '_d':
            self.__dict__[name] = val
        else:
            return self._d.__setattr__(name, val)

    def __delattr__(self, name):
        if name == '_d':
            del self.__dict__[name]
        else:
            return self._d.__delattr__(name)

    def __init__(self):
        self._d = {}

This produces the following output when used:

>>> d = DB()
__getattribute__("__dict__")
it is not being diverted to self._d
__getattr__("__dict__")
Traceback (most recent call last):
  File "<pyshell#2>", line 1, in <module>
    d = DB()
  File "<pyshell#1>", line 31, in __init__
    self._d = {}
  File "<pyshell#1>", line 20, in __setattr__
    self.__dict__[name] = val
  File "<pyshell#1>", line 15, in __getattr__
    raise AttributeError(name)
AttributeError: __dict__

Shouldn't it never get to __getattr__("__dict__"), since self.__dict__ should already exist by virtue of this being an object?

I have been using this object (well, an essentially identical object) successfully in a program I have been using for several months in Python 2.6 and am trying to upgrade to Python 3.4. According to everything I can find online, Python 3 should be treating this the same as Python 2.

Edit: Similar to this other question but that question deals with __getattr__ not being called at all. Both are apparently related to new-style vs. old-style classes, though.

Community
  • 1
  • 1
user2100826
  • 335
  • 2
  • 3
  • 13

1 Answers1

2

You're overriding __getattribute__ - which is the default implementation for attribute lookup - in such a way that it raises a NameError when looking up __dict__. This triggers the call to __getattr__, which in turn raises an AttributeError. The result is exactly what you actually asked for (if not what you expected).

FWIW:

  1. as a general rule, don't use eval("self.%s" % name), use getattr(obj, name)
  2. don't override __getattribute__ (nor__setattr__) unless you really understand what you're doing
  3. if you really have to override them (hint: there are very very very few reasons to do so), delegate to the default implementation (using super()).

Also: the code you posted doesn't work "as is" in Python 2.7 (the call to self._d.__setattr__ raises an AttributeError, and making DB a new-style class breaks in exactly the same way as in Python 3.x.

bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
  • So it looks like what you're saying is that because `__getattribute__` raises an AttributeError, that error means Python no longer looks for whether `__dict__` exists, and so it goes straight to `__getattr__`? (And, FWIW, how can I _really_ understand what I'm doing unless I override `__getattribute__` once in a while? Thanks for the tips, tho.) – user2100826 Apr 01 '15 at 20:31
  • "because `__getattribute__` raises an AttributeError, that error means Python no longer looks for whether `__dict__` exists" => Yes, that's *exactly* the point. – bruno desthuilliers Apr 01 '15 at 20:44