如何在 Python 中创建模块范围的变量? [复制]
Posted
技术标签:
【中文标题】如何在 Python 中创建模块范围的变量? [复制]【英文标题】:How to create module-wide variables in Python? [duplicate] 【发布时间】:2010-12-30 22:42:34 【问题描述】:有没有办法在模块内部设置一个全局变量?当我尝试以最明显的方式进行操作时,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:分配前引用了局部变量“DBNAME” ...
有什么想法吗?根据this fellow's 的建议,我正在尝试使用模块设置单例。
【问题讨论】:
【参考方案1】:这是发生了什么。
首先,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__
测试就足够了,因为除了空字符串之外的任何字符串值都将评估为真,因此任何实际的数据库名称都将评估为真。但是对于可能包含可能为 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
之类的普通类。
【讨论】:
两个前导下划线会导致名称混淆。通常一个下划线就足以表明一个变量应该被认为是私有的。 ***.com/questions/6930144/… 关于 Box 类,在 init 函数中定义 dbname = None 会不会比在示例中定义到 this 外部更好? Python 不关心变量是如何设置的。Box
类或类似的配方定义了一个 __init__()
函数,它从 kwargs
中获取所有值并将它们设置在类字典中。然后你可以做_m = Box(dbname="whatever")
,它很整洁。从 Python 3.3 开始,现在有了 types.SimpleNameSpace
,它是 Box
类的全功能实现;见:docs.python.org/3/library/…【参考方案2】:
通过在模块上显式访问模块级变量来显式访问它们
简而言之:这里描述的技术与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.
作为额外的奖励,我发现它总体上非常 Pythonic,因为它非常适合 Python 的 显式优于隐式的策略。
【讨论】:
我喜欢你可以在第二个模块中使用更精确的“from db import”,即使你必须在主模块中使用更大的“import db”。如果您跳过“sys”魔法并在 initialize_db 中使用“global”,这似乎是正确的。您能否评论一下全局与您的答案的优缺点,因为它们似乎都起作用? 对我而言,pro 是您不再需要范围操作。您可以通过从一个对象访问变量 db_name 来明确指定范围,该对象恰好是模块。在任何时候使用它之前,您不必声明要使用的对象的位置。您也可以在处理函数中使用名为 db_name 的局部变量,也可以在 this.db_name 旁边。 对我来说,这似乎是最干净的方法,但我的 linter 对此犹豫不决。我做错了什么还是你/其他人也有这个问题?非常感谢,克里斯 @beeb 我的client_b.py
示例有一点小问题。在导入时,它将在client_b
的模块范围内创建一个新变量,该变量从client_a
中获得db_name
的当前值。您可以像在示例中那样检查它,但如果值通过 client_a
中的赋值更改,例如通过调用 initialize_db()
,这意味着引用 client_a.db_name
指向一个新值,其他引用,例如 client_b.db_name
仍然指向导入时分配的旧值,因为我们没有重新分配这些值。这有点误导,我会更新答案。
在模块本身上绑定模块全局变量看起来很酷,但是现在如果客户想要更改模块的全局,他们仅限于import db
,并且不能使用更明确的@987654337 @不再。从可用性的角度来看不是很酷,不是吗?【参考方案3】:
Steveha 的回答对我有帮助,但忽略了一个重要的点(我认为 Wisty 的意思)。如果在函数中只访问变量而不赋值,则不需要 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)
【讨论】:
【参考方案4】:为此,您需要将变量声明为全局变量。但是,也可以使用module_name.var_name
从模块外部 访问全局变量。将此添加为模块的第一行:
global __DBNAME__
【讨论】:
有什么方法可以让整个模块都可以访问它,但不能被 module_name.__DBNAME__ 调用? 是的...您可以将全局语句放在您的函数中以使其在模块内“全局”(在该函数中...您必须在每个函数中重复全局声明使用这个全局)。例如(原谅cmets中的代码):def initDB(name):\n global __DBNAME__
谢谢,杰瑞特。不幸的是,当我尝试这样做并在控制台上运行 dir(mymodule) 时,它显示变量可用并且我可以访问它们。我误会你了吗?
把整个东西放在一个类中。这样,至少想要访问私有变量的人必须做一些工作。
这不是可执行的 daveslab。 Python 的理念是,我们都是成年人,私有变量和受保护变量最好通过任何严格的编译器强制机制的约定和约定来实现。【参考方案5】:
你爱上了一个微妙的怪癖。您不能在 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__
,我只是在修改它的内容。
【讨论】:
这不是真的。global
允许您设置模块级别的名称。以上是关于如何在 Python 中创建模块范围的变量? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
如何在 python 中使用 sqlalchemy 在查询中创建 sql server 表变量