"""
Główne narzędzie: mapattrs() mapuje wszystkie atrybuty występujące w instancji lub przez nią dziedziczone
do instancji lub klasy, z której są dziedziczone.

Również tutaj: różne narzędzia słownikowe wykorzystujące wyrażenia składane.

Zakłada, że dir() zwraca wszystkie atrybuty instancji. Aby emulować dziedziczenie, używa krotki mro klasy, która daje porządek wyszukiwania MRO dla klas w Pythonie 3.X. Rekurencyjne przeszukiwanie drzewa dla porządku DFLR klas jest uwzględnione, ale nieużywane.
"""

import pprint
def trace(label, X, end='\n'):
    print(f'{label}\n{pprint.pformat(X)}{end}')   # Wyświetlanie w schludnej formie

def filterdictvals(D, V):
    """
    Słownik D z usuniętymi wpisami dla wartości V.
    filterdictvals(dict(a=1, b=2, c=1), 1) => {'b': 2}
    """
    return {K: V2 for (K, V2) in D.items() if V2 != V}

def invertdict(D):
    """
    Słownik D z wartościami zamienionymi na klucze (pogrupowane według wartości).
    Wszystkie wartości muszą być haszowalne by działać jako słownik/zestaw kluczy.
    invertdict(dict(a=1, b=2, c=1)) => {1: ['a', 'c'], 2: ['b']}
    """
    def keysof(V):
        return sorted(K for K in D.keys() if D[K] == V)
    return {V: keysof(V) for V in set(D.values())}

def dflr(cls):
    """
    Porządek najpierw w głąb, potem od lewej do prawej (DFLR).
    Zapętlenia nie są możliwe: Python nie zezwala na zmiany dla metody __bases__
    """
    here = [cls]
    for sup in cls.__bases__:
        here += dflr(sup)
    return here

def inheritance(instance):
    """
    Sekwenjcja kolejności dziedziczenia: MRO lub DFLR.
    Sam porządek DFLR nie jest już używany w Pythonie 3.X.
    """
    if hasattr(instance.__class__, '__mro__'):
        return (instance,) + instance.__class__.__mro__
    else:
        return [instance] + dflr(instance.__class__)

def mapattrs(instance, withobject=False, bysource=False):
    """
    Słownik z kluczami, które są wszystkimi dziedziczonymi atrybutami instancji,
    z wartościami informującymi, skąd dziedziczony jest dany atrybut
    withobject: False=oznacza usunięcie wbudowanych atrybutów klasy
    bysource:   True=oznacza grupowanie wyników według obiektów, a nie atrybutów
    Obsługuje klasy ze slotami, które wykluczają metodę __dict__ w instancjach
    """
    attr2obj = {}
    inherits = inheritance(instance)
    for attr in dir(instance):
        for obj in inherits:
             if hasattr(obj, '__dict__') and attr in obj.__dict__:    # Sloty zadziałają
               attr2obj[attr] = obj
               break

    if not withobject:
        attr2obj = filterdictvals(attr2obj, object)
    return attr2obj if not bysource else invertdict(attr2obj)

if __name__ == '__main__':

    class D:         attr2 = 'D'
    class C(D):      attr2 = 'C'
    class B(D):      attr1 = 'B'
    class A(B, C):   pass
    I = A()
    I.attr0 = 'I'

    print(f'Py=>{I.attr0=}, {I.attr1=}, {I.attr2=}\n')    # Przeszukiwanie Pythona
    trace('DZIEDZICZENIE', inheritance(I))                  # [Kolejność dziedziczenia]
    trace('ATRYBUTY',  mapattrs(I))                     # {Atrybut => Źródło}
    trace('ŹRÓDŁA',     mapattrs(I, bysource=True))      # {Źródło => [Atrybuty]}

