如何在 Python 中创建模块范围的变量?[重复]
- 2025-04-15 09:19:00
- admin 原创
- 25
问题描述:
有没有办法在模块内部设置全局变量?当我尝试用下面最明显的方式执行此操作时,Python 解释器说该变量__DBNAME__
不存在。
...
__DBNAME__ = None
def initDB(name):
if not __DBNAME__:
__DBNAME__ = name
else:
raise RuntimeError("Database name has already been set.")
...
在另一个文件中导入模块后
...
import mymodule
mymodule.initDB('mydb.sqlite')
...
回溯结果如下:
UnboundLocalError: local variable '__DBNAME__' referenced before assignment
有什么想法吗?我正在尝试按照这位朋友的建议,使用模块来设置单例。
解决方案 1:
事情是这样的。
首先,Python 真正拥有的全局变量只有模块作用域的变量。你无法创建真正全局的变量;你只能在特定作用域内创建变量。(如果你在 Python 解释器内创建了一个变量,然后导入了其他模块,那么这个变量就位于最外层作用域,因此在你的 Python 会话中是全局的。)
要创建模块全局变量,您只需分配一个名称即可。
假设有一个名为 foo.py 的文件,其中包含以下一行:
X = 1
现在想象一下你导入它。
import foo
print(foo.X) # prints 1
但是,假设你想在函数内部将一个模块范围的变量用作全局变量,就像你的例子一样。Python 默认将函数变量视为局部变量。你只需global
在函数中添加一个声明,然后再尝试使用全局变量即可。
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.")
顺便说一句,对于这个例子,简单的if not __DBNAME__
测试就足够了,因为除了空字符串之外的任何字符串值都会被评估为 true,所以任何实际的数据库名称都会被评估为 true。但是对于可能包含数字值(该数字值可能为 0)的变量,您不能直接使用if not variablename
;在这种情况下,您应该None
使用is
运算符显式地测试 。我修改了示例以添加一个显式None
测试。显式测试None
永远不会出错,所以我默认使用它。
最后,正如其他人在本页所指出的,双下划线开头会向 Python 发出信号,表明您希望该变量对模块来说是“私有的”。如果您执行了from mymodule import *
,Python 不会将以双下划线开头的名称导入到您的命名空间中。但是,如果您只是执行了一个简单的import mymodule
,然后 say ,dir(mymodule)
您将在列表中看到“私有”变量,并且如果您明确引用了mymodule.__DBNAME__
Python,它不会在意,它只会允许您引用它。双下划线开头会向模块的用户发出一个重要提示,表明您不希望他们将该名称重新绑定到他们自己的某个值。
在 Python 中,最佳实践是不这样做,而是通过使用或明确执行类似导入import *
来最小化耦合并最大化显式性。mymodule.something
`from mymodule import something`
编辑:如果出于某种原因,你需要在没有该global
关键字的非常老版本的 Python 中执行类似的操作,那么有一个简单的解决方法。不要直接设置模块全局变量,而是在模块全局级别使用可变类型,并将你的值存储在其中。
在你的函数中,全局变量名将是只读的;你无法重新绑定实际的全局变量名。(如果你在函数内部赋值该变量名,它只会影响函数内部的局部变量名。)但是你可以使用该局部变量名访问实际的全局对象,并在其中存储数据。
您可以使用,list
但您的代码会很难看:
__DBNAME__ = [None] # use length-1 list as a mutable
# later, in code:
if __DBNAME__[0] is None:
__DBNAME__[0] = name
Adict
更好。但最方便的是一个类实例,你可以直接使用一个简单的类:
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
(您实际上不需要将数据库名称变量大写。)
__m.dbname
我喜欢直接使用而不是 的语法糖__m["DBNAME"]
;在我看来,这似乎是最方便的解决方案。而且这个dict
解决方案也很好用。
您dict
可以使用任何可散列的值作为键,但是当您对有效标识符的名称感到满意时,您可以使用像Box
上面那样的简单类。
解决方案 2:
通过在模块上显式访问模块级变量来显式访问它们
简而言之:这里描述的技术与steveha的答案中的相同,不同之处在于,没有创建任何人工辅助对象来显式限定变量的作用域。相反,模块对象本身被赋予一个变量指针,因此在从任何地方访问时都能提供显式的作用域。(类似于在局部函数作用域中赋值)。
可以将其视为当前模块的自身,而不是当前实例!
# 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))
由于模块被缓存,因此只需导入一次,您可以db.py
在任意数量的客户端上导入任意次数,操作相同的通用状态:
# client_a.py
import db
db.initialize_db('mongo')
# client_b.py
import db
if (db.db_name == 'mongo'):
db.db_name = None # this is the preferred way of usage, as it updates the value for all clients, because they access the same reference from the same module object
# client_c.py
from db import db_name
# be careful when importing like this, as a new reference "db_name" will
# be created in the module namespace of client_c, which points to the value
# that "db.db_name" has at import time of "client_c".
if (db_name == 'mongo'): # checking is fine if "db.db_name" doesn't change
db_name = None # be careful, because this only assigns the reference client_c.db_name to a new value, but leaves db.db_name pointing to its current value.
作为额外的奖励,我发现它整体上非常符合 Python 风格,因为它很好地符合了 Python 的“显式优于隐式”策略。
解决方案 3:
Steveha 的回答对我有帮助,但忽略了一个重要点(我认为 Wisty 想表达的就是这个点)。如果在函数中只访问变量但不赋值,那么 global 关键字就没有必要了。
如果在赋值时不使用 global 关键字,Python 会创建一个新的局部变量——模块变量的值现在将隐藏在函数内部。使用 global 关键字可以在函数内部赋值模块变量。
如果您没有分配变量,Python 2.7 下的 Pylint 1.3.1 强制不使用全局变量。
module_var = '/dev/hello'
def readonly_access():
connect(module_var)
def readwrite_access():
global module_var
module_var = '/dev/hello2'
connect(module_var)
解决方案 4:
为此,您需要将变量声明为全局变量。但是,全局变量也可以通过使用 从模块外部module_name.var_name
访问。将其添加到模块的第一行:
global __DBNAME__
解决方案 5:
你被一个微妙的怪癖迷惑了。你不能在 Python 函数内部重新赋值模块级变量。我认为这是为了防止有人意外地在函数内部重新赋值。
您可以访问模块命名空间,但不应尝试重新赋值。如果您的函数赋值了某个值,它会自动变为函数变量——并且 Python 不会在模块命名空间中查找。
您可以执行以下操作:
__DB_NAME__ = None
def func():
if __DB_NAME__:
connect(__DB_NAME__)
else:
connect(Default_value)
__DB_NAME__
但您不能在函数内部重新分配。
一种解决方法:
__DB_NAME__ = [None]
def func():
if __DB_NAME__[0]:
connect(__DB_NAME__[0])
else:
__DB_NAME__[0] = Default_value
请注意,我没有重新分配__DB_NAME__
,我只是修改了它的内容。
扫码咨询,免费领取项目管理大礼包!