在文件之间使用全局变量?

323

我对全局变量的工作原理有点困惑。 我有一个大型项目,约有50个文件,我需要为所有这些文件定义全局变量。

我的做法是在项目的main.py文件中定义它们,如下所示:

# ../myproject/main.py

# Define global myList
global myList
myList = []

# Imports
import subfile

# Do something
subfile.stuff()
print(myList[0])

我正在尝试在 subfile.py 中使用 myList,如下所示

# ../myproject/subfile.py

# Save "hey" into myList
def stuff():
    globals()["myList"].append("hey")

我尝试的另一种方式,但也不起作用

# ../myproject/main.py

# Import globfile    
import globfile

# Save myList into globfile
globfile.myList = []

# Import subfile
import subfile

# Do something
subfile.stuff()
print(globfile.myList[0])

我在subfile.py中放置了以下内容:

# ../myproject/subfile.py

# Import globfile
import globfile

# Save "hey" into myList
def stuff():
    globfile.myList.append("hey")

但是,再次尝试后,它仍然没有起作用。我该如何实现这个功能?我知道当两个文件彼此不了解时(子文件不知道主文件),它不能像那样工作,但我想不出如何做到这一点,而又不使用 io 写入或 pickle,这是我不想要的。


@rodion:导入循环 - 子文件中的代码尝试导入globfile,在其主体中又将其自身重新导入。 - jsbueno
10个回答

472
问题出在你在main.py中定义了myList,但是subfile.py需要使用它。这里有一个简洁的解决方法:将所有全局变量移动到一个文件中,我称其为settings.py。这个文件负责定义全局变量并初始化它们。
# settings.py

def init():
    global myList
    myList = []
接下来,你的subfile可以导入全局变量:
# subfile.py

import settings

def stuff():
    settings.myList.append('hey')
请注意,subfile不会调用init()函数——这个任务由main.py来执行。
# main.py

import settings
import subfile

settings.init()          # Call only once
subfile.stuff()         # Do stuff with global var
print settings.myList[0] # Check the result

这样做可以在达到目标的同时避免多次初始化全局变量。


58
我喜欢这种总体方式,但不喜欢整个“init()”的东西。模块只在第一次导入时才被评估,因此在模块主体中初始化这些变量是完全可以的。 - Kirk Strauser
27
+1 Kirk: 我同意。不过,我的方法可以避免其他模块在主程序启动之前修改globals.myList这种情况的发生。 - Hai Vu
3
你应该将它命名为其他名称,因为“globals”是一个内置名称。PyLint会发出警告:“重新定义内置的'globals'(redefined-builtin)”。 - twasbrillig
谢谢。有什么办法可以使用此文件结构(即从settings.py导入全局变量)来消除在Eclipse PyDev中出现的“未定义的变量导入”错误吗?我不得不在PyDev中禁用错误,这并不理想。 - Franck Dernoncourt
1
是的,我也强烈感觉整个方法都是临时抱佛脚。最好创建变量,在函数之间传递或使用共同的类/对象。如果不利用更好的工具来实现相同的目的,这就是一个软件工程问题。 - j riv
显示剩余8条评论

192

请查看Python文档中关于在模块间共享全局变量的内容:

在一个程序内跨模块共享信息的经典方法是创建一个特殊模块(通常称为config或cfg)。

config.py:

x = 0   # Default value of the 'x' configuration setting

在应用程序的所有模块中导入配置模块;然后该模块将作为全局名称提供。

main.py:

import config
print (config.x)
一般来说,不要使用 modulename import *。这样会使导入者的命名空间变得混乱,并且会使代码检查工具很难检测到未定义的名称。

11
这种方法似乎比被接受的答案更为简洁。 - JoeyC
29
请注意,您不能使用“from config import x”来设置x,只能使用“import config”。 - Yariv
我认为这个答案部分是不正确的。它似乎只保证第一种import语法能够工作。链接文档中也只有第一种语法,而没有from config import x。当使用from语法时,事情变得更加复杂,导入顺序非常重要。 - Mark Rucker
我确认像@Yariv所说的那样,from config import x是无法工作的注释。另外,您正在导入一个小写x并打印一个大写X,这也不会起作用。尽管如此,链接仍然很棒 :) - WinEunuuchs2Unix
相对于其他Python包,您将config.py文件放在哪里? - NullPumpkinException

24
你可以将Python的全局变量看作是“模块”变量,因此它们比C语言中传统的“全局变量”更有用。 全局变量实际上在模块的__dict__中定义,并且可以作为模块属性从该模块外部访问。 因此,在你的例子中:
# ../myproject/main.py

# Define global myList
# global myList  - there is no "global" declaration at module level. Just inside
# function and methods
myList = []

# Imports
import subfile

# Do something
subfile.stuff()
print(myList[0])

并且:

# ../myproject/subfile.py

# Save "hey" into myList
def stuff():
     # You have to make the module main available for the 
     # code here.
     # Placing the import inside the function body will
     # usually avoid import cycles - 
     # unless you happen to call this function from 
     # either main or subfile's body (i.e. not from inside a function or method)
     import main
     main.mylist.append("hey")

2
哇,通常人们会期望两个相互导入的文件陷入无限循环。 - Nikhil VJ
3
乍一看似乎是这样,不是吗?在def stuff()中发生的事情是当文件加载时并不运行导入...它只有在调用stuff()函数时才会运行。因此,从main开始,我们导入subfile,然后调用subfile.stuff(),随后再导入main...没有循环,只需在main中导入一次即可。请参见subfile.py示例中关于导入循环的注释。 - John
看起来它不再工作了:“很可能是由于循环导入”,乍一看是预料之中的(Python 3.9.5)。 - Yuri Khristich

12

使用 from your_file import * 应该可以解决你的问题。它会定义所有内容使其在全局范围内可用(当然,不包括导入中的局部变量)。

例如:

##test.py:

from pytest import *

print hello_world

并且:

##pytest.py

hello_world="hello world!"

4
除非您将值分配给这样的变量,否则不会执行此操作。 - jsbueno
5
我个人尽可能避免使用import *,以便引用明确(不会令人困惑)。此外,您曾经在任何模块中实际使用过所有 "*" 引用吗? - ThorSummoner
32
不要使用import *。你的全局变量将不再保持同步。每个模块都会收到自己的副本。在一个文件中更改变量不会反映在另一个文件中。这也在https://docs.python.org/2/faq/programming.html#how-do-i-share-global-variables-across-modules 中被警告。 - Isa Hassen

10

Hai Vu的答案非常好,只有一个评论:

如果你在其他模块中使用全局变量并且想要动态设置全局变量,请注意在设置全局变量之后再导入其他模块,例如:

# settings.py
def init(arg):
    global myList
    myList = []
    mylist.append(arg)


# subfile.py
import settings

def print():
    settings.myList[0]


# main.py
import settings
settings.init("1st")     # global init before used in other imported modules
                         # Or else they will be undefined

import subfile    
subfile.print()          # global usage

4

您的第二次尝试将完美地工作,并且实际上是一种非常好的处理全局变量名的方式。但是,您在最后一行中有一个名称错误。以下是正确的写法:

# ../myproject/main.py

# Import globfile    
import globfile

# Save myList into globfile
globfile.myList = []

# Import subfile
import subfile

# Do something
subfile.stuff()
print(globfile.myList[0])

看到最后一行了吗?myList 是 globfile 的一个属性,而不是 subfile。这样就可以按照您的意愿进行操作。
Mike

2

根据以上答案和链接,我创建了一个名为global_variables.py的新模块:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# ==============================================================================
#
#       global_variables.py - Global variables shared by all modules.
#
# ==============================================================================

USER = None                 # User ID, Name, GUID varies by platform

def init():
    """ This should only be called once by the main module
        Child modules will inherit values. For example if they contain
        
            import global_variables as g
            
        Later on they can reference 'g.USER' to get the user ID.
    """
    global USER

    import getpass
    USER = getpass.getuser()

# End of global_variables.py


然后在我的主模块中,我使用了这个:
import global_variables as g
g.init()

在另一个导入的子模块中,我可以使用:

import global_variables as g
# hundreds of lines later....
print(g.USER)

我只花了几分钟测试了两个不同的Python多模块程序,目前为止它完美地运行。


2

当你使用from config import mySharedThing时,命名空间问题就会出现。这一点不可过于强调。

在其他地方使用from是可以的。

你甚至可以拥有一个完全为空的配置模块。

# my_config.py
pass

# my_other_module.py
import my_config

def doSomething():
    print(my_config.mySharedThing.message)

# main.py
from dataclasses import dataclass
from my_other_module import doSomething
import my_config

@dataclass
class Thing:
    message: str

my_config.mySharedThing = Thing('Hey everybody!')
doSomething()

结果:

$ python3 main.py
Hey everybody!

但是使用从from导入的对象将会让您走上沮丧之路。

# my_other_module.py
from my_config import mySharedThing

def doSomething():
    print(mySharedThing.message)

结果:

$ python3 main.py
ImportError: cannot import name 'mySharedThing' from 'my_config' (my_config.py)

也许您会尝试像这样修复它:
# my_config.py
mySharedThing = None

结果:

$ python3 main.py
AttributeError: 'NoneType' object has no attribute 'message'

也许你会找到这个页面并尝试通过添加一个init()方法来解决它。

但整个问题在于from


2
我刚看到这篇文章,想分享我的解决方案,以防有人和我一样处于同样的情况,即开发的程序中有很多文件,并且你没有时间仔细考虑模块的整个导入顺序(如果你从一开始就没有正确地考虑这个问题,就像我一样)。
在这种情况下,在初始化全局变量的脚本中,只需编写一个类,例如:
class My_Globals:
  def __init__(self):
    self.global1 = "initial_value_1"
    self.global2 = "initial_value_2"
    ...

然后使用,而不是在脚本中初始化全局变量的那一行,改为使用

global1 = "initial_value_1"

使用

globals = My_Globals()

然后我可以通过以下方式检索/更改任何这些全局变量的值:

globals.desired_global

在任何脚本中,这些更改也会自动应用于使用它们的所有其他脚本。现在所有问题都已解决,只需使用之前因此帖子/讨论中提到的问题而失败的确切相同的导入语句即可。我简单地考虑了全局对象属性会动态更改而无需考虑/更改任何导入逻辑,与简单导入全局变量相比,这绝对是解决此类问题的最快最简便(以供以后访问)方法。


0

我曾经在互联网上看到过一种方法(很抱歉,无法记得是在哪里看到的),自从那时起我就一直在使用:

  1. 创建一个名为(例如)variables.py 的文件,在其中声明所有全局变量,例如:

    variable1=0.0 variable2="我的文本"

  2. 在任何需要调用这些变量的地方(例如在其他文件中)调用它们:

    variables.variable1=1(这里将其赋值为1)

编辑:找到了一个例子:https://www.edureka.co/community/52900/how-do-i-share-global-variables-across-modules-python


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接