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

How to create module-wide variables in Python?

本问题已经有最佳答案,请猛点这里访问。

是否有方法在模块内设置全局变量?当我尝试用下面显示的最明显的方法进行时,python解释器说变量__DBNAME__不存在。

1
2
3
4
5
6
7
8
9
...
__DBNAME__ = None

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

在将模块导入到其他文件后

1
2
3
4
...
import mymodule
mymodule.initDB('mydb.sqlite')
...

追溯到:UnboundLocalError: local variable '__DBNAME__' referenced before assignment

有什么想法吗?我正试着用一个模块来建立一个单体模型,按照这个家伙的建议。


这是发生的事情。

首先,Python真正拥有的全局变量是模块范围的变量。不能生成真正全局的变量;您所能做的只是在特定范围内生成一个变量。(如果在python解释器中生成一个变量,然后导入其他模块,那么您的变量在最外部的范围内,因此在python会话中是全局的。)

要使模块成为全局变量,只需将其分配给一个名称。

设想一个名为foo.py的文件,其中包含以下单行:

1
X = 1

现在假设您导入它。

1
2
import foo
print(foo.X)  # prints 1

然而,让我们假设您希望使用模块范围变量之一作为函数内部的全局变量,如您的示例中所示。python的默认设置是假定函数变量是本地的。在尝试使用global之前,只需在函数中添加一个global声明。

1
2
3
4
5
6
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__测试是足够的,因为除了空字符串之外的任何字符串值都将计算为真,所以任何实际的数据库名称都将计算为真。但是对于可能包含0的数值的变量,不能只说if not variablename;在这种情况下,应该使用is运算符显式测试None。我修改了示例以添加一个显式的None测试。对None的显式测试从来没有错,所以我默认使用它。

最后,正如本页中其他人所指出的,两个前导下划线向Python发出信号,表示您希望该变量对模块"私有"。如果您使用import * from mymodule,python将不会将带有两个前导下划线的名称导入您的名称空间。但是如果你只做一个简单的import mymodule,然后说dir(mymodule),你会在列表中看到"private"变量,如果你明确地引用mymodule.__DBNAME__,python就不在乎了,它只会让你引用它。双前导下划线是模块用户的主要线索,您不希望他们将该名称重新绑定到自己的某个值。

在python中,最好的做法是不执行import *,而是通过使用mymodule.something或显式执行from mymodule import something之类的导入来最小化耦合和最大化显式性。

编辑:如果出于某种原因,您需要在没有global关键字的非常旧的Python版本中执行类似的操作,那么有一个简单的解决方法。不要直接设置模块全局变量,而是在模块全局级别使用可变类型,并将值存储在其中。

在函数中,全局变量名将是只读的;您将无法重新绑定实际的全局变量名。(如果在函数内分配给该变量名,它只会影响函数内的局部变量名。)但可以使用该局部变量名访问实际的全局对象,并在其中存储数据。

您可以使用list,但您的代码将很难看:

1
2
3
4
5
__DBNAME__ = [None] # use length-1 list as a mutable

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

一个dict更好。但是最方便的是一个类实例,您可以使用一个普通的类:

1
2
3
4
5
6
7
8
9
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的普通类。


通过在模块上显式访问模块级变量来显式访问它们

简而言之:这里描述的技术与Steveha的答案相同,只是没有创建任何人工助手对象来显式地限定变量的范围。相反,模块对象本身被赋予一个变量指针,因此在从任何地方访问时都提供显式范围。(类似于本地函数范围中的赋值)。

把它想象成当前模块的自我,而不是当前实例!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 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,并操作相同的通用状态:

1
2
3
4
# client_a.py
import db

db.initialize_db('mongo')
1
2
3
4
5
# 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
1
2
3
4
5
6
7
8
# 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的政策,显式比隐式要好。


史蒂文的回答对我很有帮助,但忽略了一个重要的问题(我认为威斯蒂正在讨论这个问题)。如果只访问但不在函数中分配变量,则不需要使用global关键字。

如果不使用global关键字分配变量,那么python将创建一个新的局部变量——模块变量的值现在将隐藏在函数中。使用global关键字在函数内分配模块var。

pylint 1.3.1在python 2.7下,如果不分配var,则强制不使用global。

1
2
3
4
5
6
7
8
9
module_var = '/dev/hello'

def readonly_access():
    connect(module_var)

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

为此,需要将变量声明为全局变量。但是,也可以使用module_name.var_name从模块外部访问全局变量。将此添加为模块的第一行:

1
global __DBNAME__


你爱上了一个微妙的怪癖。不能在python函数内重新分配模块级变量。我认为这是为了阻止人们意外地在函数内部重新分配东西。

您可以访问模块名称空间,只是不应该尝试重新分配。如果您的函数分配了一些东西,它会自动成为一个函数变量——而python不会在模块名称空间中查找。

你可以做到:

1
2
3
4
5
6
7
__DB_NAME__ = None

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

但不能在函数内部重新分配__DB_NAME__

一个解决办法:

1
2
3
4
5
6
7
__DB_NAME__ = [None]

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

注意,我不是重新分配__DB_NAME__,我只是修改它的内容。