Default values for parameters
In part 7 you gave a function's parameters default values, used whenever the caller leaves the argument out. Methods and constructors can have them too. They're handy when most objects start the same way:
class Character:
def __init__(self, name, health=100, level=1):
self.name = name
self.health = health
self.level = level
def heal(self, amount=10):
self.health += amount
def __str__(self):
return f"{self.name}: health {self.health}, level {self.level}"
ana = Character("Ana")
bo = Character("Bo", 80)
cy = Character("Cy", level=5)
bo.heal()
bo.heal(5)
print(ana)
print(bo)
print(cy)
Ana: health 100, level 1
Bo: health 95, level 1
Cy: health 100, level 5Arguments fill the parameters in order, so Character("Bo", 80) sets the health. To give a later parameter and skip an earlier one, name it: Character("Cy", level=5) is a keyword argument. Writing Character("Cy", 5) would set the health to 5, not the level.
Parameters with default values come after the ones without. def __init__(self, health=100, name): is a syntax error: parameter without a default follows parameter with a default.
The trap: a list as a default value
A default value is created only once, when Python reads the def line, not on every call. For a number or a string that never matters. For a list it does, because a list can change. Every object that gets the default ends up with the same list:
class Backpack:
def __init__(self, owner, items=[]):
self.owner = owner
self.items = items
def pack(self, item):
self.items.append(item)
ana = Backpack("Ana")
bo = Backpack("Bo")
ana.pack("map")
print(bo.items)
print(ana.items is bo.items)
['map']
TrueAna packed a map, and it turned up in Bo's backpack, because both backpacks refer to the one list that was made for the default.
The fix is to use None as the default, and create a new list inside the method when the caller didn't give one:
class Backpack:
def __init__(self, owner, items=None):
self.owner = owner
if items is None:
self.items = []
else:
self.items = items
def pack(self, item):
self.items.append(item)
ana = Backpack("Ana")
bo = Backpack("Bo")
ana.pack("map")
print(bo.items)
print(Backpack("Cy", ["tent"]).items)
[]
['tent']Now self.items = [] runs on every call, so every backpack gets a list of its own. Never use a list (or a dictionary) as a default value; use None and create it in the method.