2.34. Code động¶
Ba hàm built-in nhận một chuỗi mã nguồn Python và chạy nó: eval(), exec(), và compile(). Cùng nhau chúng cho phép code xây dựng và thực thi thêm code tại thời gian chạy -- điều này đôi khi là đúng công cụ, và thường xuyên hơn là nguồn gốc của lỗi và lỗ hổng bảo mật.
Cảnh báo
Các hàm này thực thi Python tùy ý. Truyền đầu vào của người dùng cho eval hoặc exec -- một chuỗi từ file mà người dùng có thể chỉnh sửa, payload nhận qua mạng, giá trị được gõ tại REPL -- cho phép đầu vào đó làm bất cứ điều gì mà tập lệnh gọi có thể làm, kể cả xóa mọi file trên thiết bị. Sử dụng chúng có chủ đích, không bao giờ bên trong code chạy tự động, và không bao giờ trên dữ liệu bạn không kiểm soát.
2.34.1. eval¶
eval() chạy một biểu thức đơn và trả về giá trị của nó:
>>> eval("3 * 7")
21
>>> name = "OpenMV"
>>> eval("name.lower()")
'openmv'
Biểu thức thấy globals và locals của caller theo mặc định, đó là lý do tại sao name được giải quyết trong ví dụ thứ hai. Truyền dictionary tường minh cho phép bạn sandbox việc đánh giá:
eval("a + b", {"__builtins__": None}, {"a": 1, "b": 2})
Ngay cả khi đã sandbox, eval vẫn nguy hiểm. Có những kỹ thuật nổi tiếng để thoát khỏi các sandbox như vậy; đừng chỉ dựa vào thủ thuật __builtins__ rỗng cho đầu vào không tin tưởng.
2.34.2. exec¶
exec() chạy một khối code thay vì một biểu thức đơn -- các câu lệnh, định nghĩa hàm, vòng lặp -- và trả về None:
exec("for i in range(3): print(i)")
Kết quả:
0
1
2
Khối có thể định nghĩa các tên trở nên có sẵn sau đó, với một số lưu ý về phạm vi cục bộ và toàn cục. exec bên trong một hàm hiếm khi hoạt động theo cách người viết kỳ vọng; nếu bạn cần nó, hãy chạy ở cấp module.
2.34.3. compile¶
compile() chuyển đổi một chuỗi nguồn thành một đối tượng code có thể được truyền cho eval() hoặc exec() sau đó. Dùng nó khi cùng nguồn sẽ chạy nhiều lần -- việc phân tích cú pháp xảy ra một lần, việc thực thi nhanh hơn:
expr = compile("x * x", "<expr>", "eval")
for x in range(5):
print(eval(expr))
Kết quả:
0
1
4
9
16
Đối số thứ hai là nhãn xuất hiện trong traceback nếu code raise lỗi. Đối số thứ ba là "eval" cho một biểu thức đơn, "exec" cho một khối, hoặc "single" cho câu lệnh kiểu tương tác in kết quả.
2.34.4. Khi nào nên dùng những hàm này¶
Hầu như không bao giờ. Hầu hết các trường hợp sử dụng mà người mới bắt đầu tưởng tượng đều có các lựa chọn an toàn hơn:
Đọc file cấu hình. Dùng
json-- dữ liệu có cấu trúc, không thực thi.Đánh giá một giá trị số được người dùng gõ. Dùng
int()/float()để phân tích, sau đó là phép tính. Nếu người dùng thực sự cần nhập công thức, hãy dùng trình phân tích biểu thức nhỏ, không phảieval.
Khi bạn thực sự cần eval / exec / compile, hãy cô lập call site, ghi lại chuỗi chính xác sắp được thực thi, và coi nguồn là thứ đáng ngờ nhất trong code của bạn.