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

时间:2019-02-12 21:02:41

标签: python namespaces jupyter built-in

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

我发现在笔记本中定义这些变量时可以使用以下功能:

import builtins

builtins.my_variable = my_value

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

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

另一种有效的方法:

import helper
helper.my_variable = my_value

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

2 个答案:

答案 0 :(得分:0)

首先,我要说的是,我试图避开全局状态,因为有时被说成是不可避免的。在Python中,创建全局状态的最简单方法是只包含一个带有变量的模块。例如,

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

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

import constants

,并且在具有功能的模块中,您可以执行相同的操作。

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

CAVEAT Python实际上没有常量的概念,因为您可以执行类似constants.constant1 = 'Some new value'的操作,这将改变{{1}的新赋值}。

答案 1 :(得分:0)

开始:我建议将变量传递给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. 可能导致dict的基础builtins增大大小,减少内存访问位置
  2. 可能会创建其他冲突,以查找特定的内置函数(仅仅因为您的新属性存在而使对有用的内置函数的访问变慢)
  3. 可能在其他模块中隐藏错误,例如一个模块应该创建一个与您在builtins中推入的名字相同的变量,但是要么这样做失败,然后默默地使用您的定义,或者更糟糕的是,有意地依靠不存在的名称来懒惰地初始化其自己的属性,现在再也不会使用您的定义对其进行初始化
  4. 使代码难以维护;如果我在模块中看到对名为foo的变量的引用,则希望能够在模块中找到定义,或者通过查看导入(from x import *语法来找到定义的来源这一点让人感到困惑,这就是为什么静态代码检查器经常将from x import *报告为错误)的原因。如果它是在其他不相关的模块中秘密创建的,并且被推到builtins中(或者更糟的是,从许多不同的不相关模块中进行了变异),那么我将对犯下这种残暴行为的人感到愤怒。

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

您的helpers模块方法并不完全糟糕,尽管在实践中我建议直接在as aqual.abdullah suggestshelpers.py定义共享值并将其视为常量,而不是使用常量。其他模块在此处创建它们(这会引起许多与修改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的全局状态进行相互不兼容更改的问题(因为该状态存储在实例中,这些实例是独立拥有和管理的)。使用带有默认参数的初始化程序意味着您永远不会丢失原始默认值。您可以随时制作一份新副本。