Some questions about __getattr__ and __getattribute__?

The first demo:

class B:
    def __init__(self):
        self.name = '234'
    # def __getattribute__(self, name):
    #     print('getattr')
    def __getattr__(self, name):    
        print('get')
    def __setattr__(self, name, value):
        print('set')
    def __delattr__(self, name):
        print('del')


b = B()
print(b.__dict__)
b.name

b.__dict__ is {}, but the second demo:

class B:
    def __init__(self):
        self.name = '234'
    def __getattribute__(self, name):
        print('getattr')
    def __getattr__(self, name):
        print('get')
    def __setattr__(self, name, value):
        print('set')
    def __delattr__(self, name):
        print('del')


b = B()
print(b.__dict__)
b.name

b.__dict__ is None, why? And b.__dict__ invokes __getattribute__, but don’t invoke __getattr__, does it mean __getattribute__ will prevent from invoking __getattr__?

Solution:

The __getattribute__, __setattr__ and __delattr__ methods are called for all attribute access (getting, setting and deleting). __getattr__ on the other hand is only called for missing attributes; it is not normally already implemented, but if it is then __getattribute__ calls it if it could not otherwise locate the attribute, or if an AttributeError was raised by __getattribute__.

You replaced the standard implementations of the 3 main methods with methods that do nothing but print and return None (the default in the absence of an explicit return statement). __dict__ is just another attribute access, and your __getattribute__ method returns None, and never itself calls __getattr__ or raises an AttributeError.

From the Customizing attribute access documentation:

object.__getattr__(self, name)
Called when an attribute lookup has not found the attribute in the usual places (i.e. it is not an instance attribute nor is it found in the class tree for self).

and

object.__getattribute__(self, name)
Called unconditionally to implement attribute accesses for instances of the class. If the class also defines __getattr__(), the latter will not be called unless __getattribute__() either calls it explicitly or raises an AttributeError.

(Bold emphasis mine).

Either call the base implementation (via super().__getattribute__) or raise an AttributeError:

>>> class B:
...     def __init__(self):
...         self.name = '234'
...     def __getattribute__(self, name):
...         print('getattr')
...         return super().__getattribute__(name)
...     def __getattr__(self, name):
...         print('get')
...     def __setattr__(self, name, value):
...         print('set')
...     def __delattr__(self, name):
...         print('del')
...
>>> b = B()
set
>>> b.__dict__
getattr
{}
>>> b.name
getattr
get
>>> class B:
...     def __init__(self):
...         self.name = '234'
...     def __getattribute__(self, name):
...         print('getattr')
...         raise AttributeError(name)
...     def __getattr__(self, name):
...         print('get')
...     def __setattr__(self, name, value):
...         print('set')
...     def __delattr__(self, name):
...         print('del')
...
>>> b = B()
set
>>> b.__dict__
getattr
get
>>> b.name
getattr
get

Note that by calling super().__getattribute__ the actual __dict__ attribute is found. By raising an AttributeError instead, __getattr__ was called, which also returned None.