File: LP6E/Chapter31/mapattrs.py
"""
Main tool: mapattrs() maps all attributes on or inherited by an
instance to the instance or class from which they are inherited.
Also here: assorted dictionary tools using comprehensions.
Assumes dir() gives all attributes of an instance. To emulate
inheritance, this uses the class's __mro__ tuple, which gives the
MRO search order for classes in Python 3.X. A recursive tree
traversal for the DFLR order of classes is included but unused.
"""
import pprint
def trace(label, X, end='\n'):
print(f'{label}\n{pprint.pformat(X)}{end}') # Print nicely
def filterdictvals(D, V):
"""
dict D with entries for value V removed.
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):
"""
dict D with values changed to keys (grouped by values).
Values must all be hashable to work as dict/set keys.
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):
"""
Depth-first left-to-right order of class tree at cls.
Cycles not possible: Python disallows on __bases__ changes.
"""
here = [cls]
for sup in cls.__bases__:
here += dflr(sup)
return here
def inheritance(instance):
"""
Inheritance order sequence: MRO or DFLR.
DFLR alone is no longer used in Python 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):
"""
dict with keys giving all inherited attributes of instance,
with values giving the object that each is inherited from.
withobject: False=remove object built-in class attributes.
bysource: True=group result by objects instead of attributes.
Supports classes with slots that preclude __dict__ in instances.
"""
attr2obj = {}
inherits = inheritance(instance)
for attr in dir(instance):
for obj in inherits:
if hasattr(obj, '__dict__') and attr in obj.__dict__: # Slots okay
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') # Python's search
trace('INHERITANCE', inheritance(I)) # [Inheritance order]
trace('ATTRIBUTES', mapattrs(I)) # {Attr => Source}
trace('SOURCES', mapattrs(I, bysource=True)) # {Source => [Attrs]}