views:

343

answers:

2

This is a very contrived example as it's not easy to explain the context in which I have ended up implementing this solution. However, if anyone can answer why this particular peculiarity happens, I'd be grateful.

The example:

class A(dict):  
    def __init__(self):
     self['a'] = 'success'

    def __getitem__(self, name):
     print 'getitem'
     return dict.__getitem__(name)

class B(object):
    def __init__(self):
     self._a = A()
     setattr(self, '__getitem__', self._a.__getitem__) 

b = B()
c = b['a']

This outputs:

c = b['a']
TypeError: 'B' object is unsubscriptable

Even though it's a bizarre way of doing this (clearly subclassing would be more logical), why doesn't it find the method I have explicitly set?

If I do this:

dir(b)

I get this:

['__class__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__getitem__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__', '_a']

The same issue occurs with other methods such as __iter__. What is it about explicitly defining this method that works?

+1  A: 

This is because you can't override special class methods on the fly.

I wasn't able to find a reference about this but is basically because they are class methods and are not allowed to be instance methods.

mpeterson
you can override them on the fly, the mistake was that Dan messed with instance methods instead of class methods.
Toni Ruža
spot on, I didn't understand the difference between instance methods and class methods in this sense. thanks.
Dan
+7  A: 

When you use brackets [] python looks in the class. You must set the method in the class.

Here's your code adapted:

class A(dict):  
    def __init__(self):
        self['a'] = 'success'

    def __getitem__(self, name):
        print 'getitem!'
        return dict.__getitem__(self, name)

class B(object):
    def __init__(self):
        self._a = A()
        B.__getitem__ = self._a.__getitem__

b = B()
c = b['a']
nosklo
Hang on, you're pointing a class method to an instance method, doesn't that mean it'll point to the same instance for all instances of B?
Dan
@Dan: yes. __getitem__ only works for the entire class. There is no per-instance __getitem__.
nosklo