Class là bản thiết kế, object là thứ được tạo ra từ bản thiết kế đó. Class Enemy mô tả một con quái có tên, máu, biết nhận sát thương. Mỗi con Slime, Goblin, Orc trong màn chơi là một object riêng, có máu riêng, bị đánh riêng.
Vì sao cần class
Không có class, dữ liệu một con quái nằm rải rác trong nhiều biến, còn các hàm xử lý nó thì nằm chỗ khác:
slime_name = "Slime"
slime_hp = 30
goblin_name = "Goblin"
goblin_hp = 50
def take_damage(hp, amount):
return hp - amount
slime_hp = take_damage(slime_hp, 10)
Thêm con thứ mười thì phải thêm hai mươi biến, rất dễ truyền nhầm máu con này vào con kia. Class gói dữ liệu và hành động của một con quái vào một chỗ.
Tạo class và object
class Enemy:
def __init__(self, name, hp):
self.name = name
self.hp = hp
slime = Enemy("Slime", 30)
goblin = Enemy("Goblin", 50)
print(slime.name, slime.hp) # Slime 30
print(goblin.name, goblin.hp) # Goblin 50
class Enemy:định nghĩa class. Tên class viếtPascalCase(viết hoa chữ đầu mỗi từ), khác vớisnake_casecủa biến.Enemy("Slime", 30)tạo một object mới. Gọi tên class như gọi hàm.slime.nameđọc thuộc tính (dữ liệu gắn với object) bằng dấu chấm.
init và self
__init__ là hàm khởi tạo, Python tự gọi nó mỗi lần bạn tạo object. Đây là chỗ gán giá trị ban đầu cho object.
self là chính object đang được tạo hoặc đang được dùng. Khi viết Enemy("Slime", 30), Python tạo một object trống, rồi gọi __init__ với self là object đó, name là "Slime", hp là 30. Dòng self.hp = hp gắn giá trị 30 vào object.
Lỗi hay gặp: quên
self.khi gán. Viếthp = hptrong__init__chỉ tạo biến tạm trong hàm, object không có thuộc tínhhp. Truy cậpslime.hpsẽ báoAttributeError: 'Enemy' object has no attribute 'hp'.
Tên __init__ có hai dấu gạch dưới mỗi bên. Viết _init_ hay __int__ thì Python không coi là hàm khởi tạo, và Enemy("Slime", 30) sẽ báo TypeError: Enemy() takes no arguments.
Method
Method là hàm viết bên trong class. Tham số đầu tiên luôn là self, để method biết đang làm việc với object nào.
class Enemy:
def __init__(self, name, hp, gold=5):
self.name = name
self.hp = hp
self.gold = gold
def take_damage(self, amount):
self.hp -= amount
if self.hp < 0:
self.hp = 0
def is_alive(self):
return self.hp > 0
def describe(self):
return f"{self.name} ({self.hp} máu)"
slime = Enemy("Slime", 30)
goblin = Enemy("Goblin", 50, gold=12)
slime.take_damage(12)
print(slime.describe()) # Slime (18 máu)
print(goblin.describe()) # Goblin (50 máu)
slime.take_damage(40)
print(slime.is_alive()) # False
print(slime.hp) # 0
Khi gọi slime.take_damage(12), bạn chỉ truyền 12. Python tự đưa slime vào làm self. Vì thế self.hp -= amount trừ máu đúng con slime, con goblin không bị ảnh hưởng.
Người mới hay quên self ở danh sách tham số:
class Enemy:
def roar():
print("Grừ!")
Enemy().roar()
# TypeError: Enemy.roar() takes 0 positional arguments but 1 was given
"1 was given" chính là self mà Python tự truyền vào. Thêm self vào: def roar(self):.
Nhiều object trong list
Class kết hợp với list để quản lý cả đợt quái:
wave = [Enemy("Slime", 30), Enemy("Bat", 15), Enemy("Orc", 120, gold=40)]
for enemy in wave:
enemy.take_damage(20)
survivors = []
for enemy in wave:
if enemy.is_alive():
survivors.append(enemy.describe())
print(survivors) # ['Slime (10 máu)', 'Orc (100 máu)']
Mỗi object giữ máu riêng, vòng lặp chỉ cần gọi method, không phải nhớ biến nào của con nào.
In object cho dễ đọc
print(slime) mặc định in ra thứ khó hiểu như <__main__.Enemy object at 0x...>. Thêm method __str__ để quyết định object in ra thế nào:
class Item:
def __init__(self, name, price):
self.name = name
self.price = price
def __str__(self):
return f"{self.name} ({self.price} vàng)"
sword = Item("Kiếm sắt", 250)
print(sword) # Kiếm sắt (250 vàng)
Bài tiếp theo: tạo class mới từ class có sẵn với kế thừa.
Bài tập
Viết class Player có name, hp (mặc định 100) và gold (mặc định 0). Thêm method loot(enemy) cộng vàng của quái vào túi người chơi nếu quái đã chết. Dùng lại class Enemy ở trên để thử.
Xem đáp án
class Enemy:
def __init__(self, name, hp, gold=5):
self.name = name
self.hp = hp
self.gold = gold
def take_damage(self, amount):
self.hp = max(self.hp - amount, 0)
def is_alive(self):
return self.hp > 0
class Player:
def __init__(self, name, hp=100, gold=0):
self.name = name
self.hp = hp
self.gold = gold
def loot(self, enemy):
if not enemy.is_alive():
self.gold += enemy.gold
enemy.gold = 0
aki = Player("Aki")
orc = Enemy("Orc", 60, gold=40)
aki.loot(orc)
print(aki.gold) # 0, orc còn sống
orc.take_damage(60)
aki.loot(orc)
print(aki.gold) # 40
enemy.gold = 0 để không nhặt vàng của cùng một con hai lần.