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'}
Asked By: Grizzly2501

||

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)
Answered By: Dylan Randall
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.