如何在Python中创建模块范围的变量?

是否有一种方法可以在模块内部设置一个全局变量?当我试图以如下所示的最明显的方式来做时,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')
...

回溯是:

< p >… 赋值前引用的局部变量“DBNAME” …< / p >

什么好主意吗?我试图通过使用模块来设置一个单例,根据这个家伙的建议。

340765 次浏览

为此,需要将变量声明为全局变量。然而,全局变量也可以通过使用module_name.var_name从模块的中访问。添加这个作为模块的第一行:

global __DBNAME__

事情是这样的。

首先,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;在这种情况下,你应该使用is操作符显式测试None。我修改了示例,添加了一个显式的None测试。None的显式测试永远不会出错,所以我默认使用它。

最后,正如其他人在本页上注意到的那样,两个前导下划线向Python发出信号,表明您希望该变量对模块是“私有”的。如果你曾经执行import * from mymodule, Python将不会将带有两个前导下划线的名称导入到你的名称空间。但如果你只做一个简单的import mymodule,然后说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

dict更好。但最方便的是类实例,你可以只使用一个平凡的类:

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这样的普通类。

你被一个微妙的怪癖骗了。不能在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__,我只是修改了它的内容。

Steveha的回答对我很有帮助,但忽略了一个重要的点(我认为wisty正在抓住这个点)。如果只访问而不分配函数中的变量,则不需要使用global关键字。

如果你给变量赋值时不带global关键字,那么Python会创建一个新的局部var——模块变量的值现在会隐藏在函数内部。使用global关键字在函数中为模块var赋值。

Pylint 1.3.1在Python 2.7下强制NOT使用global如果你没有分配var。

module_var = '/dev/hello'


def readonly_access():
connect(module_var)


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

通过显式访问模块上的模块级别变量来显式访问模块级别变量


简而言之:这里描述的技术与steveha的回答< em > < / em >除外中的技术相同,即不创建人工帮助对象来显式地创建作用域变量。相反,模块对象本身被赋予了一个变量指针,因此在从任何地方访问时都提供了显式的范围。(类似于局部函数作用域的赋值)

把它想象成< em > < / em >当前模块自我< em > < / em >,而不是当前实例!

# 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的显性比隐性好策略。