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.

2
  • 1
    Make a custom list subclass inside your Kid class and override append(), maybe? Commented Jul 23, 2019 at 14:00
  • @GreenCloakGuy I think that's a solution but I was wondering if Python has any built-in syntax for a problem like this. Commented Jul 23, 2019 at 14:05

1 Answer 1

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
Sign up to request clarification or add additional context in comments.

4 Comments

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
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.
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!
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)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.