Sử dụng biến toàn cục với từ khóa global trong Python

Python Tutorial | by Hoc Python

Khi lập trình Python, bạn sẽ thường xuyên làm việc với các hàm để tổ chức mã nguồn và thực hiện các tác vụ cụ thể. Một khái niệm quan trọng liên quan đến hàm là phạm vi của biến (variable scope), tức là nơi mà một biến có thể được truy cập và sử dụng. Thông thường, các biến được tạo bên trong một hàm chỉ có thể sử dụng trong hàm đó (biến cục bộ). Tuy nhiên, đôi khi, bạn cần truy cập hoặc thậm chí thay đổi một biến đã được khai báo ở bên ngoài tất cả các hàm – một biến toàn cục. Bài này sẽ giới thiệu biến toàn cục là gì, lý do tại sao việc thay đổi chúng từ bên trong hàm lại phức tạp, và cách từ khóa global xuất hiện như một "chìa khóa" giúp bạn vượt qua giới hạn đó để quản lý dữ liệu hiệu quả hơn trong chương trình của mình.

Biến Toàn Cục (Global Variables) là gì?

Trong lập trình Python, việc hiểu về phạm vi (scope) của biến là rất quan trọng để quản lý dữ liệu hiệu quả. Biến được chia thành hai loại chính dựa trên phạm vi của chúng: biến cục bộ và biến toàn cục. Phần này sẽ tập trung vào biến toàn cục.

Định nghĩa

  • Một biến toàn cục (global variable) là một biến được khai báo (tức là được tạo ra và gán giá trị) ở bên ngoài tất cả các hàm và lớp trong chương trình của bạn.

  • Khi một biến được khai báo ở cấp độ cao nhất của tập tin Python (gọi là cấp độ module), nó trở thành biến toàn cục.

  • Đặc điểm quan trọng nhất của biến toàn cục là nó có thể được truy cập (đọc giá trị) từ bất kỳ đâu trong chương trình, bao gồm cả bên trong các hàm hoặc các khối mã khác.

Phạm vi (Scope) của biến

Phạm vi của một biến xác định "khu vực" mà biến đó có thể được nhìn thấy và sử dụng trong code.

Biến toàn cục (Global Scope):

  • Biến được tạo ở cấp độ module (bên ngoài mọi hàm).

  • Có thể được truy cập (đọc) từ bất kỳ hàm nào, bất kỳ lớp nào, hoặc bất kỳ phần nào khác của chương trình. Chúng có "tuổi thọ" kéo dài trong suốt quá trình chạy của chương trình.

  • Hãy hình dung biến toàn cục như một bảng thông báo công cộng mà tất cả mọi người trong một tòa nhà đều có thể đọc được.

Biến cục bộ (Local Scope):

  • Biến được tạo ra bên trong một hàm hoặc một khối mã cụ thể (ví dụ: bên trong vòng lặp for, khối if).

  • Chỉ có thể được truy cập và sử dụng bên trong chính cái hàm hoặc khối mã đó. Khi hàm kết thúc thực thi, các biến cục bộ của nó thường sẽ bị hủy.

  • Biến cục bộ giống như một ghi chú riêng tư mà chỉ người trong một căn phòng cụ thể mới có thể đọc.

Ví dụ cơ bản: Tạo một biến toàn cục và in nó bên trong một hàm

Hãy xem một ví dụ đơn giản để thấy cách một biến toàn cục hoạt động:

# Đây là biến 'thong_bao' được khai báo ở cấp độ toàn cục (Global Scope)
thong_bao = "Chào mừng bạn đến với Python!"

def hien_thi_thong_bao():
    # Bên trong hàm, chúng ta có thể truy cập (đọc) giá trị của biến toàn cục 'thong_bao'
    print("Thông báo từ hàm:", thong_bao)

def them_phu_de():
    # Biến toàn cục 'thong_bao' cũng có thể được truy cập ở đây
    print("-----")
    print(thong_bao) # Đọc biến toàn cục
    print("-----")

# Gọi các hàm để xem chúng truy cập biến toàn cục như thế nào
print("Thông báo ngoài hàm:", thong_bao) # Truy cập biến toàn cục từ bên ngoài hàm
hien_thi_thong_bao()
them_phu_de()

# Bạn có thể thay đổi biến toàn cục từ bên ngoài hàm
thong_bao = "Python thật tuyệt vời!"
print("Thông báo sau khi thay đổi ngoài hàm:", thong_bao)
hien_thi_thong_bao() # Hàm sẽ in ra giá trị mới của biến toàn cục

Giải thích:

  • Biến thong_bao được tạo ở dòng đầu tiên, bên ngoài mọi hàm, do đó nó là một biến toàn cục.

  • Khi hàm hien_thi_thong_bao()them_phu_de() được gọi, chúng có thể dễ dàng truy cập và in giá trị của thong_bao mà không cần phải truyền nó như một tham số.

  • Bất kỳ thay đổi nào đối với thong_bao ở cấp độ toàn cục (như dòng thong_bao = "Python thật tuyệt vời!") sẽ được phản ánh khi biến đó được truy cập từ bất kỳ đâu trong chương trình.

Tuy nhiên, có một điểm cần lưu ý: mặc dù bạn có thể đọc biến toàn cục từ bên trong một hàm, việc thay đổi giá trị của nó từ bên trong hàm lại đòi hỏi một cách tiếp cận đặc biệt với từ khóa global. Chúng ta sẽ tìm hiểu điều đó ở phần tiếp theo.

Vấn đề: Không Thể Thay Đổi Biến Toàn Cục Trực Tiếp trong Hàm trong Python

Bạn đã biết rằng một biến toàn cục có thể được đọc từ bất kỳ đâu trong chương trình, kể cả bên trong một hàm. Tuy nhiên, một điều bất ngờ đối với nhiều người mới học là bạn không thể trực tiếp thay đổi giá trị của biến toàn cục từ bên trong một hàm theo cách thông thường.

Python Tạo Biến Cục Bộ Mới Nếu Gán Trong Hàm

Đây là một quy tắc quan trọng trong Python liên quan đến phạm vi của biến (variable scope):

  • Khi bạn gán một giá trị mới cho một biến bên trong một hàm, Python sẽ tự động coi đó là việc bạn đang tạo ra một biến cục bộ mới với tên đó, ngay cả khi đã có một biến toàn cục cùng tên bên ngoài.

  • Biến cục bộ này chỉ tồn tại trong phạm vi của hàm đó và sẽ bị hủy sau khi hàm kết thúc.

  • Kết quả là, biến toàn cục bên ngoài sẽ không bị ảnh hưởng bởi bất kỳ sự thay đổi nào bên trong hàm. Nó vẫn giữ nguyên giá trị ban đầu của mình.

Hãy hình dung thế này: Biến toàn cục là một cuốn sổ ghi chép chung ở phòng khách. Khi bạn vào phòng ngủ (hàm) và bắt đầu ghi chép vào một cuốn sổ mới có cùng tên, bạn không hề động đến cuốn sổ ở phòng khách. Cuốn sổ mới này chỉ có ý nghĩa trong phòng ngủ của bạn mà thôi.

Ví dụ Minh Họa

Để thấy rõ điều này, hãy cùng xem một ví dụ cụ thể:

# 1. Tạo biến toàn cục
count = 0 # Biến 'count' được khai báo ở cấp độ toàn cục

def increase_count():
    # 2. Trong hàm, gán giá trị mới cho biến cùng tên
    # Python hiểu đây là việc TẠO MỚI một biến cục bộ 'count' trong hàm này
    count = 1 # Dòng này tạo ra một biến cục bộ mới tên là 'count'
    print(f"Bên trong hàm: Giá trị của 'count' cục bộ là: {count}")

def display_global_count():
    # Hàm này chỉ ĐỌC biến toàn cục
    print(f"Trong hàm display_global_count: Giá trị của 'count' toàn cục là: {count}")

# In biến bên ngoài hàm (trước khi gọi hàm thay đổi)
print(f"Ngoài hàm (ban đầu): Giá trị của 'count' toàn cục là: {count}") # Output: Ngoài hàm (ban đầu): Giá trị của 'count' toàn cục là: 0

# Gọi hàm mà chúng ta nghĩ là sẽ thay đổi biến toàn cục
increase_count()
# Output: Bên trong hàm: Giá trị của 'count' cục bộ là: 1

# In biến bên ngoài hàm (sau khi gọi hàm thay đổi)
# Bạn sẽ thấy biến toàn cục không thay đổi!
print(f"Ngoài hàm (sau khi gọi increase_count): Giá trị của 'count' toàn cục là: {count}") # Output: Ngoài hàm (sau khi gọi increase_count): Giá trị của 'count' toàn cục là: 0

display_global_count()
# Output: Trong hàm display_global_count: Giá trị của 'count' toàn cục là: 0

Giải thích kết quả:

  • Ban đầu, biến toàn cục count có giá trị là 0.

  • Khi increase_count() được gọi, dòng count = 1 bên trong hàm tạo ra một biến count MỚI, cục bộ chỉ tồn tại trong hàm đó. Biến cục bộ này có giá trị là 1.

  • Vì vậy, print() bên trong increase_count() hiển thị 1.

  • Tuy nhiên, khi chúng ta in count bên ngoài hàm sau khi increase_count() đã chạy, nó vẫn hiển thị 0. Điều này chứng tỏ biến toàn cục count hoàn toàn không bị ảnh hưởng.

Vấn đề này đặt ra câu hỏi: Nếu chúng ta thực sự muốn thay đổi biến toàn cục từ bên trong một hàm, phải làm thế nào? Câu trả lời nằm ở từ khóa global, mà chúng ta sẽ tìm hiểu ở phần tiếp theo.

Giải Pháp: Từ Khóa global trong Python

Như đã thấy ở phần trước, Python có một cơ chế bảo vệ mặc định: nếu bạn gán một biến bên trong hàm mà không làm gì thêm, Python sẽ tạo một biến cục bộ mới. Để khắc phục điều này và thực sự thay đổi một biến toàn cục từ bên trong một hàm, bạn cần sử dụng từ khóa global.

Mục đích của global

Từ khóa global là một chỉ dẫn rõ ràng cho trình thông dịch Python. Nó có hai mục đích chính:

  • Cho Python biết rằng bạn muốn làm việc với biến toàn cục có sẵn: Thay vì tạo ra một biến cục bộ mới cùng tên, global báo hiệu rằng bạn đang tham chiếu đến một biến đã tồn tại ở phạm vi toàn cục.

  • Cho phép bạn đọc và thay đổi giá trị của biến toàn cục từ bên trong một hàm: Khi bạn đã khai báo một biến là global bên trong hàm, bạn có toàn quyền truy cập để đọc và cập nhật giá trị của biến toàn cục đó.

Hãy nghĩ về global như việc bạn "yêu cầu quyền truy cập đặc biệt" vào cuốn sổ ghi chép chung ở phòng khách (biến toàn cục) khi bạn đang ở trong phòng ngủ (hàm). Sau khi được cấp quyền, bạn có thể thoải mái đọc và ghi vào cuốn sổ đó.

Cú pháp

Cú pháp của từ khóa global rất đơn giản:

global ten_bien_toan_cuc
  • Vị trí quan trọng: Bạn phải khai báo global ten_bien_toan_cuc trước khi bạn sử dụng hoặc gán lại biến đó trong hàm. Thường thì nó sẽ được đặt ở đầu hàm, trước dòng code đầu tiên sử dụng hoặc sửa đổi biến toàn cục đó.

Ví dụ minh họa

Hãy sử dụng ví dụ từ phần trước để thấy cách global thay đổi hành vi của chương trình:

# 1. Tạo biến toàn cục
dem_so_lan_truy_cap = 0 # Biến toàn cục để đếm số lần hàm được gọi

def tang_dem_so():
    # 2. Sử dụng từ khóa 'global' để chỉ rõ rằng chúng ta muốn làm việc với biến toàn cục 'dem_so_lan_truy_cap'
    global dem_so_lan_truy_cap # Dòng này yêu cầu quyền truy cập vào biến toàn cục

    # Bây giờ, khi chúng ta gán giá trị mới cho 'dem_so_lan_truy_cap',
    # chúng ta đang THAY ĐỔI biến toàn cục, không phải tạo biến cục bộ mới.
    dem_so_lan_truy_cap += 1 # Tăng giá trị của biến toàn cục lên 1
    print(f"Bên trong hàm: Biến toàn cục 'dem_so_lan_truy_cap' là: {dem_so_lan_truy_cap}")

# In biến toàn cục ban đầu
print(f"Ngoài hàm (ban đầu): Biến toàn cục 'dem_so_lan_truy_cap' là: {dem_so_lan_truy_cap}") # Output: Ngoài hàm (ban đầu): Biến toàn cục 'dem_so_lan_truy_cap' là: 0

# Gọi hàm để thay đổi biến toàn cục
tang_dem_so()
# Output: Bên trong hàm: Biến toàn cục 'dem_so_lan_truy_cap' là: 1

tang_dem_so() # Gọi lại hàm để thấy sự thay đổi tiếp tục
# Output: Bên trong hàm: Biến toàn cục 'dem_so_lan_truy_cap' là: 2

# In biến bên ngoài hàm sau khi gọi hàm
print(f"Ngoài hàm (sau khi gọi hàm 2 lần): Biến toàn cục 'dem_so_lan_truy_cap' là: {dem_so_lan_truy_cap}") # Output: Ngoài hàm (sau khi gọi hàm 2 lần): Biến toàn cục 'dem_so_lan_truy_cap' là: 2

Giải thích kết quả:

  • Ban đầu, biến toàn cục dem_so_lan_truy_cap có giá trị 0.

  • Khi hàm tang_dem_so() được gọi, dòng global dem_so_lan_truy_cap báo cho Python biết rằng dem_so_lan_truy_cap mà chúng ta đang thao tác ở đây là chính là biến toàn cục bên ngoài, không phải một biến cục bộ mới.

  • Do đó, khi dem_so_lan_truy_cap += 1 được thực thi, nó trực tiếp tăng giá trị của biến toàn cục.

  • Bạn có thể thấy rõ điều này khi in biến dem_so_lan_truy_cap bên ngoài hàm; giá trị của nó đã thay đổi thành 2 sau hai lần gọi hàm.

Từ khóa global là một công cụ mạnh mẽ để quản lý các biến có phạm vi toàn cầu, nhưng nó cũng đi kèm với những cân nhắc riêng về mặt thiết kế chương trình.

Khi nào nên và không nên sử dụng global?

Mặc dù từ khóa global cung cấp khả năng mạnh mẽ để thay đổi biến toàn cục từ bên trong hàm, nhưng nó cũng là một công cụ cần được sử dụng cẩn thận. Việc lạm dụng global có thể dẫn đến mã nguồn khó hiểu, khó bảo trì và dễ phát sinh lỗi.

Khi nào nên dùng global

Có một số trường hợp cụ thể mà việc sử dụng biến toàn cục và từ khóa global có thể hợp lý và hiệu quả:

  • Khi bạn cần chia sẻ một giá trị duy nhất, không đổi (hoặc ít thay đổi) giữa nhiều phần của chương trình (Ví dụ: Cấu hình, hằng số): Nếu bạn có một giá trị cấu hình (ví dụ: tên cơ sở dữ liệu, kích thước bộ đệm tối đa, chế độ debug) mà nhiều hàm cần truy cập và đôi khi cần cập nhật, biến toàn cục có thể tiện lợi.

# Ví dụ: Biến cấu hình
CHE_DO_DEBUG = False # Biến toàn cục để kiểm soát chế độ debug

def ghi_log(thong_diep):
    if CHE_DO_DEBUG: # Truy cập biến toàn cục để kiểm tra điều kiện
        print(f"[DEBUG] {thong_diep}")
    else:
        print(f"[INFO] {thong_diep}")

def bat_che_do_debug():
    global CHE_DO_DEBUG # Sử dụng global để thay đổi biến toàn cục
    CHE_DO_DEBUG = True
    print("Đã bật chế độ Debug.")

ghi_log("Chương trình bắt đầu.") # Output: [INFO] Chương trình bắt đầu.
bat_che_do_debug()
ghi_log("Lỗi xảy ra trong quá trình xử lý.") # Output: [DEBUG] Lỗi xảy ra trong quá trình xử lý.

Khi bạn cần một bộ đếm hoặc trạng thái duy nhất cho toàn bộ ứng dụng:

# Ví dụ: Bộ đếm truy cập
so_lan_truy_cap_trang = 0 # Biến toàn cục để đếm số lần truy cập

def xu_ly_yeu_cau_trang():
    global so_lan_truy_cap_trang # Thay đổi biến toàn cục
    so_lan_truy_cap_trang += 1
    print("Yêu cầu trang đã được xử lý.")

xu_ly_yeu_cau_trang()
xu_ly_yeu_cau_trang()
xu_ly_yeu_cau_trang()
print(f"Tổng số lần truy cập trang: {so_lan_truy_cap_trang}") # Output: Tổng số lần truy cập trang: 3
  • Khi bạn chắc chắn rằng việc thay đổi biến này từ nhiều nơi là an toàn và dễ kiểm soát: Điều này thường xảy ra trong các script nhỏ, đơn giản, nơi mà việc theo dõi sự thay đổi của biến toàn cục không quá phức tạp. Tuy nhiên, đây là một đánh đổi và cần phải cân nhắc kỹ lưỡng.

Khi nào không nên dùng global (Hạn chế/nhược điểm)

Mặc dù có những trường hợp hợp lý để dùng global, trong hầu hết các tình huống, bạn nên tránh nó vì những lý do sau:

  • Làm cho code khó hiểu và khó theo dõi hơn (Spaghetti Code): Khi một biến toàn cục có thể bị thay đổi bởi bất kỳ hàm nào trong chương trình, việc tìm ra hàm nào đã thay đổi nó và khi nào trở nên cực kỳ khó khăn. Điều này giống như một "ma trận" rối rắm, khiến việc gỡ lỗi và hiểu luồng dữ liệu trở thành ác mộng.

  • Tăng khả năng gây lỗi (Side Effects) khi chương trình lớn: Một "side effect" là khi một hàm làm điều gì đó ngoài việc trả về giá trị (ví dụ: thay đổi một biến toàn cục). Nếu nhiều hàm cùng thay đổi một biến toàn cục, chúng có thể vô tình ảnh hưởng đến nhau theo những cách không mong muốn và khó lường trước. Điều này dẫn đến các lỗi khó tìm và sửa chữa, đặc biệt trong các dự án lớn.

  • Giảm tính tái sử dụng của hàm: Khi một hàm phụ thuộc vào việc đọc hoặc thay đổi một biến toàn cục cụ thể, nó sẽ trở nên kém độc lập. Bạn sẽ khó khăn hơn khi muốn sử dụng lại hàm đó trong một phần khác của chương trình hoặc trong một dự án khác, vì nó luôn cần đến sự tồn tại của biến toàn cục đó.

# Hàm phụ thuộc vào biến toàn cục 'thue_suat_mac_dinh'
thue_suat_mac_dinh = 0.10 # Biến toàn cục

def tinh_gia_sau_thue(gia_goc):
    # Đây là một hàm không tái sử dụng tốt vì nó dùng biến toàn cục
    return gia_goc * (1 + thue_suat_mac_dinh)

# Nếu bạn muốn dùng hàm này với thuế suất khác, bạn phải thay đổi biến toàn cục
# hoặc viết một hàm mới, thay vì truyền tham số.

Thường có cách tốt hơn: Trong hầu hết các trường hợp, có những giải pháp thiết kế tốt hơn để quản lý dữ liệu mà không cần lạm dụng global:

  • Truyền tham số (Passing Arguments): Thay vì biến toàn cục, hãy truyền dữ liệu mà hàm cần như một tham số. Điều này giúp hàm độc lập và dễ kiểm soát hơn.

def tinh_gia_sau_thue_tot_hon(gia_goc, thue_suat):
    return gia_goc * (1 + thue_suat)

# Hàm này có thể tái sử dụng dễ dàng với bất kỳ thuế suất nào
gia_cuoi = tinh_gia_sau_thue_tot_hon(100, 0.08)

Trả về giá trị (Returning Values): Nếu một hàm cần thay đổi một giá trị, hãy để nó trả về giá trị mới, sau đó bạn có thể gán giá trị đó cho biến bên ngoài.

def tang_so(so):
    return so + 1

my_number = 5
my_number = tang_so(my_number) # Gán lại giá trị mới
print(my_number) # Output: 6

Sử dụng đối tượng (Objects) và lớp (Classes): Đối với các chương trình phức tạp hơn, việc tổ chức dữ liệu và các hàm liên quan vào các lớp (class) là một phương pháp mạnh mẽ hơn nhiều để quản lý trạng thái, tránh sự phụ thuộc vào biến toàn cục.

Lời khuyên: Hạn chế sử dụng global

Quy tắc chung là: Hãy hạn chế tối đa việc sử dụng từ khóa global. Chỉ dùng nó khi bạn thực sự hiểu được tác động của nó và chắc chắn rằng không có giải pháp thay thế nào tốt hơn. Trong các trường hợp còn lại, hãy ưu tiên:

  • Truyền dữ liệu vào và ra khỏi hàm thông qua tham số và giá trị trả về.

  • Sử dụng các cấu trúc dữ liệu như list hoặc dictionary để nhóm các giá trị liên quan.

  • Xem xét sử dụng lập trình hướng đối tượng (class và object) để quản lý trạng thái và hành vi của chương trình một cách có tổ chức hơn.

Hiểu được những hạn chế này sẽ giúp bạn viết code Python bền vững và dễ bảo trì hơn trong dài hạn.

Kết bài

việc hiểu rõ về biến toàn cục (global variables) và cách sử dụng từ khóa global là rất quan trọng khi lập trình Python. Biến toàn cục cho phép bạn chia sẻ dữ liệu trên toàn bộ chương trình, nhưng bạn cần dùng global để có thể thay đổi giá trị của chúng từ bên trong các hàm. Mặc dù global là một công cụ mạnh mẽ, nó nên được sử dụng một cách thận trọng và có cân nhắc. Lạm dụng biến toàn cục có thể dẫn đến mã nguồn khó hiểu, khó bảo trì và dễ phát sinh lỗi. Thay vào đó, hãy ưu tiên các phương pháp quản lý dữ liệu hiệu quả hơn như truyền tham số, trả về giá trị, hoặc sử dụng lập trình hướng đối tượng, để đảm bảo chương trình của bạn luôn rõ ràng, ổn định và dễ phát triển trong tương lai.

Bài viết liên quan