pythonobject-composition

How can I create a list attribute that updates dynamically in Python?


I have two classes that refer to each other in a one-to-many relationship (Kid and Toy in the below example). When I assign a new Toy to Kid, I want the Kid to be assigned to Toy as well.

Creating a custom class based on list for the toys attribute and redefining methods (e.g. append, extend, delete) would work but I wanted to know if there is a better way.

class Toy:
    def __init__(self, name, kid=None):
        self.name = name
        self.kid = kid

class Kid:
    def __init__(self, name, toys):
        self.name = name
        self.toys = toys

    @property
    def toys(self):
        return self._toys

    @toys.setter
    def toys(self, val):
        self._toys = val
        # Assign the kid to the toys
        for toy in self._toys:
            toy.kid = self

if __name__ == "__main__":
    toys = [Toy('Woodie'), Toy('Slinky'), Toy('Rex')]
    andy = Kid('Andy', toys)

    # Andy corrected assigned to toys
    for toy in andy.toys:
        print('{}\t{}'.format(toy.name, toy.kid.name))
    print('-')

    # Add new toy
    andy.toys.append(Toy('Buzz'))

    # Throws error because Buzz is not assigned Andy
    for toy in andy.toys:
        print('{}\t{}'.format(toy.name, toy.kid.name))

Output:

Woodie  Andy
Slinky  Andy
Rex     Andy
-
Woodie  Andy
Slinky  Andy
Rex     Andy
Traceback (most recent call last):
  File "c:/Users/jonat/Desktop/tests/inheritance_q.py", line 34, in <module>
    print('{}\t{}'.format(toy.name, toy.kid.name))
AttributeError: 'NoneType' object has no attribute 'name'

I would like Buzz to be assigned Andy.


Solution

  • You could just add a method into your Kid class:

    class Toy:
        def __init__(self, name, kid=None):
            self.name = name
            self.kid = kid
    
    class Kid:
        def __init__(self, name, toys):
            self.name = name
            self.toys = toys
    
        @property
        def toys(self):
            return self._toys
    
        @toys.setter
        def toys(self, val):
            self._toys = val
            # Assign the kid to the toys
            for toy in self._toys:
                toy.kid = self
    
        def give_toy(self, toy):
            toy.kid = self
            self.toys.append(toy)
    
    
    if __name__ == "__main__":
        toys = [Toy('Woodie'), Toy('Slinky'), Toy('Rex')]
        andy = Kid('Andy', toys)
    
        # Andy corrected assigned to toys
        for toy in andy.toys:
            print('{}\t{}'.format(toy.name, toy.kid.name))
        print('-')
    
        # Add new toy
        andy.give_toy(Toy('Buzz'))
    
        # Throws error because Slinky is not assigned Andy
        for toy in andy.toys:
            print('{}\t{}'.format(toy.name, toy.kid.name))
    

    Output:

    Woodie  Andy
    Slinky  Andy
    Rex     Andy
    -
    Woodie  Andy
    Slinky  Andy
    Rex     Andy
    Buzz    Andy