Saturday, 8 April 2023

How to make sure list defined in class variable objects are not shared across different instances?

I have the following:

class Loop:
    def __init__(self, group_class: Type[SegmentGroup], start: str, end: str):
        self.group_class = group_class
        self.start = start
        self.end = end

        self._groups: List[SegmentGroup] = []
   
   def from_segments(self, segments):
       self._groups = []  # have to clear so this is not shared among other `Article` instances
       for segment in segments:
            group = self.group_class()
            if some_conditions_are_valid and after_have_populated_group:
                 self._groups.append(group)


class SegmentGroupMetaClass(type):
    def __new__(cls, name, bases, attrs):
        exclude = set(dir(type))

        components: Dict[str, str] = {}
        loops: Dict[str, str] = {}

        for k, v in attrs.items():
            if k in exclude:
                continue

            if isinstance(v, Segment):
                components[v.unique_tag] = k
            elif isinstance(v, Loop):
                loops[v.start] = k

        attrs.update(__components__=components, __loops__=loops)
        return super().__new__(cls, name, bases, attrs)


class SegmentGroup(metaclass=SegmentGroupMetaClass):
    def from_segments(self, ...):
        """May call `Loop.from_segments`

And then I have:

class Topic(SegmentGroup):
    infos = Segment("CAV")
    ...


class Article(SegmentGroup):
    topics = Loop(Topic, ...)
    ...

I have a routine that essentially calls Article().from_segments(segments) where segments is injected from another service in a loop that creates a new Article instance on every new iteration.

As you may have noticed, Loop._groups must be properly handled in order to not have its values shared among different Article instances. My (hacky) solution works fine if we call Loop.from_segments for all iterations, but this is not guaranteed as the topics segment loop may be missing in the injected segments.

This means that for an Article without the topics segments, it'll actually use the values from the previous iteration (because Loop._groups won't be cleared since Loop.from_segments won't be called).

I can think in a way to fix this by clearing Loop._groups at Article.__init__...

class SegmentGroup(metaclass=SegmentGroupMetaClass):
    def __init__(self) -> None:
        for loop_label in self.__loops__.values():
            setattr(getattr(self, loop_label), "_groups", [])

...but it looks even hackier and non-elegant/less efficient to me.

How would you clear Loop._groups for every new Article instance?



from How to make sure list defined in class variable objects are not shared across different instances?

No comments:

Post a Comment