【问题标题】:How to make a cross-module variable?如何制作跨模块变量?
【发布时间】:2010-09-13 15:23:07
【问题描述】:

__debug__ 变量很方便,部分原因是它会影响每个模块。如果我想创建另一个以相同方式工作的变量,我该怎么做?

变量(让我们保持原始状态并称其为“foo”)不必是真正的全局变量,因为如果我在一个模块中更改 foo,它会在其他模块中更新。如果我可以在导入其他模块之前设置 foo 然后他们会看到相同的值,我会很好。

【问题讨论】:

    标签: python module global


    【解决方案1】:

    如果您需要一个全局跨模块变量,也许只需简单的全局模块级变量就足够了。

    a.py:

    var = 1
    

    b.py:

    import a
    print a.var
    import c
    print a.var
    

    c.py:

    import a
    a.var = 2
    

    测试:

    $ python b.py
    # -> 1 2
    

    真实世界的例子:Django's global_settings.py(虽然在 Django 应用程序中设置是通过导入 object django.conf.settings 来使用的)。

    【讨论】:

    • 更好,因为它避免了可能的命名空间冲突
    • 如果您要导入的模块(在本例中为 a.py)包含 main() 怎么办?有关系吗?
    • @sedeh:没有。如果 a.py 也作为脚本运行,则在其中使用 if __name__=="__main__" 保护以避免在导入时运行意外代码。
    • 在现实世界中,您必须小心使用此解决方案。如果程序员使用“from a import var”获取您的“全局”变量,(在 c.py 中尝试这种变体)他们会在导入时获得该变量的副本。
    • @PaulWhipp:错误(提示:使用id() 来检查身份)
    【解决方案2】:

    我不以任何方式、形式或形式支持此解决方案。但是,如果您将一个变量添加到 __builtin__ 模块,它将像来自任何其他包含 __builtin__ 的模块的全局变量一样被访问——默认情况下,这就是所有这些。

    a.py 包含

    print foo
    

    b.py 包含

    import __builtin__
    __builtin__.foo = 1
    import a
    

    结果是打印了“1”。

    编辑: __builtin__ 模块可作为本地符号 __builtins__ 使用——这就是这两个答案之间存在差异的原因。另请注意,__builtin__ 在 python3 中已重命名为 builtins

    【讨论】:

    • 有什么理由不喜欢这种情况吗?
    • 一方面,它打破了人们在阅读代码时的期望。 “这里使用的这个 'foo' 符号是什么?为什么我看不到它是在哪里定义的?”
    • 如果 Python 的未来版本开始使用您选择的实际内置名称,它也很容易造成严重破坏。
    • 这是一个很好的解决方案,例如与导入的模块共享数据库连接。作为健全性检查,我确保导入的模块断言hasattr(__builtin__, "foo")
    • 对于任何阅读此答案的人:不要!做 !这个!真的,不要。
    【解决方案3】:

    定义一个模块(称之为“globalbaz”)并在其中定义变量。所有使用这个“伪全局”的模块都应该导入“globalbaz”模块,并使用“globalbaz.var_name”引用它

    无论更改的位置如何,这都有效,您可以在导入之前或之后更改变量。导入的模块将使用最新的值。 (我在一个玩具示例中对此进行了测试)

    为了澄清,globalbaz.py 看起来像这样:

    var_name = "my_useful_string"
    

    【讨论】:

      【解决方案4】:

      我相信在很多情况下它确实是有意义的,并且它简化了编程以拥有一些跨多个(紧密耦合)模块已知的全局变量。本着这种精神,我想详细说明一下拥有一个由需要引用它们的模块导入的全局模块的想法。

      当只有一个这样的模块时,我将其命名为“g”。在其中,我为我打算视为全局的每个变量分配默认值。在使用其中任何一个的每个模块中,我不使用“from g import var”,因为这只会导致一个局部变量,该变量仅在导入时从 g 初始化。我以 g.var 和“g”的形式进行大多数引用。不断提醒我正在处理其他模块可能访问的变量。

      如果这样一个全局变量的值要在模块中的某个函数中频繁使用,那么该函数可以制作一个本地副本:var = g.var。但是,重要的是要意识到对 var 的赋值是本地的,如果没有在赋值中显式引用 g.var,就无法更新全局 g.var。

      请注意,您还可以让模块的不同子集共享多个此类全局模块,以使事情得到更严格的控制。我为我的全局模块使用短名称的原因是为了避免因为它们的出现而使代码过于混乱。只需一点经验,它们就变得足够记忆,只有 1 或 2 个字符。

      当 x 尚未在 g 中定义时,仍然可以对 g.x 进行赋值,然后不同的模块可以访问 g.x。然而,即使解释器允许,这种方法也不是那么透明,我确实避免了。由于赋值的变量名称拼写错误,仍然有可能在 g 中意外创建一个新变量。有时,对 dir(g) 的检查有助于发现此类意外可能出现的任何意外名称。

      【讨论】:

      • 这个有趣的观察解决了我的问题:“我不使用“from g import var”,因为这只会产生一个局部变量,该变量仅在导入时从 g 初始化。假设“from..import”与“import”相同似乎是合理的,但事实并非如此。
      【解决方案5】:

      您可以将一个模块的全局变量传递给另一个模块:

      在模块 A 中:

      import module_b
      my_var=2
      module_b.do_something_with_my_globals(globals())
      print my_var
      

      在模块 B 中:

      def do_something_with_my_globals(glob): # glob is simply a dict.
          glob["my_var"]=3
      

      【讨论】:

        【解决方案6】:

        全局变量通常是个坏主意,但您可以通过分配给__builtins__ 来做到这一点:

        __builtins__.foo = 'something'
        print foo
        

        此外,模块本身就是可以从任何模块访问的变量。所以如果你定义了一个名为my_globals.py的模块:

        # my_globals.py
        foo = 'something'
        

        那么你也可以在任何地方使用它:

        import my_globals
        print my_globals.foo
        

        使用模块而不是修改 __builtins__ 通常是执行此类全局变量的更简洁的方法。

        【讨论】:

        • __builtins__ 是一个 CPython 特性,你真的不应该使用它 - 最好使用 __builtin__(或 Python3 中的 builtins)作为 the accepted answer 显示
        【解决方案7】:

        您已经可以使用模块级变量执行此操作。无论从哪个模块导入模块,它们都是相同的。因此,您可以在任何有意义的模块中将变量设置为模块级变量,并从其他模块访问或分配给它。最好调用一个函数来设置变量的值,或者使其成为某个单例对象的属性。这样,如果您最终需要在变量更改时运行一些代码,您可以在不破坏模块的外部接口的情况下这样做。

        这通常不是做事的好方法——很少使用全局变量——但我认为这是最干净的方法。

        【讨论】:

          【解决方案8】:

          我想发布一个答案,即存在找不到变量的情况。

          循环导入可能会破坏模块行为。

          例如:

          first.py

          import second
          var = 1
          

          second.py

          import first
          print(first.var)  # will throw an error because the order of execution happens before var gets declared.
          

          main.py

          import first
          

          在这个例子中应该很明显,但在大型代码库中,这可能真的很混乱。

          【讨论】:

            【解决方案9】:

            我想知道是否可以通过使用类命名空间而不是全局/模块命名空间来传递变量值,从而避免使用全局变量的一些缺点(参见例如http://wiki.c2.com/?GlobalVariablesAreBad)。下面的代码表明这两种方法本质上是相同的。如下所述,使用类命名空间有一点优势。

            以下代码片段还显示,属性或变量可以在全局/模块命名空间和类命名空间中动态创建和删除。

            wall.py

            # Note no definition of global variables
            
            class router:
                """ Empty class """
            

            我将此模块称为“墙”,因为它用于反弹变量。它将充当临时定义空类“路由器”的全局变量和类范围属性的空间。

            source.py

            import wall
            def sourcefn():
                msg = 'Hello world!'
                wall.msg = msg
                wall.router.msg = msg
            

            此模块导入 wall 并定义单个函数 sourcefn,该函数定义消息并通过两种不同的机制发出消息,一种通过全局变量,另一种通过路由器函数。请注意,变量wall.msgwall.router.message 是首次在其各自的命名空间中定义。

            dest.py

            import wall
            def destfn():
            
                if hasattr(wall, 'msg'):
                    print 'global: ' + wall.msg
                    del wall.msg
                else:
                    print 'global: ' + 'no message'
            
                if hasattr(wall.router, 'msg'):
                    print 'router: ' + wall.router.msg
                    del wall.router.msg
                else:
                    print 'router: ' + 'no message'
            

            这个模块定义了一个函数destfn,它使用两种不同的机制来接收源发出的消息。它允许变量“msg”可能不存在。 destfn 也会在变量显示后将其删除。

            main.py

            import source, dest
            
            source.sourcefn()
            
            dest.destfn() # variables deleted after this call
            dest.destfn()
            

            该模块依次调用之前定义的函数。第一次调用dest.destfn 后,变量wall.msgwall.router.msg 不再存在。

            程序的输出是:

            全球:世界你好!
            路由器:世界你好!
            全局:没有消息
            路由器:没有消息

            以上代码片段表明模块/全局和类/类变量机制本质上是相同的。

            如果要共享大量变量,可以通过使用几个墙型模块来管理命名空间污染,例如wall1、wall2 等或通过在单个文件中定义多个路由器类型的类。后者稍微整洁一些,因此可能代表使用类变量机制的边际优势。

            【讨论】:

              【解决方案10】:

              这听起来像是在修改 __builtin__ 命名空间。这样做:

              import __builtin__
              __builtin__.foo = 'some-value'
              

              不要直接使用__builtins__(注意额外的“s”)——显然这可以是字典或模块。感谢 ΤZΩΤZΙΟΥ 指出这一点,更多内容可以在here 找到。

              现在foo 可以在任何地方使用。

              我一般不建议这样做,但使用它取决于程序员。

              必须按照上面的方法进行赋值,设置foo = 'some-other-value'只会设置在当前命名空间中。

              【讨论】:

              • 我记得(来自 comp.lang.python)应该避免直接使用 builtins;相反,按照 Curt Hagenlocher 的建议,导入 builtin 并使用它。
              【解决方案11】:

              我将它用于我觉得确实缺少的几个内置原始函数。一个例子是 find 函数,它与 filter、map、reduce 具有相同的使用语义。

              def builtin_find(f, x, d=None):
                  for i in x:
                      if f(i):
                          return i
                  return d
              
              import __builtin__
              __builtin__.find = builtin_find
              

              一旦运行(例如,通过在入口点附近导入),所有模块都可以使用 find(),就好像它是内置的一样。

              find(lambda i: i < 0, [1, 3, 0, -5, -10])  # Yields -5, the first negative.
              

              注意:当然,您可以使用 filter 和另一行来测试零长度,或者使用 reduce 来测试一种奇怪的行,但我一直觉得这很奇怪。

              【讨论】:

                【解决方案12】:

                我可以通过使用字典来实现跨模块可修改(或可变)变量:

                # in myapp.__init__
                Timeouts = {} # cross-modules global mutable variables for testing purpose
                Timeouts['WAIT_APP_UP_IN_SECONDS'] = 60
                
                # in myapp.mod1
                from myapp import Timeouts
                
                def wait_app_up(project_name, port):
                    # wait for app until Timeouts['WAIT_APP_UP_IN_SECONDS']
                    # ...
                
                # in myapp.test.test_mod1
                from myapp import Timeouts
                
                def test_wait_app_up_fail(self):
                    timeout_bak = Timeouts['WAIT_APP_UP_IN_SECONDS']
                    Timeouts['WAIT_APP_UP_IN_SECONDS'] = 3
                    with self.assertRaises(hlp.TimeoutException) as cm:
                        wait_app_up(PROJECT_NAME, PROJECT_PORT)
                    self.assertEqual("Timeout while waiting for App to start", str(cm.exception))
                    Timeouts['WAIT_JENKINS_UP_TIMEOUT_IN_SECONDS'] = timeout_bak
                

                启动test_wait_app_up_fail时,实际超时时间为3秒。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2020-05-03
                  • 2011-06-25
                  • 2016-10-20
                  • 2018-02-02
                  • 2017-06-08
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多