class GetAttr:
    cattr = 88                       # Atrybuty przechowywane w klasie i instancji
    def __init__(self):              # To pomija metodę getattr, ale nie getattribute
       self.iattr = 77

    def __len__(self):               # Ponowne zdefiniowane len(), nie wywołuje getattr
        print('__len__: 66')
        return 66

    def __getattr__(self, attr):     # Dostarczenie __str__ po żądaniu, inaczej pusta funkcja
        print('getattr:', attr)      # Nigdy nie uruchamiane dla __str___ — dziedziczone z object
        if attr == '__str__':
            return lambda *args: '[Getattr str]'
        else:
            return lambda *args: None

class GetAttribute:
    cattr = 88                       # Podobnie, ale przechwytuje wszystkie atrybuty
    def __init__(self):              # Z wyjątkiem niejawnych pobrań dla wbudowanych operacji
        self.iattr = 77               

    def __len__(self):               # Ponowne zdefiniowane len(), nie wywołuje getattribute
        print('__len__: 66')         # Ale jawne pobrania dziedziczonego __str__ tak
        return 66

    def __getattribute__(self, attr):
        print('getattribute:', attr)
        if attr == '__str__':
            return lambda *args: '[GetAttribute str]'
        else:
            return lambda *args: None

for Class in GetAttr, GetAttribute:
    print('\n' + Class.__name__.ljust(50, '='))
    X = Class()

    # Zdefiniowane atrybuty wywołują getattribute, ale nie getattr

    X.cattr                   # Atrybut klasy (zdefiniowany, pomija getattr)
    X.iattr                   # Atrybut instancji (zdefiniowany, pomija getattr)
    X.other                   # Brakujący atrybut
    len(X)                    # __len__ definiowane w sposób bezpośredni

    # Operacje wbudowane nie wywołują ani getattr ani getattribute
    # Żadne domyślne atrybuty nie są dziedziczone po obiekcie klasy nadrzędnej

    try:    X[0]              # TPróbuje wywołać__getitem__
    except: print('niepowodzenie []')
    try:    X + 99            # Jak wyżej, __add__
    except: print('niepowodzenie +')
    try:    X()               # Jak wyżej, __call__
    except: print('niepowodzenie ()')

    # Ale jawne wywołania uruchamiają oba przechwytywania

    X.__getitem__(0)
    X.__add__(99)
    X.__call__()

    # Domyślna klasa nadrzędna obiektu definiuje metodę __str__, która wyklucza getattr
    # Jednak bezwzględna metoda getattribute również nie jest wywoływana dla niejawnych pobrań
 
    print(X.__str__())        # __str__: jawne wywołanie => tylko __getattr__ jest pomijane
    print(X)                  # __str__: niejawne poprzez wbudowaną funkcję => oba pomijane

