1

I created this Level class with two objects: name & the precondition of level. With the property function at the end, I want to get a set of all preconditions for a certain level. (For example: for Level 3 the precondition is {Level 2}, and for Level 2 the precondition is {Level 1}, so the all preconditions of Level 3 is {Level 1, Level 2})

class Level:
    def __init__(self,
        name: str,
        preconditions: list[Level] = [],
    ):
        self.name = name
        self.preconditions = set(preconditions)

    def __repr__(self):
        return f'Level(name="{self.name}", preconditions={self.preconditions})'

    @property
    def all_preconditions(self) -> set[Level]:
        preconditions = set()
        for p in self.preconditions:
            preconditions.update(p.preconditions)
            p = p.preconditions
        return preconditions

My code works so far, but I have a loop problem. There are some Levels who are dependent on each other. For example: Level A precondition = {Level B} & Level B precondition = {Level A}.

In this case, I'm getting an infinite loop of preconditions as an output. For example:

{Level(name="A", preconditions={Level(name="B", preconditions={Level(name="A", precondition={Level(name="B", preconditions=set(...)}

Could anyone help me how I stop the loop and only get one precondition?

ezgibec
  • 19
  • 3
  • 2
    FWIW, be very careful with [`preconditions: list[Level] = []`](https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument) – DeepSpace Jul 30 '22 at 20:42
  • 1
    Also, `p = p.preconditions` doesn't really do anything... – DeepSpace Jul 30 '22 at 20:43
  • 2
    What output do you expect? You do have an infinite loop of preconditions. I'm not sure how otherwise you expect Python to express that – DeepSpace Jul 30 '22 at 20:48
  • @DeepSpace I would expect to have Level A precondition = {Level B} & Level B precondition = {Level A}. I still need the for loop to reach the precondition of a precondition (for example example 1 above in my text). – ezgibec Jul 30 '22 at 20:52
  • But `self.preconditions` is part of the object's `repr`, the infinite loop is just in the object's representation string – DeepSpace Jul 30 '22 at 20:54
  • Does this answer your question? [How do I handle recursive repr()'s in Python?](https://stackoverflow.com/questions/2858921/how-do-i-handle-recursive-reprs-in-python) – Uncle Dino Jul 30 '22 at 21:43

1 Answers1

1

NOTE: old answer re-done completely

I misunderstood part of the question, I thought that the problem was with the all_preconditions method, not the __repr__ part.

A simple solution would be to not format using __repr__ when pritning preconditins, so return f'Level(name="{self.name}", preconditions={[p.name for p in self.preconditions]})'.

The better solution would be to make __repr__() not recurse, and it turns out, there's already a solution for that - explicitly call repr(): return f'Level(name="{self.name}", preconditions={repr(self.preconditions)})'

I found this SO question by googling "python repr recursive data type".

EDIT 2:

If you make the Level class into a dataclass using the dataclasses module, the auto-generated __repr__ is already recursion-free.

EDIT 3:

I did some more digging, and it turns out, that the repr() recursion-breaking is only happening for builtin types, for example list, tuple, dict or set, and can not be easily implemented for your own types, which is a shame. The fact that you use lists/dicts saves you now, as they will stop the recursion.

Luckily, dataclasses seem to also define the default repr with this feature.

Uncle Dino
  • 812
  • 1
  • 7
  • 23