質問

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

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.

他のヒント

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.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top