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

时间:2009-12-29 22:34:16

标签: python variables singleton module scope

有没有办法在模块内部设置全局变量?当我尝试以最明显的方式执行此操作时,如下所示,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

有什么想法吗?我正在尝试按照this fellow's建议使用模块设置单例。

5 个答案:

答案 0 :(得分:208)

这是正在发生的事情。

首先,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发出信号,表示您希望该变量对模块是“私有的”。如果您执行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这样的普通类。

答案 1 :(得分:55)

通过在模块上访问它们来明确访问模块级变量

简而言之:此处描述的技术与steveha's answer 除外)相同,没有人工辅助对象创建以显式范围变量。 相反,模块对象本身被赋予一个变量指针,因此在从任何地方访问时提供显式范围。 (如本地功能范围内的作业)

自我 视为 当前模块 ,而不是当前实例!

# 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,您可以根据需要随意导入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.

作为一个额外的奖励我发现它非常pythonic整体,因为它很好地适合Pythons政策明确比隐含更好。

答案 2 :(得分:24)

史蒂夫莎的回答对我很有帮助,但遗漏了一个重要的观点(我认为这是一个很有意义的观点)。如果您只访问但不在函数中分配变量,则不需要global关键字。

如果分配不带global关键字的变量,那么Python会创建一个新的本地var - 模块变量的值现在将隐藏在函数内部。使用global关键字在函数内分配模块var。

如果你没有指定var。

,Python 2.7下的Pylint 1.3.1强制执行NOT using global
module_var = '/dev/hello'

def readonly_access():
    connect(module_var)

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

答案 3 :(得分:6)

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

global __DBNAME__

答案 4 :(得分:-7)

你正在堕落一个微妙的怪癖。您不能在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__,我只是修改其内容。