-1

I'm trying to get the following code to work as intended in the comments below. For instance, I will create three Dog instances using the commands below (dogs: buddy, pascal, and kimber). However, when running buddy.teach('sit'), it adds the 'sit' trick to both buddy and pascal instances. Can you please manipulate the code so that the attribute is defined for the instance objects rather than the class object?

Please only edit the code (do not change the commands in the comments below).

class Dog:
    def __init__(self, name, tricks=set()):
        self.name = name
        self.tricks = tricks
    def teach(self, trick):
        self.tricks.add(trick)
# Change the broken code above so that the following lines work:
# 
# buddy = Dog('Buddy')
# pascal = Dog('Pascal')
# kimber = Dog('Kimber', tricks={'lie down', 'shake'})
# buddy.teach('sit')
# pascal.teach('fetch')
# buddy.teach('roll over')
# kimber.teach('fetch')
# print(buddy.tricks)  # {'sit', 'roll over'}
# print(pascal.tricks)  # {'fetch'}
# print(kimber.tricks)  # {'lie down', 'shake', 'fetch'}
ThePyGuy
  • 17,779
  • 5
  • 18
  • 45
NUSav77
  • 62
  • 6

3 Answers3

1

Here you go:

class Dog:
    def __init__(self, name, tricks=set()):
        self.name = name
        self.tricks = set(tricks)
    def teach(self, trick):
        self.tricks.add(trick)
ObjectJosh
  • 601
  • 3
  • 14
1

Your __init()__ has the following definition:

def __init__(self, name, tricks=set()):

That set() is and object, which is instantiated once for the class definition and shared in each instance. So every instance that uses the default tricks share the same set of tricks(!).

To prevent this, a common pattern is:

class Dog:
    def __init__(self, name, tricks=None):
        self.name = name
        self.tricks = set()
        if tricks:
            self.tricks = tricks
    def teach(self, trick):
        self.tricks.add(trick)
agtoever
  • 1,669
  • 16
  • 22
1

The problem comes from your default argument tricks=set() in __init__.

Default arguments are only evaluated once, when the function/method gets defined. So, a empty set gets created, which will be used as the default argument whenever __init__ gets called without specifying tricks.

When you execute buddy = Dog('Buddy'), this empty set gets used as tricks, and in self.tricks = tricks, you make the tricks attribute of buddy refer to it.

Later, you execute pascal = Dog('Pascal'), again without specifying trick, so the same set created as the default argument gets used, and the trick attribute of pascal will also refer to it.

So, both instances share the same set for tricks.

There would be no problem if you passed a value for tricks when creating an instance: it would be an independant set. There wouldn't be any problem either if this default weren't mutable (see “Least Astonishment” and the Mutable Default Argument.

In order to avoid using a mutable default argument, the classic solution is as follows:

 class Dog:
    def __init__(self, name, tricks=None):
        if tricks is None:
            tricks = set()
        self.name = name
        self.tricks = tricks
        
    def teach(self, trick):
        self.tricks.add(trick)
        
buddy = Dog('Buddy')
pascal = Dog('Pascal')
kimber = Dog('Kimber', tricks={'lie down', 'shake'})
buddy.teach('sit')
pascal.teach('fetch')
buddy.teach('roll over')
kimber.teach('fetch')
print(buddy.tricks)  # {'sit', 'roll over'}
print(pascal.tricks)  # {'fetch'}
print(kimber.tricks)  # {'lie down', 'shake', 'fetch'}

Output:

{'roll over', 'sit'}
{'fetch'}
{'lie down', 'fetch', 'shake'}
Thierry Lathuille
  • 23,663
  • 10
  • 44
  • 50