Trong chương 18, “Hàm Python”, chúng ta đã học được rằng một hàm chỉ nên sử dụng các biến đã được xác định trong khối của hàm hoặc được truyền rõ ràng dưới dạng tham số [Quy tắc 1]. Quy tắc này hoạt động chặt chẽ với định nghĩa của chúng tôi về một biến trong Python, như một cái tên đề cập đến một số dữ liệu
Đây là một khái niệm mạnh mẽ, cho phép chúng ta truyền tham số cho hàm và nhận giá trị trả về. Trong Python, một phần dữ liệu có thể được tham chiếu bởi nhiều biến. Các biến tham số là tên mới cho cùng một dữ liệu. Chúng ta có thể khám phá khái niệm này bằng một ví dụ đơn giản
Trong mẫu mã này, chúng tôi đã tạo một danh sách duy nhất, nhưng chúng tôi đang liên kết hai tên biến với nó, nums
và numsb
. Khi chúng tôi sửa đổi danh sách thông qua một tên, thay đổi được phản ánh qua tên kia [chúng tôi chỉ có thể thay đổi dữ liệu vì danh sách có thể thay đổi, nhưng ngay cả dữ liệu không thay đổi cũng có thể được gọi bằng nhiều tên]
Điều tương tự cũng xảy ra khi chúng ta sử dụng một tham số trong một hàm. Hãy xem lại hàm gc_content[]
, sử dụng hàm base_composition[]
mà chúng ta đã viết [nhưng bỏ qua ở đây cho rõ ràng]. Trong danh sách này, tất cả các tên biến thuộc về hàm được đánh dấu bằng màu đỏ, trong khi các tên biến “toàn cầu” khác được đánh dấu bằng màu tím và được gạch chân bằng đường đứt nét. Theo Quy tắc 1, chúng ta sẽ không thấy bất kỳ biến màu tím/gạch chân nào trong định nghĩa hàm
Bên ngoài hàm, chuỗi dữ liệu "ACCCTAGACTG"
được gán cho biến seq3
, nhưng bên trong hàm, nó được gán cho biến seq
. Bên trong hàm, giá trị dữ liệu 0.54
được tham chiếu bởi biến gc
và bên ngoài nó được tham chiếu bởi seq3_gc
Chúng tôi biết rằng chúng tôi có thể truy cập biến seq3
từ bên trong hàm, ngay cả khi chúng tôi không nên. Nhưng còn chiều ngược lại thì sao?
Trên thực tế, chúng ta không thể. Dòng numsb
2 ở trên, ngay cả sau khi tính toán seq3_gc
, sẽ tạo ra một numsb
4. Lỗi này xảy ra do các biến có phạm vi và phạm vi của biến gc
bị giới hạn, từ thời điểm nó được xác định cho đến khi kết thúc khối chức năng mà nó được xác định [tất cả các biến được đánh dấu màu đỏ đều có đặc điểm này]. Các biến có phạm vi giới hạn được gọi là biến cục bộ. Phạm vi của một biến là bối cảnh trong đó có thể được sử dụng. Nó định nghĩa “biến tồn tại bao lâu” hoặc “biến tồn tại ở đâu. ” Một biến có phạm vi giới hạn, như trong quá trình thực thi một hàm, được gọi là biến cục bộ
Sau khi lệnh gọi hàm kết thúc, hình ảnh sẽ giống như vậy
Cuối cùng, bộ thu gom rác sẽ nhận thấy rằng không có biến nào tham chiếu đến dữ liệu numsb
6, numsb
7 và numsb
8 và nó sẽ xóa những biến đó khỏi RAM để nhường chỗ cho dữ liệu mới
Bởi vì phạm vi nói về các biến, nó ít liên quan đến dữ liệu mà một biến đề cập đến—ngoại trừ việc nói rằng dữ liệu được thu thập rác khi không có biến nào đề cập đến dữ liệu đó trong bất kỳ phạm vi hiện tại nào. Tuy nhiên, vì biến gc
không nằm trong phạm vi sau khi chức năng kết thúc, nên chúng tôi không thể sử dụng nó bên ngoài khối chức năng
Một trong những tính năng đặc biệt thú vị và mạnh mẽ của các biến cục bộ là chúng tồn tại độc lập với bất kỳ biến nào khác có thể tồn tại cùng tên vào thời điểm đó [đôi khi chúng được cho là “che khuất” các biến khác có cùng tên]. Hãy xem xét đoạn mã sau
Bạn nghĩ dòng cuối cùng, numsb
2, sẽ in gì? . Mặc dù hàm gc_content[]
định nghĩa biến gc
của chính nó, nhưng vì biến này là cục bộ nên nó không “đè” lên biến gc
bên ngoài. Tóm tắt
- Các biến được xác định trong một hàm [và các tham số cho một hàm] là cục bộ của lệnh gọi hàm
- Các biến cục bộ này “bóng tối” bất kỳ biến nào khác có thể tồn tại trong quá trình thực thi hàm
- Khi lời gọi hàm kết thúc, các biến cục bộ sẽ bị quên
Một trong những bài học quan trọng về phạm vi là biết khi nào nó có thể dẫn đến rắc rối. Điều này dễ thấy nhất nếu chúng ta so sánh cách phạm vi hoạt động với một ngôn ngữ khác, trong trường hợp này là C++. Trong C++, các biến được khai báo với kiểu của chúng và các biến được khai báo trong khối câu lệnh if [hoặc bất kỳ khối nào khác] cũng là cục bộ của khối đó. Điều này có nghĩa là chúng vượt ra ngoài phạm vi khi khối kết thúc. Nếu chúng ta thử và sử dụng chúng bên ngoài khối mà chúng được khai báo, sẽ xảy ra lỗi
Mặt khác, trong Python, các biến được khai báo trong câu lệnh if, khối vòng lặp for và khối vòng lặp while không phải là biến cục bộ và nằm trong phạm vi bên ngoài khối. Do đó, chúng tôi nói rằng C ++ có phạm vi "cấp khối", trong khi Python chỉ sử dụng phạm vi "cấp chức năng". Các dấu ngoặc trong hình bên dưới minh họa phạm vi cho một số biến trong mã C++ và Python tương đương. Vì C++ sử dụng phạm vi cấp khối và gc_content[]
5 và gc_content[]
6 được khai báo bên trong các khối, chúng tôi không thể truy cập nội dung của chúng bên ngoài các khối đó
Điều này có vẻ như là một lợi thế cho Python, nhưng trong trường hợp này, chúng tôi nghĩ rằng C ++ có thể đúng. Xét cho cùng, nếu chúng ta cần các biến gc_content[]
6 và gc_content[]
8 có thể truy cập được sau các khối if trong ví dụ C++, chúng ta có thể khai báo chúng cùng với gc_content[]
5. Chúng tôi thậm chí có thể đã cung cấp cho chúng một giá trị mặc định để giữ bằng cách sử dụng các khai báo như base_composition[]
0 và base_composition[]
1; . Điều này sẽ đặc biệt hữu ích nếu giá trị của gc_content[]
5 không được đặt thành 5 như ở đây, mà phụ thuộc vào dữ liệu đầu vào
Mặt khác, trong Python, mã như thế này có thể gây ra sự cố. Nếu biến gc_content[]
5 phụ thuộc vào dữ liệu đầu vào, thì các biến gc_content[]
6 và gc_content[]
8 có thể không bao giờ được đặt, dẫn đến một biến base_composition[]
7 cuối cùng khi chúng được truy cập sau đó. Chúng tôi chắc chắn không nên coi bất kỳ chương trình nào đôi khi tạo ra lỗi, tùy thuộc vào dữ liệu đầu vào, là rất tốt
Có lẽ cách tốt nhất để giải quyết loại vấn đề này là giả sử rằng Python sử dụng phạm vi cấp độ khối, khai báo các biến trong khối/cấp độ rộng nhất mà chúng sẽ được sử dụng. base_composition[]
8 là một giá trị mặc định tốt mà sau này chúng ta có thể kiểm tra nếu chúng ta muốn thực thi mã sau này một cách có điều kiện trên cơ sở liệu các biến có thực sự được đặt thành thứ gì đó hữu ích hay không
Mã này sẽ không bị lỗi, bất kể giá trị của gc_content[]
5 là bao nhiêu. Việc đặt các giá trị ban đầu cho các biến này thành base_composition[]
8 trước các khối if cũng cung cấp lời nhắc và gợi ý trực quan rằng các biến này dự định sẽ được sử dụng bên ngoài các khối if lồng nhau và chúng ta nên kiểm tra nội dung của chúng trước khi sử dụng chúng sau này.
Do đó, C ++ và các ngôn ngữ có phạm vi cấp khối khác khuyến khích các biến "tồn tại trong thời gian ngắn", đây là một câu thần chú lập trình tốt. Chỉ xác định các biến khi cần thiết và sử dụng chúng trong thời gian ngắn nhất có thể giúp tạo ra mã rõ ràng hơn, mô-đun hơn và thậm chí hiệu quả hơn [vì các biến tồn tại trong thời gian ngắn cho phép trình thu gom rác dọn sạch nhiều dữ liệu hơn]. Theo một cách nào đó, việc phá vỡ Quy tắc 1 của các hàm là một kiểu lạm dụng phạm vi biến tương tự
Các lập trình viên mới bắt đầu thường thấy dễ dàng hơn để tránh các quy ước này bằng cách đặt một số lượng lớn các biến gần đầu chương trình, sau đó truy cập và đặt chúng ở các điểm khác nhau trong suốt chương trình. Điều này đôi khi được gọi một cách thông tục là “mã spaghetti”, bởi vì các kết nối khoảng cách xa được tạo giữa các vùng mã khác nhau giống như mì spaghetti. Tuy nhiên, hiếm khi chiến lược này được đền đáp
Như đã thảo luận trong chương 25, “Thuật toán và cấu trúc dữ liệu”, các biến cục bộ và các khái niệm tương tự cho phép chúng ta giải quyết các vấn đề phức tạp theo những cách hấp dẫn và tao nhã