2009-12-29 20 views
145

Có cách nào để thiết lập biến toàn cầu bên trong mô-đun không? Khi tôi cố gắng làm điều đó một cách rõ ràng nhất như xuất hiện bên dưới, trình thông dịch Python cho biết biến số __DBNAME__ không tồn tại.Làm thế nào để tạo các biến rộng mô-đun trong Python?

... 
__DBNAME__ = None 

def initDB(name): 
    if not __DBNAME__: 
     __DBNAME__ = name 
    else: 
     raise RuntimeError("Database name has already been set.") 
... 

Và sau khi nhập các mô-đun trong một tập tin khác nhau

... 
import mymodule 
mymodule.initDB('mydb.sqlite') 
... 

Và traceback là: UnboundLocalError: local variable '__DBNAME__' referenced before assignment

Bất kỳ ý tưởng? Tôi đang cố gắng để thiết lập một singleton bằng cách sử dụng một mô-đun, theo đề nghị this fellow's.

+6

Nhờ mọi người vì câu trả lời tuyệt vời! Đã học nhiều về Python hơn tôi mong đợi. – daveslab

Trả lời

172

Đây là những gì đang diễn ra.

Đầu tiên, các biến toàn cục duy nhất mà Python thực sự có là các biến phạm vi mô-đun. Bạn không thể tạo ra một biến thực sự toàn cầu; tất cả những gì bạn có thể làm là tạo một biến trong một phạm vi cụ thể. (Nếu bạn tạo một biến bên trong trình thông dịch Python, và sau đó nhập các mô-đun khác, biến của bạn nằm trong phạm vi ngoài cùng và do đó toàn cầu trong phiên Python của bạn.)

Tất cả những gì bạn phải làm để tạo biến toàn cầu là chỉ cần gán cho một cái tên.

Hãy tưởng tượng một tập tin gọi là foo.py, chứa dòng này:

X = 1 

Bây giờ hãy tưởng tượng bạn nhập nó.

import foo 
print(foo.X) # prints 1 

Tuy nhiên, giả sử bạn muốn sử dụng một trong các biến phạm vi mô-đun của bạn như một hàm bên trong chung, như trong ví dụ của bạn. Mặc định của Python là giả định rằng các biến chức năng là cục bộ. Bạn chỉ cần thêm tuyên bố global vào chức năng của mình, trước khi bạn cố gắng sử dụng toàn cục.

def initDB(name): 
    global __DBNAME__ # add this line! 
    if __DBNAME__ is None: # see notes below; explicit test for None 
     __DBNAME__ = name 
    else: 
     raise RuntimeError("Database name has already been set.") 

Bằng cách này, ví dụ này, if not __DBNAME__ kiểm tra đơn giản là đủ, bởi vì bất kỳ chuỗi giá trị khác hơn là một chuỗi rỗng sẽ đánh giá đúng, vì vậy bất kỳ tên cơ sở dữ liệu thực tế sẽ đánh giá đúng. Nhưng đối với các biến có thể chứa giá trị số có thể là 0, bạn không thể chỉ nói if not variablename; trong trường hợp đó, bạn nên kiểm tra rõ ràng None bằng toán tử is. Tôi đã sửa đổi ví dụ để thêm bài kiểm tra None rõ ràng. Các thử nghiệm rõ ràng cho None là không bao giờ sai, vì vậy tôi mặc định để sử dụng nó.

Cuối cùng, như những người khác đã lưu ý trên trang này, hai dấu gạch dưới hàng đầu báo hiệu cho Python rằng bạn muốn biến là "riêng tư" đối với mô-đun. Nếu bạn đã từng làm một import * from mymodule, Python sẽ không nhập tên có hai dấu gạch dưới hàng đầu vào không gian tên của bạn. Nhưng nếu bạn chỉ cần thực hiện một đơn giản import mymodule và sau đó nói dir(mymodule) bạn sẽ thấy các biến "riêng tư" trong danh sách và nếu bạn tham chiếu rõ ràng mymodule.__DBNAME__ Python sẽ không quan tâm, nó sẽ chỉ cho phép bạn tham chiếu đến nó. Các dấu gạch dưới hàng đầu kép là một đầu mối chính cho người dùng của mô-đun của bạn mà bạn không muốn họ rebinding tên đó cho một số giá trị của riêng mình.

Nó được coi là thực hành tốt nhất trong Python không làm import *, nhưng để giảm thiểu khớp nối và tối đa hóa nhân chứng bằng cách sử dụng mymodule.something hoặc bằng cách nhập một cách rõ ràng như from mymodule import something.

EDIT: Nếu vì một lý do nào đó, bạn cần phải làm điều gì đó như thế này trong phiên bản cũ của Python không có từ khóa global, có cách giải quyết dễ dàng. Thay vì đặt trực tiếp biến toàn cục mô-đun, hãy sử dụng loại có thể thay đổi ở cấp mô-đun toàn cầu và lưu trữ các giá trị của bạn bên trong nó.

Trong các chức năng của bạn, tên biến toàn cục sẽ là chỉ đọc; bạn sẽ không thể rebind tên biến toàn cầu thực tế. (Nếu bạn gán cho tên biến đó bên trong hàm của bạn, nó sẽ chỉ ảnh hưởng đến tên biến cục bộ bên trong hàm.) Nhưng bạn có thể sử dụng tên biến cục bộ đó để truy cập đối tượng toàn cục thực và lưu trữ dữ liệu bên trong nó.

Bạn có thể sử dụng một list nhưng mã của bạn sẽ xấu xí:

__DBNAME__ = [None] # use length-1 list as a mutable 

# later, in code: 
if __DBNAME__[0] is None: 
    __DBNAME__[0] = name 

Một dict là tốt hơn. Tuy nhiên, thuận tiện nhất là một cá thể của lớp, và bạn chỉ có thể sử dụng một lớp tầm thường: (. Bạn không thực sự cần phải tận dụng biến tên cơ sở dữ liệu)

class Box: 
    pass 

__m = Box() # m will contain all module-level values 
__m.dbname = None # database name global in module 

# later, in code: 
if __m.dbname is None: 
    __m.dbname = name 

Tôi thích đường cú pháp của chỉ sử dụng __m.dbname thay vì __m["DBNAME"]; nó có vẻ là giải pháp thuận tiện nhất theo ý kiến ​​của tôi. Nhưng giải pháp dict cũng hoạt động tốt.

Với dict bạn có thể sử dụng bất kỳ giá trị băm nào làm khóa, nhưng khi bạn hài lòng với tên là số nhận dạng hợp lệ, bạn có thể sử dụng một lớp tầm thường như Box ở trên.

+0

Câu trả lời hay, @steveha, tóm tắt mọi thông tin đúng cho đến nay và vẫn đưa ra câu trả lời. – daveslab

+0

Kỹ thuật thứ hai (sử dụng một lớp) là hoàn hảo. Cảm ơn. –

+0

Hai dấu gạch dưới hàng đầu sẽ dẫn đến mangling tên. Thông thường, một dấu gạch dưới đơn là đủ để cho biết rằng một biến nên được coi là riêng tư. https://stackoverflow.com/questions/6930144/underscore-vs-double-underscore-with-variables-and-methods#6930223 – Rabiees

5

Đối với điều này, bạn cần khai báo biến là toàn cầu. Tuy nhiên, biến toàn cầu cũng có thể truy cập từ bên ngoài mô-đun bằng cách sử dụng module_name.var_name. Thêm dòng này làm dòng đầu tiên của mô-đun:

global __DBNAME__ 
+0

là có cách nào để làm cho nó có thể truy cập vào toàn bộ mô-đun, nhưng không có sẵn để được gọi bởi module_name .__ DBNAME__? – daveslab

+0

Có ... bạn có thể đặt câu lệnh chung bên trong hàm của bạn để làm cho nó "toàn cục" trong mô-đun (trong hàm đó ... bạn phải lặp lại khai báo chung trong mọi hàm sử dụng toàn cục này). Ví dụ (tha thứ cho mã trong các chú thích): 'def initDB (tên): \ n global __DBNAME__' –

+0

Cảm ơn, Jarret. Thật không may, khi tôi thử điều đó, và chạy dir (mymodule) trên bàn điều khiển, nó cho thấy các biến có sẵn và tôi có thể truy cập chúng. Tôi có hiểu lầm bạn không? – daveslab

-5

Bạn đang rơi vào một điều kỳ quặc tinh tế. Bạn không thể gán lại các biến cấp mô-đun bên trong một hàm python. Tôi nghĩ rằng điều này là có để ngăn chặn mọi người tái giao công cụ bên trong một chức năng do tai nạn.

Bạn có thể truy cập vào không gian tên mô-đun, bạn không nên cố gắng chỉ định lại. Nếu hàm của bạn gán một cái gì đó, nó sẽ tự động trở thành một biến chức năng - và python sẽ không nhìn vào không gian tên mô-đun.

Bạn có thể làm:

__DB_NAME__ = None 

def func(): 
    if __DB_NAME__: 
     connect(__DB_NAME__) 
    else: 
     connect(Default_value) 

nhưng bạn có thể không tái gán __DB_NAME__ bên trong một hàm.

Một workaround:

__DB_NAME__ = [None] 

def func(): 
    if __DB_NAME__[0]: 
     connect(__DB_NAME__[0]) 
    else: 
     __DB_NAME__[0] = Default_value 

Lưu ý, tôi sẽ không tái gán __DB_NAME__, tôi chỉ sửa đổi nội dung của nó.

+3

Điều này không đúng. 'global' cho phép bạn đặt tên cấp mô-đun. – dbn

16

Câu trả lời của Steveha rất hữu ích đối với tôi, nhưng bỏ qua một điểm quan trọng (một trong những điều mà tôi nghĩ là đã xáo trộn). Từ khóa chung là không cần thiết nếu bạn chỉ truy cập nhưng không gán biến trong hàm.

Nếu bạn gán biến không có từ khóa chung thì Python tạo một biến cục bộ mới - giá trị của biến mô-đun sẽ bị ẩn bên trong hàm. Sử dụng từ khóa chung để gán var module bên trong một hàm.

Pylint 1.3.1 trong Python 2.7 thi hành KHÔNG sử dụng chung nếu bạn không chỉ định var.

module_var = '/dev/hello' 

def readonly_access(): 
    connect(module_var) 

def readwrite_access(): 
    global module_var 
    module_var = '/dev/hello2' 
    connect(module_var) 
33

Explicit quyền truy cập vào các biến cấp module bằng cách truy cập chúng explicity trên module


Nói tóm lại: Kỹ thuật mô tả ở đây cũng giống như trong steveha's answer, trừ, không có đối tượng trợ giúp nhân tạo nào được tạo ra để hiển thị các biến số một cách rõ ràng. Thay vào đó, đối tượng mô-đun chính nó được đưa ra một con trỏ biến, và do đó cung cấp phạm vi rõ ràng khi truy cập từ mọi nơi. (như các bài tập trong phạm vi chức năng cục bộ).

nghĩ về nó như tự cho hiện mô-đun thay vì dụ hiện nay!

# db.py 
import sys 

# this is a pointer to the module object instance itself. 
this = sys.modules[__name__] 

# we can explicitly make assignments on it 
this.db_name = None 

def initialize_db(name): 
    if (this.db_name is None): 
     # also in local function scope. no scope specifier like global is needed 
     this.db_name = name 
     # also the name remains free for local use 
     db_name = "Locally scoped db_name variable. Doesn't do anything here." 
    else: 
     msg = "Database is already initialized to {0}." 
     raise RuntimeError(msg.format(this.db_name)) 

As modules are cached and therefore import only once, bạn có thể nhập db.py thường xuyên trên nhiều khách hàng như bạn muốn, thao tác giống nhau, tình trạng phổ biến:

# client_a.py 
import db 

db.initialize_db('mongo') 
# client_b.py 
from db import db_name 

if (db_name == 'mongo'): 
    db_name = None 

Tôi đã sử dụng công thức này để tạo các mô-đun xử lý đơn giản và nhẹ handler(như đối lập với các lớp xử lý) kể từ khi tôi tìm thấy nó. Là một phần thưởng bổ sung, tôi thấy nó khá trầm trọng tổng thể vì nó độc đáo phù hợp với chính sách của Pythons là Rõ ràng là tốt hơn so với ngụ ý.

+1

Tôi thích rằng bạn có thể sử dụng chính xác hơn "từ nhập db" trong mô-đun thứ hai, mặc dù bạn phải thực hiện "nhập db" lớn hơn trong chính. Điều này có vẻ đúng nếu bạn bỏ qua phép thuật 'sys' và sử dụng 'global' trong initialize_db. Bạn có thể bình luận về những ưu/khuyết điểm của toàn cầu so với câu trả lời của bạn, vì cả hai đều có vẻ giống nhau? –

+0

** pro ** với tôi là bạn không cần thao tác phạm vi nữa. Bạn rõ ràng cung cấp cho phạm vi bằng cách truy cập biến db_name từ một đối tượng, điều này xảy ra với mô-đun. Bạn không phải khai báo nơi đối tượng mà bạn muốn làm việc với cuộc sống, trước khi sử dụng nó bất cứ lúc nào. Ngoài ra, bạn có thể có các biến cục bộ có tên db_name trong các hàm điều khiển, bên cạnh this.db_name. – timmwagener

+0

Với tôi, điều này có vẻ là cách sạch nhất để làm điều này, nhưng những linters của tôi đang balking vào nó. Tôi có làm điều gì đó sai hay bạn/người khác có vấn đề này không? Cảm ơn một tấn, Chris – ThePosey

Các vấn đề liên quan