如何避免使用全局变量?

新手上路,请多包涵

我使用全局变量,但我读到它们不是一个好的实践或 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()

原文由 Carl 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.4k
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")

原文由 sjc 发布,翻译遵循 CC BY-SA 4.0 许可协议

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

global 关键字是 Python 提供的工具,用于选择退出 封装 并打破变量的自然 范围。封装意味着你的每个组件都是一个逻辑上的、自包含的单元,应该作为一个黑盒子工作并 执行一件事(注意:这 件事 是概念性的,可能包含许多,可能不是平凡的,子步骤)没有改变全局状态或产生 副作用。原因是模块化:如果程序中出现问题(并且 它会),具有强大的封装性使得确定故障组件的位置变得非常容易。

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

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

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

理想情况下,函数被编写为 纯粹幂等 的;也就是说,它们不会修改全局状态并在多次调用时产生相同的结果。与其他语言相比,Python 在这方面稍微宽松一些,使用某些 就地 函数(如 sortrandom.shuffle )是很自然的。这些是证明规则的例外情况(如果您对 排序洗牌 有所了解,由于所使用的算法和效率的需要,它们在这些上下文中是有意义的)。

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

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

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

  • using "yes" and "no" string literals instead of True / False boolean values.
  • 在函数中 硬编码 值,使它们完全单一用途(它们也可以内联)。
  • print 在函数中调用(参见上面的副作用注释——如果他们愿意,更喜欢返回值并让调用范围打印)。
  • 通用变量名称,如 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 需要打印的地方。函数的一个缺点是它们可以用来隐藏重要的逻辑,所以如果它们的名称和“合同”(由名称、 文档字符串 和参数/返回值定义)不清楚,它们只会让客户感到困惑函数(通常是你自己)。

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

 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))

只有在有足够的理由进行抽象时才进行抽象(调用代码变得阻塞,或者当您多次重复类似的代码块时,这是经典的经验法则)。当你做抽象的时候,要 正确地 做。

原文由 ggorlen 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题