Code editing task: tree_abstractions
Topic: DSA; change kind: perfective

Original code:
from abc import abstractmethod

class Tree:

    @abstractmethod
    def tree_map(self, func):
        pass 

    @abstractmethod
    def tree_filter(self, func, filler):
        pass 

    @abstractmethod
    def tree_andmap(self, func):
        pass 

    @abstractmethod
    def tree_ormap(self, func):
        pass 

    @abstractmethod
    def __eq__(self, other):
        pass

class Node(Tree):

    def __init__(self, left, right):
        self.left = left
        self.right = right

    def tree_map(self, func):
        self.left.tree_map(func)
        self.right.tree_map(func)

    def tree_filter(self, func, filler):
        self.left.tree_filter(func, filler)
        self.right.tree_filter(func, filler)

    def tree_andmap(self, func):
        return self.left.tree_andmap(func) and self.right.tree_andmap(func)

    def tree_ormap(self, func):
        return self.left.tree_ormap(func) or self.right.tree_ormap(func)
    
    def __eq__(self, other):
        if isinstance(other, Node):
            return self.left == other.left and self.right == other.right
        return False

class Leaf(Tree):

    def __init__(self, value):
        self.value = value

    def tree_map(self, func):
        self.value = func(self.value)

    def tree_filter(self, func, filler):
        if func(self.value):
            self.value = filler

    def tree_andmap(self, func):
        return func(self.value)

    def tree_ormap(self, func):
        return func(self.value)
    
    def __eq__(self, other):
        if isinstance(other, Leaf):
            return self.value == other.value 
        return False

Instruction (descriptive):
Change the `tree_map` and `tree_filter` methods in `Tree` and its subclasses to return new objects rather than modifying in place.

Instruction (lazy):
Change `Tree` and its subclasses not modify in place and be chainable.

Edited code:
from abc import abstractmethod

class Tree:

    @abstractmethod
    def tree_map(self, func):
        pass 

    @abstractmethod
    def tree_filter(self, func, filler):
        pass 

    @abstractmethod
    def tree_andmap(self, func):
        pass 

    @abstractmethod
    def tree_ormap(self, func):
        pass 

    @abstractmethod
    def __eq__(self, other):
        pass

class Node(Tree):

    def __init__(self, left, right):
        self.left = left
        self.right = right

    def tree_map(self, func):
        return Node(self.left.tree_map(func), self.right.tree_map(func))

    def tree_filter(self, func, filler):
        return Node(self.left.tree_filter(func, filler), self.right.tree_filter(func, filler))

    def tree_andmap(self, func):
        return self.left.tree_andmap(func) and self.right.tree_andmap(func)

    def tree_ormap(self, func):
        return self.left.tree_ormap(func) or self.right.tree_ormap(func)
    
    def __eq__(self, other):
        if isinstance(other, Node):
            return self.left == other.left and self.right == other.right
        return False

class Leaf(Tree):

    def __init__(self, value):
        self.value = value

    def tree_map(self, func):
        return Leaf(func(self.value))

    def tree_filter(self, func, filler):
        if func(self.value):
            return Leaf(filler)
        else:
            return self

    def tree_andmap(self, func):
        return func(self.value)

    def tree_ormap(self, func):
        return func(self.value)
    
    def __eq__(self, other):
        if isinstance(other, Leaf):
            return self.value == other.value 
        return False

Tests:
### START TESTS ###
if True:  # pragma: no cover
    add_ten = lambda e : e + 10
    is_positive = lambda e : e > 0
    contains_x = lambda e : "x" in e
    count_length = lambda e : len(e)

    assert Leaf(3).tree_map(add_ten).value == Leaf(13).value
    assert Leaf(-10).tree_andmap(is_positive) == False

    assert Leaf("hello").tree_filter(contains_x, 0).value == "hello"

    tree = Node(Node(Leaf(2), Node(Leaf(5), Leaf(11))), Node(Leaf(7), Leaf(6)))
    assert tree.tree_map(add_ten) == Node(Node(Leaf(12), Node(Leaf(15), Leaf(21))), Node(Leaf(17), Leaf(16)))
    assert tree.tree_filter(is_positive, 0) == Node(Node(Leaf(0), Node(Leaf(0), Leaf(0))), Node(Leaf(0), Leaf(0)))

    assert Node(Leaf(10), Node(Leaf(4), Leaf(-9))).tree_andmap(is_positive) == False
    assert Node(Leaf(10), Node(Leaf(4), Leaf(-9))).tree_ormap(is_positive) == True

    tree2 = Node(Node(Leaf("hello"), Leaf("world")), Node(Node(Node(Leaf("hx"), Leaf("ow")), Leaf("owaowa")), Leaf("epa")))

    assert tree2.tree_map(count_length) == Node(Node(Leaf(5), Leaf(5)), Node(Node(Node(Leaf(2), Leaf(2)), Leaf(6)), Leaf(3)))
    assert tree2.tree_ormap(contains_x) == True
    assert tree2.tree_andmap(contains_x) == False

    assert tree2 != 2
    assert Leaf(3) != Leaf(4)
    assert Leaf(3) != 1