Python có 2 loại variable (biến): local, global, và free (đếm từ 0, tất nhiên).
binding
x = 42
trong Python đọc là bind name x tới object 42.
Tham khảo thêm tại bài Python call by gì?
3 loại variable trong Python
global variable
Đoạn code sau
print(x)
x = 42
x
viết sát lề, gọi là global variable. Chạy đoạn code trên sẽ hiện ra
exception:
NameError: name 'x' is not defined
do code dùng x trước khi x được bind tới object 42.
local variable
Đoạn code tiếp theo, chạy sẽ thấy gì? Gợi ý: không phải NameError:
def foo():
print(x)
x = 42
foo()
x = 42
nằm trong 1 block (trong thân function hay class), gọi là local variable.
Trong 1 block, dùng 1 variable/name trước khi bind nó (tức là có bind, nhưng
bind sau khi dùng), exception sẽ xảy ra là
UnboundLocalError: local variable 'x' referenced before assignment
free variable
def foo():
print(x)
foo()
Xóa x = 42
trong ví dụ phần local, ta chạy đoạn code này, lại thấy NameError.
NameError: name 'x' is not defined
Lần này không xảy ra UnboundLocalError, do đoạn code không bind x = 42
trong thân function (block). x
ở đây là một free variable.
Free variable hoạt động theo cách ... rất tự do. Khi không tìm thấy x trong foo, Python sẽ đi tìm x ở global (bên ngoài function foo).
x = 42
def foo():
print(x)
x = 96
foo()
x = 100
Màn hình sẽ hiện ra 42
, 100
hay 96
?
Việc tính toán tên x
có giá trị gì, được thực hiện khi function CHẠY.
Khi gọi foo()
ở trên, x đã có giá trị là 96.
Static analysis
Việc dùng các công cụ (phần mềm/chương trình) để phân tích/tìm lỗi code bằng
cách đọc code (text) - mà không cần chạy code, gọi là static analysis.
Trong Python phổ biến các công cụ như pep8
, flake8
, pylint
, mypy
cũng
có thể tính luôn, hay các tính năng
tích hợp sẵn của IDE như Pycharm.
flake8
cơ bản giống pep8
(tên mới là pycodestyle
), thêm tính năng phát
hiện thư viện không dùng tới/ hay biến không tồn tại.
import math
def foo():
print(x)
x = 42
foo()
khi chạy flake8
với file code, flake8
sẽ phát hiện ra thư viện
math
được import nhưng không dùng, tên x
chưa được định nghĩa.
$ flake8 scope.py
scope.py:1:1: F401 'math' imported but unused
scope.py:5:11: F821 undefined name 'x'
scope.py:6:5: F841 local variable 'x' is assigned to but never used
flake8
rất hữu ích khi dễ dàng phát hiện các lỗi đơn giản như gõ nhầm tên
biến hay dùng biến không tồn tại như trên.
Nhưng khi x
là free variable, không công cụ nào của Python có thể phát hiện
ra lỗi này cho tới khi chạy mới thấy exception:
def n_pymi_vn() -> int:
s = x + 1
return s
r = n_pymi_vn()
x = 10
print(r)
Do x
là free variable, các công cụ phải quét hết cả file code để tìm x
,
và nó tìm thấy, nên tin rằng x
có tồn tại, nhưng đã quá muộn rồi.
Một ví dụ vô lý hơn nữa, để thấy sự bất lực của các công cụ static analysis:
def n_pymi_vn() -> int:
s = x + 1
return s
if 1 > 10:
x = 10
r = n_pymi_vn()
print(r)
Hành động của chúng ta
Hạn chế hết mức việc sử dụng global variable, free variable, cần biến gì thì đưa argument vào function biến đó, code "sát tường" cho hết vào 1 function main và gọi main() nếu cần chạy.
def n_pymi_vn() -> int:
s = x + 1
return s
def main():
r = n_pymi_vn()
x = 10
print(r)
main()
flake8
sẽ làm tốt nhiệm vụ phát hiện ra x
chưa được định nghĩa trong ví dụ trên:
F821 undefined name 'x'
, và đoạn code này có thể viết lại để không
còn dùng global/free variable nữa:
def n_pymi_vn(x) -> int:
s = x + 1
return s
def main():
x = 10
r = n_pymi_vn(x)
print(r)
main()
Kết luận
Tính dynamic của Python khiến các công cụ khó có thể xử lý mọi trường hợp, công cụ sẽ chỉ giúp một phần, phần còn lại là sự cẩn thận của lập trình viên.
Tự do phải chăng cần trong khuôn khổ?
Comments