Python: iterating through class dependants
Question:
from dataclasses import dataclass
@dataclass
class BronzeA:
name = "bronze_a"
quality = "bronze"
dependencies = None
@dataclass
class BronzeAA:
name = "bronze_aa"
quality = "bronze"
dependencies = None
@dataclass
class SilverAA:
name = "silver_aa"
quality = "silver"
dependencies = [BronzeAA]
@dataclass
class SilverA:
name = "silver_a"
quality = "silver"
dependencies = [BronzeA, SilverAA]
@dataclass
class BronzeB:
name = "bronze_b"
quality = "bronze"
dependencies = None
@dataclass
class SilverB:
name = "silver_b"
quality = "silver"
dependencies = [BronzeB, SilverAA]
@dataclass
class Gold:
name = "gold_data"
quality = "gold"
dependencies = [SilverA, SilverB]
I want to iterate through the dependants of my data classes and get a dictionary of all dependant class names. I have attempted the below:
my_gold = Gold()
dependants = {}
for d in my_gold.dependencies:
dependants[d.name] = d.__name__
if d.quality != 'bronze':
for dd in d.dependencies:
dependants[dd.name] = dd.__name__
if dd.quality != 'bronze':
for ddd in dd.dependencies:
dependants[ddd.name] = ddd.__name__
This works, but is very clunky, and might need another level if we get more quality dependencies at the same level (note how silver already has other silver dependants). How can I do this more efficiently and without so many nested levels?
Expected output:
{'silver_a': 'SilverA',
'bronze_a': 'BronzeA',
'silver_aa': 'SilverAA',
'bronze_aa': 'BronzeAA',
'silver_b': 'SilverB',
'bronze_b': 'BronzeB'}
Answers:
Issue 1 – Not using the dataclass decorator.
By not decorating your classes with the dataclass decorator you do not gain the functionality of the dataclass that python provides.
To learn more about data classes see the following resources:
Issue 2 – Collecting the dependencies
You can think of this problem as a graph:
Image of the Graph
What we want to do is traverse this graph, from the given node, in this case the Gold dataclass. To accomplish this we can do a depth first traversal over the graph using the stack data structure. In python a list can be used as a stack with the pop method.
It is important to note that this will only work if there are no cyclical dependencies. For example if BronzeAA had SilverB as a dependency it would introduce a loop (cycle) into the graph and we would essentially traverse the graph forever with the given solution below.
Solution
from dataclasses import dataclass
# Tag the classes with @dataclass to give them the superpowers of dataclasses
# To learn more see: https://docs.python.org/3/library/dataclasses.html
# https://www.dataquest.io/blog/how-to-use-python-data-classes/
@dataclass
class BronzeA:
name = "bronze_a"
quality = "bronze"
dependencies = None
@dataclass
class BronzeAA:
name = "bronze_aa"
quality = "bronze"
dependencies = None
@dataclass
class SilverAA:
name = "silver_aa"
quality = "silver"
dependencies = [BronzeAA]
@dataclass
class SilverA:
name = "silver_a"
quality = "silver"
dependencies = [BronzeA, SilverAA]
@dataclass
class BronzeB:
name = "bronze_b"
quality = "bronze"
dependencies = None
@dataclass
class SilverB:
name = "silver_b"
quality = "silver"
dependencies = [BronzeB, SilverAA]
@dataclass
class Gold:
name = "gold_data"
quality = "gold"
dependencies = [SilverA, SilverB]
my_gold = Gold()
dependants = {}
# using a stack will allow us to iterate through the dependencies in a depth-first manner while collecting the dependants
# more about stacks, see: https://www.geeksforgeeks.org/stack-in-python/
dependencies = my_gold.dependencies
dependency = dependencies.pop(0)
# loop while there are still dependencies to iterate through
while len(dependencies) > 0:
# add the dependency to the dictionary
dependants[dependency.name] = dependency.__name__
# if there are dependencies, add them to the list
if dependency.dependencies is not None:
# the extend function will extend the list with the items from another list
# e.g.: [1, 2, 3].extend([4, 5, 6]) -> [1, 2, 3, 4, 5, 6]
dependencies.extend(dependency.dependencies)
# remove, and return, the last item from the list, thereby iterating to the next dependency
dependency = dependencies.pop(0)
print(dependants)
from dataclasses import dataclass
@dataclass
class BronzeA:
name = "bronze_a"
quality = "bronze"
dependencies = None
@dataclass
class BronzeAA:
name = "bronze_aa"
quality = "bronze"
dependencies = None
@dataclass
class SilverAA:
name = "silver_aa"
quality = "silver"
dependencies = [BronzeAA]
@dataclass
class SilverA:
name = "silver_a"
quality = "silver"
dependencies = [BronzeA, SilverAA]
@dataclass
class BronzeB:
name = "bronze_b"
quality = "bronze"
dependencies = None
@dataclass
class SilverB:
name = "silver_b"
quality = "silver"
dependencies = [BronzeB, SilverAA]
@dataclass
class Gold:
name = "gold_data"
quality = "gold"
dependencies = [SilverA, SilverB]
I want to iterate through the dependants of my data classes and get a dictionary of all dependant class names. I have attempted the below:
my_gold = Gold()
dependants = {}
for d in my_gold.dependencies:
dependants[d.name] = d.__name__
if d.quality != 'bronze':
for dd in d.dependencies:
dependants[dd.name] = dd.__name__
if dd.quality != 'bronze':
for ddd in dd.dependencies:
dependants[ddd.name] = ddd.__name__
This works, but is very clunky, and might need another level if we get more quality dependencies at the same level (note how silver already has other silver dependants). How can I do this more efficiently and without so many nested levels?
Expected output:
{'silver_a': 'SilverA',
'bronze_a': 'BronzeA',
'silver_aa': 'SilverAA',
'bronze_aa': 'BronzeAA',
'silver_b': 'SilverB',
'bronze_b': 'BronzeB'}
Issue 1 – Not using the dataclass decorator.
By not decorating your classes with the dataclass decorator you do not gain the functionality of the dataclass that python provides.
To learn more about data classes see the following resources:
Issue 2 – Collecting the dependencies
You can think of this problem as a graph:
Image of the Graph
What we want to do is traverse this graph, from the given node, in this case the Gold dataclass. To accomplish this we can do a depth first traversal over the graph using the stack data structure. In python a list can be used as a stack with the pop method.
It is important to note that this will only work if there are no cyclical dependencies. For example if BronzeAA had SilverB as a dependency it would introduce a loop (cycle) into the graph and we would essentially traverse the graph forever with the given solution below.
Solution
from dataclasses import dataclass
# Tag the classes with @dataclass to give them the superpowers of dataclasses
# To learn more see: https://docs.python.org/3/library/dataclasses.html
# https://www.dataquest.io/blog/how-to-use-python-data-classes/
@dataclass
class BronzeA:
name = "bronze_a"
quality = "bronze"
dependencies = None
@dataclass
class BronzeAA:
name = "bronze_aa"
quality = "bronze"
dependencies = None
@dataclass
class SilverAA:
name = "silver_aa"
quality = "silver"
dependencies = [BronzeAA]
@dataclass
class SilverA:
name = "silver_a"
quality = "silver"
dependencies = [BronzeA, SilverAA]
@dataclass
class BronzeB:
name = "bronze_b"
quality = "bronze"
dependencies = None
@dataclass
class SilverB:
name = "silver_b"
quality = "silver"
dependencies = [BronzeB, SilverAA]
@dataclass
class Gold:
name = "gold_data"
quality = "gold"
dependencies = [SilverA, SilverB]
my_gold = Gold()
dependants = {}
# using a stack will allow us to iterate through the dependencies in a depth-first manner while collecting the dependants
# more about stacks, see: https://www.geeksforgeeks.org/stack-in-python/
dependencies = my_gold.dependencies
dependency = dependencies.pop(0)
# loop while there are still dependencies to iterate through
while len(dependencies) > 0:
# add the dependency to the dictionary
dependants[dependency.name] = dependency.__name__
# if there are dependencies, add them to the list
if dependency.dependencies is not None:
# the extend function will extend the list with the items from another list
# e.g.: [1, 2, 3].extend([4, 5, 6]) -> [1, 2, 3, 4, 5, 6]
dependencies.extend(dependency.dependencies)
# remove, and return, the last item from the list, thereby iterating to the next dependency
dependency = dependencies.pop(0)
print(dependants)