为什么全局变量是有害的?

182

我正试图了解为什么在Python(以及编程普遍情况下),使用global被认为是不良实践。有人可以解释一下吗?也欢迎提供更多信息的链接。

4个回答

225

这与Python无关;在任何编程语言中,全局变量都不好。

然而,全局常量在概念上并不同于全局变量;全局常量是完全无害的。在Python中,两者之间的区别纯粹是按照惯例:CONSTANTS_ARE_CAPITALIZEDglobals_are_not

全局变量不好的原因在于它们使函数具有隐藏(不明显、令人惊讶、难以检测、难以诊断)的副作用,导致复杂性增加,可能导致意大利面条代码

然而,在函数式编程中,合理使用全局状态(以及本地状态和可变性)是可以接受的,无论是为了算法优化、降低复杂性、缓存和备忘,还是将源自主要命令式代码库的结构移植到实践中。

总之,你的问题可以有很多种回答,所以最好的办法就是去谷歌搜索“为什么全局变量不好”。以下是一些示例:

如果你想更深入地了解为什么副作用很重要以及许多其他启发性的事情,你应该学习函数式编程:

  • 函数式编程 - 维基百科

  • 51

    理论上讲,全局变量和“状态”(state)总是不好的,但事实上,如果你查看Python包目录,你会发现大多数模块都以一堆全局声明开始。显然,人们对此没有问题。

    具体来说,对于Python而言,全局变量的可见性仅限于模块,因此没有影响整个程序的“真正”的全局变量-这使得它们更少有害。另一个要点是:没有const,因此当需要常量时,必须使用全局变量。

    在我的实践中,如果我在函数中修改全局变量,我总是使用global进行声明,即使在技术上并不需要,例如:

    cache = {}
    
    def foo(args):
        global cache
    
        cache[args] = ...
    
    这使得全局变量的操作更容易被追踪。

    7
    在很多方面,Python 中的模块类似于单例类,而模块全局变量则类似于类属性。 - Corley Brigman
    13
    单例类实际上经常遭受通常归因于全局变量的相同问题 :) - Erik Kaplun
    4
    Python模块的可见性不仅限于模块本身,它们在整个解释器中都是可用的,而且(这是重要的)对其进行的更改会影响整个解释器。它不像创建实例的字符串那样...就像修改所有字符串实例一样。动态替换代码有风险。 - graffic
    最后,对我来说这听起来就像是“人们说喝酒有害,但每个人都在喝”。如果你不使用类,没有全局变量的生活会更加困难一些。 - m3nda
    3
    除了常量,大多数模块不会以定义全局变量为开头。全局变量指的是变量或全局状态,而非常量,因此应尽量避免使用。请注意,此处仅为翻译,未包含任何解释或其他内容。 - BlackJack
    4
    使用全局变量是一个可怕的想法,原因之一可能是无法正确测试更新某个任意字典的函数,因为该字典存在于“某个地方”。具有全局变量的代码库实际上无法被证明是功能正确的。 - Tomasz Sosiński

    18

    个人意见是,在函数逻辑中使用全局变量意味着其他代码可以更改该函数的逻辑和预期输出,这将使调试非常困难(尤其是在大型项目中),同时也会使测试更加困难。

    此外,如果考虑其他人阅读您的代码(开源社区、同事等),他们将很难理解全局变量的设置位置、更改位置以及从这个全局变量中期望什么,而不是通过阅读函数定义本身来确定其功能的独立函数。

    (可能)违反纯函数定义

    我认为,一个干净且(几乎)无错误的代码应该具有尽可能纯粹的函数(参见纯函数)。纯函数具有以下条件:

    1. 给定相同的参数值,函数始终计算出相同的结果值。函数结果值不能依赖于任何可能在程序执行过程中或在程序的不同执行之间发生变化的隐藏信息或状态,也不能依赖于来自I/O设备的任何外部输入(通常情况下除外,详见下文)。
    2. 计算结果不会导致任何语义可观察的副作用或输出,例如变异的可变对象或I/O设备的输出。

    使用全局变量至少违反了上述条件之一,如果不是两者都违反了。因为外部代码可能会导致意想不到的结果。

    另一个明确的纯函数定义是:“纯函数是一个将所有输入作为显式参数,并生成所有输出作为显式结果的函数。”[1]。使用全局变量违反了纯函数的理念,因为未显式给出或返回一个输入和可能的一个输出(即全局变量)。

    (可能)违反单元测试F.I.R.S.T原则

    如果考虑单元测试和F.I.R.S.T原则(快速测试、独立测试、可重复、自验证和及时)的话,很可能会违反独立测试原则(这意味着测试不依赖于其他测试)。

    全局变量(并非总是如此,但至少在我目前所见的大多数情况下)用于准备和传递结果给其他函数。这也违反了这个原则。如果全局变量被用于这种方式(即函数X中使用的全局变量必须先在函数Y中设置),这意味着要对函数X进行单元测试,你必须先运行测试/运行函数Y。

    将全局变量视为常量

    另一方面,正如其他人已经提到的那样,如果全局变量用作“常量”,可能会稍微好一些,因为语言不支持常量。然而,我总是更喜欢使用类并将“常量”作为类成员,而不是使用全局变量。如果您有一个需要两个不同类共享全局变量的代码,则可能需要重构您的解决方案并使您的类彼此独立。

    我不相信全局变量不能使用。但是,如果它们被使用,作者应该考虑一些原则(也许包括上述原则和其他软件工程原则和最佳实践),以获得更清洁和几乎没有错误的代码。


    1
    我喜欢“全局变量作为常量是一个问题”这个说法……因为如果你正在进行面向对象的设计……它确实是一个问题。除了IdCreator类,为什么还有其他人需要知道ID_LEN? - Erik Aronesty

    4

    它们是必需的,屏幕就是一个很好的例子。然而,在多线程环境或涉及许多开发人员的情况下,实际上常常会出现一个问题:谁设置或清除了它(错误地)?根据架构,分析可能是昂贵的,并且经常需要。虽然读取全局变量可能没问题,但写入必须受控制,例如通过单个线程或线程安全类。因此,全局变量引起了高开发成本的担忧,这可能是它们自身被认为是邪恶的后果。因此,通常最好将全局变量数量保持在较低水平。


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