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

2024-03-28 12:20:18 发布

您现在位置: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')
...

回溯是:

... UnboundLocalError: local variable 'DBNAME' referenced before assignment ...

有什么想法吗?我正试图根据this fellow's建议,通过使用模块来设置单例。


Tags: 模块方法namenoneifdefnot解释器
3条回答

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


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

把它想象成当前模块而不是当前实例的self

# 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.

作为一个额外的奖励,我发现它总体上是相当Python的,因为它很好地符合Python的政策,显式优于隐式

史蒂夫哈的回答对我很有帮助,但忽略了一个重要的问题(我认为怀斯提正在回答这个问题)。如果只访问但不在函数中分配变量,则不需要全局关键字。

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

Python 2.7下的Pylint 1.3.1如果不分配变量,则强制不使用global

module_var = '/dev/hello'

def readonly_access():
    connect(module_var)

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

这是发生的事情。

首先,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),你会看到列表中的“private”变量,如果你显式地引用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之类的小类。

相关问题 更多 >