0

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.

Jonathan Lym
  • 113
  • 8

1 Answers1

2

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
Oliver Scott
  • 1,673
  • 8
  • 17
  • 1
    No it wouldn't, but why would you need to be able to do that in this context? The addition of the method means that you would never need to do this. If OP did want this functionality then yes they would need to create a custom list class and overide append – Oliver Scott Jul 23 '19 at 14:21
  • First reason: that's what the op asked for. Second reason: it's more convenient than having to do `toys = kid.toys; toys.append(another_toy); kid.toys = toys`. Third reason: since `kids.toys` is a public attribute, you can be sure that users of this library __will__ expect `kid.toys.append(sometoy)` to work the same as `kid.toys = somelist`. – bruno desthuilliers Jul 23 '19 at 14:35
  • I can see how the `give_toy` method would be useful but in my real-world context, this relationship is relatively minor compared to the overall purpose of the class. I think having a custom list class would be accessible (users would be familiar with `append` but would have to learn `give_toy`) as suggested by bruno. Thank you for the suggestion! – Jonathan Lym Jul 23 '19 at 14:45
  • uhu, I'm afraid I missed the `give_toy()` method (which is indeed a better design, except for the fact that it doesn't solve point 3) – bruno desthuilliers Jul 23 '19 at 14:45