【问题标题】:Using builtins to store global variables across modules使用内置函数跨模块存储全局变量
【发布时间】:2019-07-06 14:31:22
【问题描述】:

我有一个 Jupyter 笔记本,我只想用它来运行在 python 'helper' 文件中定义的一堆函数。然而,笔记本确实有一些用户可以更改的变量(所以它们就像我想的常量)。我希望这些变量也可以从帮助文件中访问。我宁愿不必将这些变量传递给笔记本中​​的每个函数调用。

在 notebook 中定义这些变量时,我发现以下方法有效:

import builtins

builtins.my_variable = my_value

变量“my_variable”现在可以在 Jupyter 笔记本和帮助文件中使用。

注意:以这种方式定义变量后,如果我在笔记本中键入 help(builtins) 并一直滚动到底部,我会在“数据”部分下找到我的变量。

另一件有效的事情:

import helper
helper.my_variable = my_value

有人可以解释这些东西为什么/如何工作,如果使用它们有任何问题,如果有,可能是更好的方法吗?

【问题讨论】:

    标签: python namespaces jupyter built-in


    【解决方案1】:

    首先,让我说我尽量避免全局状态,现在说有时它是不可避免的。在 Python 中,创建全局状态的最简单方法是只使用一个包含变量的模块。例如,

    # constants.py
    constant1 = 'A Constant'
    constant2 = 'Another Constant'
    constant3 = 'Absolutely last constant'
    

    在您的 Jupyter 笔记本中,您可以执行以下操作:

    import constants
    

    在具有您的功能的模块中,您也可以这样做。

    基本上,您的第二种方法是可行的方法。

    CAVEAT Python 并没有真正意义上的常量,因为您可以执行类似constants.constant1 = 'Some new value' 的操作,这会改变constants.constant1 的新赋值的值。

    【讨论】:

    • 因此,如果我愿意,我什至可以创建一个 empty python 文件constants,然后通过constants.constant1 = 'something' 等在我的主文件中设置它的值?
    • 是的,您也可以这样做,但是如果您正在动态添加全局常量,您可能需要考虑以一种使它们在您实际需要的范围内可用的方式来组织您的代码在。
    【解决方案2】:

    开始:我建议将变量传递给helper 模块函数,而不是依赖全局状态。如果您需要某种全局状态并且不想一遍又一遍地传递它,请考虑将一些函数分组到类中,将状态传递给类的初始化程序并存储在实例中。这样,调用实例的方法会隐式传递实例,从而传递所需的状态,并且重复最少。我将在此答案的底部提供一个简单的示例。

    修改builtins 的内容将意味着用你的值膨胀最后的位置查找。可以想象,这可能会降低所有代码的速度(尤其是如果这意味着调整 builtins 模块的底层 dict 的大小,可能使其不再适合缓存)。

    从面向未来的角度来看,有occasional proposals to optimize lookups in builtins based on its presumed static contents;虽然大多数提案处理 builtins 被修改的情况,但所述优化的效果可能会丢失(恢复为仅按需执行查找)。这也有先例;在 CPython 3.3 之前,建议在 __init__ 完成之前创建实例的所有属性,之后不应删除或添加任何属性(给定属性的值仍然可以更改)。但是在 3.2 和更早的版本中,忽略这个建议并没有真正的惩罚。 Starting in 3.3, classes that followed this advice got a massive reduction in per instance memory overhead;没有遵循建议的课程一无所获。

    修改builtins还有其他问题,例如:

    1. 可能导致builtins 的底层dict 大小增加,减少内存访问局部性
    2. 可能会在查找特定内置函数时产生额外的冲突(因为新属性存在而减慢对有用内置函数的访问速度)
    3. 可能隐藏其他模块中的错误,例如一个模块应该创建一个与您在本地推入 builtins 的名称相同的变量,但要么没有这样做,而是默默地使用您的定义,或者更糟糕的是,故意依赖不存在的名称来懒惰地初始化它自己的属性,并且现在永远不会初始化它,而是使用您的定义
    4. 使您的代码难以维护;如果我在模块中看到对名为 foo 的变量的引用,我希望能够在模块中找到定义,或者通过查看导入找到定义的来源(from x import * 语法阻碍了这一点,这这就是为什么静态代码检查器经常将from x import * 报告为错误的原因)。如果它是在其他不相关的模块中秘密创建并被推到builtins 中(或者更糟糕的是,从许多不同的不相关模块中变异),我会对犯下这种暴行的人感到愤怒。

    重点是,修改builtins 是个坏主意。它可能会起作用,但不要这样做。

    您的helpers 模块方法并不完全糟糕,但实际上我建议直接在as aqual.abdullah suggests 中定义helpers.py 中的共享值并将它们视为常量,而不是让其他模块在那里创建它们(这会导致许多与修改 builtins 相同的问题,只是问题范围更有限)。

    这些方法起作用的原因是模块大多只是字符串键控 Python dicts 周围的语法糖。您可以为 Python 中的大多数(尽管不是全部)对象添加新属性,并且模块本身就是对象(而不是该一般规则的例外之一)。

    helper.my_variable = my_value
    

    真的可以归结为:

    helper.__dict__['my_variable'] = my_value
    

    并且由于所有导入器都可以看到相同的helper(一个模块缓存在第一个import,并且所有后续imports 都返回对相同缓存副本的引用),所有这些都看到修改。


    我在顶部提到的更好的解决方案是改变:

    # helpers.py
    a = 1
    b = 2
    
    def func1():
        return a + b
    
    def func2():
        return a * b
    
    def func3():
        return a / b
    

    调用者在做:

    >>> import helper
    >>> helper.a = 5
    >>> helper.func1()
    

    到基于类的设计:

    # helpers.py
    class Helper:
        def __init__(self, a=1, b=2):
            self.a = 1
            self.b = 2
    
        def func1(self):
            return self.a + self.b
    
        def func2(self):
            return self.a * self.b
    
        def func3(self):
            return self.a / self.b
    

    用法在哪里:

    >>> import helpers
    >>> h = helpers.Helper(a=5)
    >>> h.func1()
    

    或者对于给定值集的单次使用:

    >>> helpers.Helper(a=5).func1()
    

    并且使用默认值只是:

    >>> helpers.Helper().func1()
    

    这避免了多个线程(或可重入代码)对helpers 的全局状态进行相互不兼容的更改的问题(因为现在状态存储在独立拥有和管理的实例中)。使用带有默认参数的初始化程序意味着您永远不会丢失原始默认值;您可以随时制作新副本。

    【讨论】:

    • 谢谢!您描述的“创建课程”方法听起来是个好主意。并感谢您的示例。
    猜你喜欢
    • 2016-10-20
    • 1970-01-01
    • 2017-06-02
    • 2012-10-26
    • 2022-01-22
    • 2014-02-18
    • 1970-01-01
    • 2021-05-16
    相关资源
    最近更新 更多