class ListTree:
    """
    Klasa mieszana zwracająca ślad __str__ całego drzewa klasy
    i atrybutów wszystkich jego obiektów na poziomie self i powyżej.
    Metoda str uruchamiana za pomocą funkcji print zwraca skonstruowany
    ciąg. Klasa:
    - wykorzystuje nazwy __X atrybutów, aby uniknąć konfliktów z klientami;
    -  jawnie odwołuje się do klas nadrzędnych zgodnie z porządkiem DLFR (a nie MRO);
   -  wykorzystuje metodę __dict__ zamiast dir() ponieważ atrybuty są listowane według obiektu;
   - obsługuje klasy ze slotami – tutaj brak slotów zapewnia __dict__.

    """

    def __attrnames(self, obj, indent, unders=True):
        spaces = ' ' * (indent + 1)
        result = ''
        for attr in sorted(obj.__dict__):
            if attr.startswith('__') and attr.endswith('__'):
                if unders: result += f'{spaces}{attr}\n'
            else:
                result += f'{spaces}{attr}={getattr(obj, attr)!r}\n'
        return result

    def __listclass(self, aClass, indent):
        dots = '.' * indent
        preamble = (f'\n{dots}'
                    f'<Klasa {aClass.__name__}'
                    f', adres {id(aClass):#x}')

        if aClass in self.__visited:
            return preamble + ': (patrz wyżej)>\n'                # Już wylistowane
        elif aClass is object:
            self.__visited[aClass] = True
            return preamble + ': (zobacz dir(object))>\n'          # Pominięcie obiektów 
        else:
            self.__visited[aClass] = True
            here  = self.__attrnames(aClass, indent)            # Własne atrybuty + klasy nadrzędne
            above = ''
            for Super in aClass.__bases__:
                above += self.__listclass(Super, indent + 4)
            return preamble + f':\n{here}{above}{dots}>\n'

    def __str__(self):
        self.__visited = {}
        here  = self.__attrnames(self, 0)                       # Atrybuty własne
        above = self.__listclass(self.__class__, 4)             # Drzewo własnych klas nadrzędnych
        return (f'<Instancja {self.__class__.__name__}'         # Nazwa własnej klasy
                f', adres {id(self):#x}'                        # Adres własnej klasy (hex)
                f':\n{here}{above}>')                           # Atrybuty + klasy nadrzędne

if __name__ == '__main__':
    import testmixin
    testmixin.tester(ListTree)      # Test klasy z tego modułu

