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

问题描述 投票:0回答:2

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

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

import builtins

builtins.my_variable = my_value

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

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

另一件事有效:

import helper
helper.my_variable = my_value

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

python namespaces jupyter built-in
2个回答
0
投票

首先,让我说我试图避免全球状态,现在说有时它是不可避免的。在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的新任务的值。


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

© www.soinside.com 2019 - 2024. All rights reserved.