【问题标题】:How to avoid using global variables?如何避免使用全局变量?
【发布时间】:2019-12-13 22:28:39
【问题描述】:

我使用全局变量,但我读到它们不是一个好习惯或 Pythonic。我经常使用的函数会给出许多我需要在主函数中使用的是/否变量。比如不使用全局变量怎么写下面的代码?

def secondary_function():
    global alfa_is_higher_than_12
    global beta_is_higher_than_12

    alfa = 12
    beta = 5

    if alfa > 10:
        alfa_is_higher_than_12 = "yes"
    else:
        alfa_is_higher_than_12 = "no"

    if beta > 10:
        beta_is_higher_than_12 = "yes"
    else:
        beta_is_higher_than_12 = "no"

def main_function():
    global alfa_is_higher_than_12
    global beta_is_higher_than_12

    secondary_function()

    if alfa_is_higher_than_12=="yes":
        print("alfa is higher than 12")
    else:
        print("alfa isn't higher than 12")

    if beta_is_higher_than_12=="yes":
        print("beta is higher than 12")
    else:
        print("beta isn't higher thant 12")

main_function()

【问题讨论】:

  • 创建类并使用类或实例属性dzone.com/articles/…
  • 请注意:一旦您拥有完整、可运行、工作的代码,您就可以将其发布到 Code Review 上。我看到可以进行多项其他改进,但这超出了 Stack Overflow 的范围。

标签: python python-3.x function if-statement global-variables


【解决方案1】:

术语“Pythonic”不适用于这个主题——在任何编程语言和范式中使用这样的全局变量都是不好的做法,并且不是 Python 特有的。

global 关键字是 Python 提供的工具,可让您选择退出 encapsulation 并打破变量的自然 scope。封装意味着您的每个组件都是一个逻辑的、自包含的单元,应该作为一个黑盒和performs one thing 工作(注意:这一件事是概念性的,可能包含许多,可能不是琐碎的子步骤)而不改变全局状态或产生side effects。原因是模块化:如果程序中出现问题(并且会出现问题),强大的封装可以很容易地确定发生故障的组件在哪里。

封装使代码更易于重构、维护和扩展。如果您需要一个组件以不同的方式运行,那么应该很容易将其移除或调整,而不会导致这些修改导致系统中其他组件发生变化的多米诺骨牌效应。

强制封装的基本工具包括类、函数、参数和return 关键字。语言通常会提供类似效果的模块、命名空间和闭包,但最终目标始终是限制范围并允许程序员创建松散耦合的抽象。

函数通过参数接收输入并通过返回值产生输出。您可以将返回值分配给调用范围内的变量。您可以将参数视为调整函数行为的“旋钮”。在函数内部,变量只是函数使用的临时存储空间,需要生成一个返回值然后消失。

理想情况下,函数写成pureidempotent;也就是说,它们不会修改全局状态并在多次调用时产生相同的结果。与其他语言相比,Python 在这方面的严格程度要低一些,使用某些in-place 函数(如sortrandom.shuffle)是很自然的。这些是证明规则的例外(如果您对 sortingshuffling 有所了解,由于所使用的算法和对效率的需求,它们在这些上下文中是有意义的)。

就地算法是不纯且非幂等的,但如果它修改的状态仅限于其参数及其文档和返回值(通常为None)支持这一点,则行为是可预测的并且可以理解。

那么这一切在代码中是什么样子的?不幸的是,您的示例似乎是做作的,并且其目的/目标不清楚,因此没有直接的方法可以对其进行转换,从而使封装的优势显而易见。

这里列出了这些函数中除了修改全局状态之外的一些问题:

  • 使用"yes""no" 字符串字面量代替True/False 布尔值。
  • hardcoding 函数中的值,使它们完全单一用途(它们也可以内联)。
  • printing in 函数(请参阅上面的副作用备注——如果他们愿意,最好返回值并让调用范围打印)。
  • secondary_function这样的通用变量名(我假设这相当于foo/bar的例子,但它仍然不能证明它们存在的理由,因此很难修改为教学示例)。

但无论如何,这是我的镜头:

if __name__ == "__main__":
    alpha = 42
    beta = 6
    print("alpha %s higher than 12" % ("is" if alpha > 12 else "isn't"))
    print("beta %s higher than 12" % ("is" if beta > 12 else "isn't"))

我们可以看到不需要所有的功能——只要在需要比较的地方写alpha > 12,需要打印的时候调用print。函数的一个缺点是它们可以隐藏重要的逻辑,因此如果它们的名称和“合同”(由名称、docstring 和参数/返回值定义)不清楚,它们只会混淆函数的客户端(通常是您自己)。

为了便于说明,假设您经常调用此格式化程序。然后,有理由抽象;调用代码会变得很麻烦repetitive。您可以将格式化代码移动到辅助函数并传递任何动态数据以注入模板:

def fmt_higher(name, n, cutoff=12):
    verb = "is" if n > cutoff else "isn't"
    return f"{name} {verb} higher than {cutoff}"

if __name__ == "__main__":
    print(fmt_higher("alpha", 42))
    print(fmt_higher("beta", 6))
    print(fmt_higher("epsilon", 0))
    print(fmt_higher(name="delta", n=2, cutoff=-5))

我们可以更进一步,假设n > cutoff 是一个复杂得多的测试,其中包含许多小步骤,如果留在fmt_higher 中会违反单一职责。也许复杂的测试在代码的其他地方使用,并且可以推广以支持这两种用例。

在这种情况下,您仍然可以使用参数和返回值而不是 global,并对谓词执行与格式化程序相同的抽象:

def complex_predicate(n, cutoff):
    # pretend this function is much more 
    # complex and/or used in many places...
    return n > cutoff

def fmt_higher(name, n, cutoff=12):
    verb = "is" if complex_predicate(n, cutoff) else "isn't"
    return f"{name} {verb} higher than {cutoff}"

if __name__ == "__main__":
    print(fmt_higher("alpha", 42))
    print(fmt_higher("beta", 6))
    print(fmt_higher("epsilon", 0))
    print(fmt_higher(name="delta", n=2, cutoff=-5))

只有在有足够的理由进行抽象时才进行抽象(调用代码被阻塞或当您多次重复类似的代码块是经典的经验法则)。当你做抽象时,做它properly

【讨论】:

    【解决方案2】:

    有人可能会问,你可能有什么理由来构造你的代码,但假设你有你的理由,你可以从你的辅助函数中返回值:

    def secondary_function():
    
      alfa = 12
      beta = 5
    
      if alfa > 10:
          alfa_is_higher_than_12 = "yes"
      else:
          alfa_is_higher_than_12 = "no"
    
      if beta > 10:
          beta_is_higher_than_12 = "yes"
      else:
          beta_is_higher_than_12 = "no"
    
      return alfa_is_higher_than_12, beta_is_higher_than_12
    
    
    def main_function():
    
      alfa_is_higher_than_12, beta_is_higher_than_12 = secondary_function()
    
      if alfa_is_higher_than_12=="yes":
          print("alfa is higher than 12")
      else:
          print("alfa isn't higher than 12")
    
      if beta_is_higher_than_12=="yes":
          print("beta is higher than 12")
      else:
          print("beta isn't higher thant 12")
    

    【讨论】:

    • 除此之外,我经常将返回值包装在一个泛型类中,并将需要返回的任何内容塞入其中。 Python 足够灵活,可以动态添加字段,因此它已成为我链接此类内容的一种非常方便的方式 :)
    • 我从来没有学会正确使用类,因为我总能找到一种方法来解决它们。现在我应该开始使用它们了。
    • 我并不为此感到自豪。因为它,我不觉得自己像个 Python 程序员。如何使用类编写上面相同的示例?
    • 上述评论是一般性的:类对于这种情况是不合适的,因为看起来你正在尝试做什么(它并不完全清楚......看起来只是打印> 12 for几个变量)不需要状态。当您有相关实体时使用类。例如,如果您的游戏在屏幕上有许多怪物,每个怪物都有自己的位置/状态/健康,创建一个通用的 Monster 类并创建该类的 n 实例是有意义的。如果某些怪物会飞,那么您可以将MonsterFlyingMonster 子类化,并且只更改一些函数或属性。
    【解决方案3】:

    永远不要写“全局”。那么你确定你没有引入任何全局变量。

    您也可以将值作为参数传递:

    def secondary_function():
        alfa = 12
        beta = 5
    
        if alfa > 10:
            alfa_is_higher_than_12 = "yes"
        else:
            alfa_is_higher_than_12 = "no"
    
        if beta > 10:
            beta_is_higher_than_12 = "yes"
        else:
            beta_is_higher_than_12 = "no"
        return alfa_is_higher_than_12, beta_is_higher_than_12
    
    
    def main_function(alfa_is_higher_than_12, beta_is_higher_than_12):
        if alfa_is_higher_than_12=="yes":
            print("alfa is higher than 12")
        else:
            print("alfa isn't higher than 12")
    
        if beta_is_higher_than_12=="yes":
            print("beta is higher than 12")
        else:
            print("beta isn't higher thant 12")
    
    main_function(*secondary_function())
    

    【讨论】:

    • 这是一个非常好的建议——你基本上不需要这个关键字。话虽如此,这并不能保证函数不会改变全局状态,所以我会修改帖子以显示一个示例,例如,将附加到全局列表作为与直接使用 global 一样不好的做法。
    • 完全同意这一点。
    【解决方案4】:

    您可以在字典中来回传递数据。

    将整个字典传递给一个函数,修改数据,然后返回更新后的字典

    【讨论】:

      猜你喜欢
      • 2012-12-19
      • 1970-01-01
      • 1970-01-01
      • 2012-05-04
      • 2016-03-19
      • 2012-02-02
      • 2015-12-26
      • 2020-09-02
      • 2020-10-02
      相关资源
      最近更新 更多