繁体   English   中英

使用内置函数跨模块存储全局变量

[英]Using builtins to store global variables across modules

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

我发现在笔记本中定义这些变量时,以下工作有效:

import builtins

builtins.my_variable = my_value

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

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

另一件事有效:

import helper
helper.my_variable = my_value

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

首先,让我说,我试图避免出现全球状态,因为有时候人们说这是不可避免的。 在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新分配的值的效果。

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

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

从面向未来的角度来看, 偶尔一些建议基于其假定的静态内容来优化builtins的查找 尽管大多数建议都处理修改builtins的情况,但是这些优化的效果可能会丢失(恢复为仅按需执行查找)。 这也有先例。 在CPython 3.3之前,建议在__init__完成之前创建实例的所有属性,并且之后不应该删除或将任何属性添加到实例(给定属性的值仍可以更改)。 但是在3.2和更早的版本中,忽略此建议并没有真正的惩罚。 从3.3开始,遵循该建议的类在每个实例上的内存开销大大减少了 没有遵循建议的课程没有任何收获。

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

  1. 可能导致潜在的dictbuiltins扩大规模,降低内存访问局部性
  2. 可能会创建其他冲突,以查找特定的内置函数(仅由于您的新属性存在而使对有用的内置函数的访问变慢)
  3. 可能会在其他模块中隐藏错误,例如,一个模块应该创建一个变量,其名称与您在本地builtins模块中推入的名称相同,但这样做要么失败,并且静默地使用您的定义,要么更糟,故意依赖不存在的名称初始化自己的属性,现在不再使用定义来初始化它
  4. 使您的代码难以维护; 如果我在模块中看到对名为foo的变量的引用,则希望能够在模块中找到定义,或通过查看from x import *来找到定义的源( from x import *语法可以看出,这是为什么静态代码检查器经常from x import *报告错误)。 如果它是在其他不相关的模块中秘密创建的,并且被builtins (或者更糟的是,从许多不相关的模块中进行了变异)推到了我的builtins ,那么我将对犯下这种残酷行为的人感到愤怒。

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

您的helpers模块方法并不完全糟糕,尽管在实践中,我建议直接在aers.abdullah的建议下helpers.py的共享值定义为aqual.abdullah并将其视为常量,而不是在此处创建其他模块(这会导致很多与修改builtins相同的问题,只是问题的范围更有限)。

这些方法行之有效的原因在于,模块大多只是围绕字符串键Python dict的语法糖。 您可以在Python中的大多数(尽管不是全部)对象中添加新属性,而模块本身就是对象(而不是该通用规则的例外之一)。

helper.my_variable = my_value

真正归结为:

helper.__dict__['my_variable'] = my_value

并且由于其所有导入程序都可以看到相同的helper程序(在第一次import缓存了一个模块,而所有后续import都返回了对同一缓存副本的引用),因此他们所有人都看到了修改。


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

# 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的全局状态进行相互不兼容更改的问题(因为该状态存储在实例中,这些实例是独立拥有和管理的)。 使用带有默认参数的初始化程序意味着您永远不会丢失原始默认值。 您可以随时制作一份新的副本。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM