Question: 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?

Answer:  How to make sure list defined in class variable objects are not shared across different instances?  TL;DR: make so that your class variables instances use the descriptor mechanisms in Python (like the __get__ method), so that it has control on which instance it is operating at each time.  You can make your Loop class be a descriptor: this way anytime it is used in an Article instance, Python will get it through its __get__ method: you can them create a sub-object or a closure that will use a list allocated in the host instance. In this example, whenever retrieved from an instance (ex. Article().topics, this will create a temporary copy of the loop object, and set an _instance atribute on it. Then I promoted the ._groups attribute in the Loop object itself to be a property (which uses the same __get__ mechanism under the hood), to store on first access, and then use always, a list on the host instance.All you have to do is to ensure your Loop class works well with Python's copy.copy - if all its code is what is listed here, it will work nicely. Also: using .copy and not .deepcopy ensure the instance-bound Loop that is created will share any other data with the main instance - as well as be lightweight. from copy import copy class Loop: def __init__(self, group_class: Type[SegmentGroup], start: str, end: str): self.group_class = group_class self.start = start self.end = end def __set_name__(self, owner, name): # uses the Python __set_name__ mechanism to automatically know the name # each Loop instance is associated to in a class self.name = name def __get__(self, instance, owner): if instance is None: return self instance_specific_loop = copy(self) instance_specific_loop._instance = instance @property def _groups(self): attr_name = f"_groups_{self.name}" if not hasattr(self._instance, attr_name): setattr(self._instance, attr_name, []) return getattr(self._instance, attr_name) def from_segments(self, segments): 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): ... class SegmentGroup(metaclass=SegmentGroupMetaClass): def from_segments(self, ...): """May call `Loop.from_segments""" ... # it will sork, as soon as it does: # self._loop_attribute_name_.from_segments(...) class Article(SegmentGroup): topics = Loop(Topic, ...) ...