Recursively changing values of a nested dataclass in python

Question:

I have a dataclass like this:

class chapter:
    title: str
    text: str = ''
    chapter: List['chapter'] = field(default_factory=list)
    removed: bool = False
    

Say there is a list object that contains instances of that dataclass with these values:

content = [
    chapter(
        'chapter 1',
        chapter=[
            chapter('subchapter 1', "Lorem ipsum dolor"),
            chapter('subchapter 2', "Nullam a ligula")
        ]
    ),
    chapter(
        'chapter 2',
        chapter=[
            chapter('subchapter 1', "Fusce eget commodo augue"),
            chapter('subchapter 2', "Pellentesque pretium")
        ]
    ),
    chapter(
        'chapter 3', removed=True
    ),
    chapter(
        'chapter 3',
        chapter=[
            chapter('subchapter 1', "Duis sit amet tempus lectus"),
        ]
    ),
]

Let’s say I want to recursively set values removed to True for a selected chapter with an index 0, and in all subchapters of the chapter. How can I do that? Any suggestions are appreciated.

Asked By: Sonotoki

||

Answers:

Use a recursive function:

def remove_recursive(chapter_instance: chapter) -> None:
    chapter_instance.removed = True
    for c in chapter_instance.chapter:
        remove_recursive(c)

Note that conventionally you would call your class Chapter, and then could use the name chapter to refer to an instance of that class (instead of chapter_instance as I’ve very awkwardly done here.)

Answered By: Samwise

The naming in your code is a bit confusing – you should name the class Chapter instead of chapter and the list chapter should probably be called chapters.

Having said that, this appears to be what you want:

from typing import List
from dataclasses import dataclass, field


@dataclass
class Chapter:
    title: str
    text: str = ''
    chapters: List['Chapter'] = field(default_factory=list)
    removed: bool = False

    def remove(self):
        self.removed = True
        for chapter in self.chapters:
            chapter.remove()


content = [
    Chapter(
        title='chapter 1',
        chapters=[
            Chapter('subchapter 1', "Lorem ipsum dolor"),
            Chapter('subchapter 2', "Nullam a ligula")
        ]
    ),
    Chapter(
        'chapter 2',
        chapters=[
            Chapter('subchapter 1', "Fusce eget commodo augue"),
            Chapter('subchapter 2', "Pellentesque pretium")
        ]
    ),
    Chapter(
        'chapter 3', removed=True
    ),
    Chapter(
        'chapter 3',
        chapters=[
            Chapter('subchapter 1', "Duis sit amet tempus lectus"),
        ]
    ),
]


content[0].remove()
print(content)

Calling .remove() on a chapter sets .removed to True, but also calls .remove() on any of its subchapters, recursively setting .removed on all of them and their subchapters.

Answered By: Grismar