Hàm là một đoạn code có tên, viết một lần rồi gọi lại bao nhiêu lần cũng được. Thay vì chép đi chép lại công thức tính sát thương ở mười chỗ, bạn gói nó vào hàm calc_damage và gọi hàm đó. Khi đổi công thức, chỉ sửa một chỗ.
Định nghĩa và gọi hàm
def show_welcome():
print("Chào mừng tới Làng Khởi Đầu")
print("Nhấn phím bất kỳ để tiếp tục")
show_welcome()
# Chào mừng tới Làng Khởi Đầu
# Nhấn phím bất kỳ để tiếp tục
def, tên hàm, cặp ngoặc, dấu :, rồi thân hàm thụt vào. Tên hàm đặt theo snake_case giống biến, thường bắt đầu bằng động từ: show_menu, calc_damage, spawn_enemy.
Định nghĩa hàm chưa chạy gì cả. Code bên trong chỉ chạy khi bạn gọi show_welcome().
Lỗi hay gặp: gọi hàm trước dòng
def. Python chạy từ trên xuống, chưa gặpdefthì chưa biết hàm đó tồn tại, báoNameError: name 'show_welcome' is not defined. Đặt các hàm ở đầu file.
Tham số
Tham số là giá trị bạn đưa vào hàm lúc gọi:
def heal(hp, amount):
print(f"Hồi {amount} máu, từ {hp} lên {hp + amount}")
heal(40, 25) # Hồi 25 máu, từ 40 lên 65
heal(10, 50) # Hồi 50 máu, từ 10 lên 60
Gọi thiếu hay thừa tham số đều lỗi: heal(40) báo TypeError: heal() missing 1 required positional argument: 'amount'.
return
Chỗ người mới hay nhầm: print trong hàm chỉ hiện kết quả lên màn hình. Code gọi hàm không lấy được con số đó để tính tiếp.
def calc_damage(attack, defense):
print(attack - defense)
result = calc_damage(50, 20) # in ra 30
print(result) # None
Hàm không có return thì trả về None. Muốn lấy giá trị ra dùng tiếp, phải return:
def calc_damage(attack, defense):
damage = attack - defense
return max(damage, 1) # ít nhất 1 sát thương
boss_hp = 500
boss_hp -= calc_damage(50, 20)
boss_hp -= calc_damage(10, 99)
print(boss_hp) # 469
return kết thúc hàm ngay lập tức. Code sau return trong cùng nhánh không chạy. Tận dụng điều này để thoát sớm:
def buy(gold, price):
if gold < price:
return gold # không đủ tiền, giữ nguyên
return gold - price
print(buy(100, 250)) # 100
print(buy(300, 250)) # 50
Trả nhiều giá trị thì ngăn cách bằng dấu phẩy, Python gói thành tuple:
def split_loot(gold, players):
return gold // players, gold % players
each, left = split_loot(100, 3)
print(each, left) # 33 1
Tham số mặc định
Cho tham số một giá trị sẵn, người gọi không truyền thì dùng giá trị đó:
def spawn_enemy(name, hp=50, level=1):
return f"{name} (lv{level}, {hp} máu)"
print(spawn_enemy("Slime")) # Slime (lv1, 50 máu)
print(spawn_enemy("Orc", 120)) # Orc (lv1, 120 máu)
print(spawn_enemy("Orc", 120, 5)) # Orc (lv5, 120 máu)
Tham số có mặc định phải đứng sau tham số không có mặc định. def spawn_enemy(hp=50, name): báo SyntaxError: parameter without a default follows parameter with a default.
Lỗi hay gặp: dùng list làm giá trị mặc định, kiểu
def add_item(item, bag=[]). List này được tạo một lần lúc định nghĩa hàm và dùng chung cho mọi lần gọi, nên đồ của lần gọi trước còn nằm lại ở lần gọi sau. Dùngbag=Nonerồi trong hàm viếtif bag is None: bag = [].
Keyword arguments
Gọi hàm bằng tên tham số thay vì thứ tự. Cách này rõ nghĩa hơn khi hàm có nhiều tham số:
print(spawn_enemy("Goblin", level=3)) # Goblin (lv3, 50 máu)
print(spawn_enemy(level=2, hp=80, name="Bat")) # Bat (lv2, 80 máu)
Dòng đầu bỏ qua hp để nó giữ mặc định, chỉ đổi level. Không có keyword arguments thì phải truyền cả hp dù không muốn đổi.
Trộn hai kiểu được, nhưng tham số theo vị trí phải đứng trước: spawn_enemy(name="Bat", 80) báo SyntaxError.
Biến trong hàm
Biến tạo bên trong hàm chỉ sống trong hàm đó:
def roll_crit():
crit_bonus = 2
return crit_bonus
roll_crit()
print(crit_bonus)
# NameError: name 'crit_bonus' is not defined
Muốn lấy giá trị ra ngoài thì dùng return, đừng cố đọc biến bên trong hàm. Hàm nhận dữ liệu qua tham số và trả kết quả qua return thì dễ hiểu và dễ kiểm tra hơn nhiều.
Bài tiếp theo: hàm viết trên một dòng với lambda.
Bài tập
Viết hàm calc_reward(level, is_boss=False) trả về số vàng: level * 10, nếu là boss thì nhân 5. Gọi cho quái level 3, và boss level 4 bằng keyword argument.
Xem đáp án
def calc_reward(level, is_boss=False):
gold = level * 10
if is_boss:
gold *= 5
return gold
print(calc_reward(3)) # 30
print(calc_reward(4, is_boss=True)) # 200