【问题标题】:Global variables in imported class functions in PythonPython中导入的类函数中的全局变量
【发布时间】:2013-03-14 06:25:07
【问题描述】:

我试图将我的代码分布在不同的文件中以提高可读性,但我遇到了在导入文件中引用未定义的全局名称的麻烦。有没有比在调用时故意将所有必要的值传递给函数更好的解决方案?

我的代码目前是这样的:

#foo.py

import bar

def main():
    a = 1
    b = bar.Bar()
    while True:
        a += 1
        b.incr()
        print a
        print b.c

if __name__ == '__main__':
    main()

#END foo.py

-

#bar.py

class Bar:
    def __init__(self):
        self.c = 0
    def incr(self):
        self.c += a

#END bar.py

它给了我 NameError: global name 'a' is not defined。我是否需要像这样重写 main():

def main():
        b = new bar.Bar()
        while True:
            a += 1
            b.incr(a)
            print a
            print b.c

然后像这样重写incr():

def incr(self,a):
            c += a

或者有没有更好的方法?

为了记录,上面的代码被大量抽象了。实际代码涉及大量类和函数,它们传递多种类型的许多变量,包括一些大型字典。

提前致谢!

【问题讨论】:

  • 由于全局变量通常不是一个好主意,在函数之间传递变量是更好的选择。
  • 感谢@PurityLake 和 MartijnPieters - 关于性能,这不是问题吗?是否有一个很好的解决方案可以将大量变量从函数传递到函数?将它们全部放入字典或字典字典中,也许?
  • 访问 locals 比访问 globals 更快,而且 globals 字典通常要大得多,使事情变得更糟。所以这可能不是问题;这可能是一种改进。无论如何,性能在这里不太可能是相关的。但如果这是一个问题,请将其作为Bar 实例上的对象属性。并使用 __slots__ 而不是实例字典。或者将Bar 移动到 Cython。或者……
  • 对象的教科书用法是将数据与函数关联起来。如果您有一些函数定期使用/修改的长期数据,那么这就是对象的用途。如果您发现您需要一个对象中的一些数据经常存在于另一个对象中,那么您可能需要继承、组合或重新设计您的代码。
  • @yoel: 将config 字典传递给每个对象的__init__ 怎么样,所以main 可以更改config['gravity'] = 2,而Rock 可以检查self.config['gravity'] 并找到新值?或者您可以将配置设置为类实例而不是字典,所以它是self.config.gravity。其中一个有问题吗?

标签: python python-2.7 global-variables


【解决方案1】:

如果您只是想知道它为什么不起作用:

首先,main 不是设置一个全局的a,而是一个本地的。如果你想让它成为全球性的,你必须明确:

def main():
    global a
    a = 1
    # ...

但这并不能解决问题,因为 Python 中的“全局”是每个模块的。换句话说,这只是创建了一个foo.a,但您在bar.Bar 中的代码将寻找bar.a,而这将不存在。

您可以import foo,然后根据需要访问foo.a。 (在这种情况下,循环依赖应该没有问题,if __name__ == '__main__' 不会被执行两次。)或者你可以假设foo 已经被导入,并使用sys.modules['foo'].a。或者你可以将afoo 注入到bar 的字典中。或者很多其他的技巧。但这些都是可怕的事情。

如果您无法制作全局作品,那么正确的答案几乎总是不使用全局作品。 (事实上​​,即使您可以制作出全球性的作品,这通常也是正确的答案。)

那么,你是怎么做到的呢?在不了解更多细节的情况下,很难猜测a 是否属于显式模块全局、类属性、实例属性、incr 的参数等。但考虑到什么,找出哪个最有意义a 代表,并这样做。


在 cmets 中,您建议您有一堆这些配置变量。如果您将它们全部包装在 dict 中并将 dict 传递给每个对象的构造函数,这将使事情变得更简单。

更重要的是,dict 是对可变对象的引用。复制它只是对同一个对象进行另一个引用。

main.py:

def main():
    configs = {}
    configs['gravity'] = 1.0
    rock = rps.Rock(configs)
    rock.do_stuff()
    configs['gravity'] = 2.1
    rock.do_stuff()
if __name__ == '__main__':
    main()

rps.py:

class Rock(object):
    def __init__(self, configs):
        self.configs = configs
    def do_stuff(self):
        print('Falling with gravity {}.'.format(self.configs['gravity']))

当你运行它时,它会打印如下内容:

Falling with gravity 1.0.
Falling with gravity 2.1.

您还没有修改值 configs['gravity'](这是一个不可变的浮点数;您无法修改它),但是您已经用不同的值替换了它(因为 configs 是可变的dict;你可以修改它就好了)。如果身份不清楚,可以尝试在各个地方打印出id(self.configs)id(self.configs['gravity'])等内容。

【讨论】:

  • 谢谢 - 只是为了确保我理解:如果 configs['gravity'] 等于 1 并且我说 configs['gravity'] = 2,它会删除旧的 'gravity' 并创建一个新的?那么当一个先前创建的对象去查找它时,它只会找到新的值?
  • @yoel:是的,你明白了。当您执行configs['gravity'] = 2 时,之后执行configs['gravity']foo['gravity'](如果foo 是对与configs 相同的字典的引用)的任何人都会看到2
【解决方案2】:

如果aBar的一个特征或者配置,它应该是它的一个实例或者类属性。

class Bar(object):
    class_step = 1
    def __init__(self, inst_step):
        self.inst_step = inst_step
        self.c = 0
        self.step_mode = 'class'
    def inc(self):
        self.c += Bar.class_step if self.step_mode == 'class' else self.inst_step

【讨论】:

  • 谢谢 - 不是。假设a 不仅被Bar 使用,还被SpamEggsToast 使用。
  • 然后在所有需要使用数据的类之间共享一个配置对象...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-03-01
  • 2013-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-02
相关资源
最近更新 更多