2
votes

Consider the following difference between classic classes and the new style classes.

   class A():
        data = 'abcd'
        def __getattr__(self, name): 
            return getattr(self.data, name)

    class B(object):
        data = 'abcd'
        def __getattr__(self, name): 
            return getattr(self.data, name)



    print(A()[0])       # Prints 'a'
    print(B()[0])       # TypeError: 'B' object does not support indexing

I do know that explanation for this property is that new style objects attribute search starts at class instead of instances for builtin operations. But the class object too has __getattr__ defined and why it doesn't get invoked for the missing attribute here which is __getitem__.

2
The first answer in the question you pointed says that getattr and getattribute are bypassed. But my question is specifically why it is being bypassed.rogue-one
Supposedly because of the link that was included in that answer, 2nd paragraph: The rationale behind this behaviour lies with a number of special methods such as __hash__() and __repr__() that are implemented by all objects, including type objects. If the implicit lookup of these methods used the conventional lookup process, they would fail when invoked on the type object itselfJon Clements
" the implicit lookup of these methods used the conventional lookup process, they would fail when invoked on the type object itself" .. no explanation whatsoever is given for the above statement.. would appreciate a detailed explanation.rogue-one

2 Answers

1
votes

As @Jon mentions in the comments you can find the answer in the question Asymmetric behavior for __getattr__, newstyle vs oldstyle classes and in the documentation at Special method lookup for new-style classes.

Special methods are directly looked up in the class object for performance reasons.

I would like to add that, as far as I know, this means while you can still forward all non-special methods from to the encapsulated class with __getattr__ you will have to forward all special methods explicitly:

class A():
    data = 'abcd'
    def __getattr__(self, name): 
        return getattr(self.data, name)

class B(object):
    data = 'abcd'
    # forward all non-special methods to data
    def __getattr__(self, name): 
        return getattr(self.data, name)
    # forward __getitem__ to data
    def __getitem__(self, index):
        return self.data[index]


print(A()[0])       # Prints 'a'
print(B()[0])       # explicitly defined Prints 'a'
print(B().join([' 1 ',' 2 ']))  # forwarded to data prints ' 1 abcd 2 '

I would also like to point out that B.data is a class attribute not an instance attribute. That's fine for this example, but might not be what you are intending.

1
votes

I figured out the answer is that the __getattr__ is called only if the attribute search starts at the instance object. But if the attribute search explicitly on the class and instance is skipped __getattr__ is never called.

class B():
    data = 'abcd'
    def __getattr__(self, name):
        print('You are looking for something that doesn\'t exist')
        return None

b = B()
b.a
You are looking for something that doesn't exist
B.a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: class B has no attribute 'a'

Hence in classic classes the search for __getitem__ starts at instance object and __getattr__ is invoked whereas in the new style class the search starts at class object and hence __getattr__ is not invoked.