Lộ mật khẩu cùng dataclass
by Pymier0
dataclasses
module là 1 standard library rất mới, xuất hiện ở Python 3.7, với tác dụng:
This module provides a decorator and functions for automatically adding generated special methods such as
__init__()
and__repr__()
to user-defined classes.
https://peps.python.org/pep-0557/
đây là một tính năng được yêu thích ở các bản Python mới (3.7+).
dataclass giúp viết class ngắn hơn, không phải tự gõ __init__
dataclass
là một decorator, dùng trên đầu class:
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
email: str
u = User("Pymier", 8, "[email protected]")
print(u)
# User(name='Pymier', age=8, email='[email protected]')
Tiện vậy có gì mà không tốt?
dataclass luôn in ra mọi attribute
Mặc định, khi print một object tạo bởi class dùng dataclass, nó sẽ in ra mọi attribute. Giả sử một ngày, cần chứa mật khẩu trong class User, đoạn code trở thành:
@dataclass
class User:
name: str
age: int
email: str
password: str
u = User("Pymier", 8, "[email protected]", "hunter42")
print(u)
# User(name='Pymier', age=8, email='[email protected]', password='hunter42')
Vậy là nhờ dùng dataclass, ta VÔ TÌNH để lộ mật khẩu, in ra màn hình, render ra website hay theo log file lộ ra ngoài…
Sửa lại thế nào?
muốn print object ra gì, rõ là phải sửa __str__
!, thêm method __str__
vào
...
def __str__(self) -> str:
return f"{self.name=} {self.age=} {self.email=}"
u = User("Pymier", 8, "[email protected]", "hunter42")
print(u)
# self.name='Pymier' self.age=8 self.email='[email protected]'
Kết quả giờ không còn hiện ra password nữa, ta lặng lẽ ỉm đi cái security bug này và không ai biết tới, thật tuyệt vời!
Xong chưa? nên dừng lại 1 chút suy nghĩ rồi hãy đọc tiếp.
Đoạn code ban đầu hoàn hảo, được thiết kế chủ ý dùng dataclass vì có thể in ra mọi thông tin, thì sau 1 thay đổi “nhỏ” và thường là do 1 người khác thay đổi, trở thành code có lỗ hổng bảo mật (security vulnerability). Người ta thường so sánh ngành lập trình với ngành xây dựng, software thì toàn bug còn nhà thì mấy khi “hỏng”? software bị thay đổi hàng ngày, còn nhà thì vài năm hay thập kỷ mới thay.
Tạo 1 class mới chứa dataclass này
Tạo 1 team class dùng dataclass, và in ra:
@dataclass
class Team:
members: list[User]
t = Team(members=[u])
print(t)
# Team(members=[User(name='Pymier', age=8, email='[email protected]', password='hunter42')])
dù ta đã viết __str__
cho User
, nhưng khi in ra Team
object, nó sử dụng __repr__
chứ không dùng __str__
, như dòng đầu tiên trong tài liệu của dataclasses
đã viết:
automatically adding generated special methods such as
__init__()
and__repr__()
to user-defined classes
Cách fix: thêm __repr__
:
If a class defines
__repr__()
but not__str__()
, then__repr__()
is also used when an “informal” string representation of instances of that class is required.
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
email: str
password: str
def __repr__(self) -> str:
return f"{self.name=} {self.age=} {self.email=}"
u = User("Pymier", 8, "[email protected]", "hunter42")
print(u)
# self.name='Pymier' self.age=8 self.email='[email protected]'
@dataclass
class Team:
members: list[User]
t = Team(members=[u])
print(t)
# Team(members=[self.name='Pymier' self.age=8 self.email='[email protected]'])
Bạn đọc tham khảo sự khác biệt giữa __str__
và __repr__
: https://stackoverflow.com/questions/1436703/what-is-the-difference-between-str-and-repr
Bất ngờ thay, mấy ông già không “cool” không “trend” không dùng dataclass
không gặp phải vấn đề này:
from dataclasses import dataclass
class User:
def __init__(self, name: str, age: int, email: str, password: str):
self.name = name
self.age = age
self.email = email
self.password = password
u = User("Pymier", 8, "[email protected]", "hunter42")
print(u, repr(u))
# <__main__.User object at 0x7f9efd510410> <__main__.User object at 0x7f9efd510410>
Update: dataclasses cũng đã tính đến chuyện này, để KHÔNG hiện một attribute khi __repr__
, dùng field(repr=False)
from dataclasses import dataclass, field
@dataclass
class User:
name: str
age: int
email: str
password: str = field(repr=False)
u = User("Pymier", 8, "[email protected]", "hunter42")
print(u)
# User(name='Pymier', age=8, email='[email protected]')
Tham khảo
https://docs.python.org/3.11/library/dataclasses.html
Xem list các method được dataclass autogenerate tại https://peps.python.org/pep-0557/#abstract
Kết luận
dataclasses
tiện lợi, giúp viết ít code hơn, nhưng cần biết những gì nó “tự động” để tránh bất ngờ.
Default không có nghĩa là không biết. Explicit is better than implicit.
Hết.
Đăng ký ngay tại PyMI.vn để học Python tại Hà Nội TP HCM (Sài Gòn), trở thành lập trình viên #python chuyên nghiệp ngay sau khóa học.