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

dav*_*lab 198 python variables singleton scope module

有没有办法在模块内部设置全局变量?当我尝试以最明显的方式执行此操作时,如下所示,Python解释器说该变量__DBNAME__不存在.

...
__DBNAME__ = None

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

并在将模块导入另一个文件后

...
import mymodule
mymodule.initDB('mydb.sqlite')
...
Run Code Online (Sandbox Code Playgroud)

追溯是: __DBNAME__

有任何想法吗?我正在尝试使用模块来设置单例,根据这个人的建议.

ste*_*eha 225

这是正在发生的事情.

首先,Python真正拥有的唯一全局变量是模块范围的变量.你不能创造一个真正全球化的变量; 您所能做的就是在特定范围内创建变量.(如果在Python解释器中创建变量,然后导入其他模块,则您的变量位于最外层范围内,因此在Python会话中是全局的.)

制作模块全局变量所需要做的就是分配一个名称.

想象一个名为foo.py的文件,包含这一行:

X = 1
Run Code Online (Sandbox Code Playgroud)

现在假设您导入它.

import foo
print(foo.X)  # prints 1
Run Code Online (Sandbox Code Playgroud)

但是,假设您希望将一个模块范围变量用作函数内的全局变量,如示例所示.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.")
Run Code Online (Sandbox Code Playgroud)

顺便说一下,对于这个例子,简单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
Run Code Online (Sandbox Code Playgroud)

A 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
Run Code Online (Sandbox Code Playgroud)

(您实际上不需要将数据库名称变量大写.)

我喜欢使用__m.dbname而不是使用的语法糖__m["DBNAME"]; 在我看来,它似乎是最方便的解决方案.但dict解决方案也可以.

使用a dict可以使用任何hashable值作为键,但是如果您对作为有效标识符的名称感到满意,则可以使用Box如上所述的普通类.

  • 两个领先的下划线将导致名称损坏.通常,单个下划线足以表明变量应被视为私有./sf/ask/485110111/#6930223 (3认同)

tim*_*ner 65

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


总之:这里所描述的技术是相同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))
Run Code Online (Sandbox Code Playgroud)

由于模块是高速缓存的,因此只导入一次,您可以db.py根据需要在任意数量的客户端上导入,操作相同的通用状态:

# client_a.py
import db

db.initialize_db('mongo')
Run Code Online (Sandbox Code Playgroud)
# 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
Run Code Online (Sandbox Code Playgroud)
# 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.
Run Code Online (Sandbox Code Playgroud)

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

  • 对我来说,这似乎是最干净的方法,但是我的小子们对此一无所知。我是在做错事吗?还是您/其他人也有这个问题?多谢克里斯 (2认同)

Bra*_*Dre 29

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

如果您在没有global关键字的情况下分配变量,那么Python会创建一个新的局部变量 - 模块变量的值现在将隐藏在函数内部.使用global关键字在函数内分配模块var.

如果不指定var,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)
Run Code Online (Sandbox Code Playgroud)


Chi*_*chi 6

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

global __DBNAME__
Run Code Online (Sandbox Code Playgroud)

  • 这不是强制执行的daveslab.Python中的想法是我们都是成年人,私有和受保护的变量最好通过合同和约定完成任何严格的编译器强制机制. (2认同)