【问题标题】:Clear persistent variables in local functions from within the main function从主函数中清除局部函数中的持久变量
【发布时间】:2019-12-21 22:53:50
【问题描述】:

我有一个代码,它由一个包含多个函数的文件组成,其中一些函数使用persistent 变量。为了使其正常工作,持久变量必须为空。

有记录的方法可以在多功能文件中clear持久变量,例如:

clear functionName % least destructive
clear functions    % more destructive
clear all          % most destructive

不幸的是,我不能保证用户在调用函数之前记得清除persistent变量,所以我正在探索在代码开头执行清除操作的方法。为了说明这个问题,请考虑以下示例:

function clearPersistent(methodId)
if ~nargin, methodId = 0; end

switch methodId
  case 0 
    % do nothing
  case 1
    clear(mfilename);
  case 2
    eval(sprintf('clear %s', mfilename));
  case 3
    clear functions;
  case 4
    clear all;
end

subfunction();
subfunction();
end

function [] = subfunction()
persistent val

if isempty(val)
  disp("val is empty");
  val = 123;
else
  disp("val is not empty");
end
end

第一次运行时,我们得到:

>> clearPersistent
val is empty
val is not empty

我希望此时再次运行该函数,任何非 0 输入都会导致 val 变量被清除,但可惜 - 情况并非如此。在设置val 之后,除非我们使用顶部 sn -p externally 中显示的替代方案之一,或者修改.m 文件,否则它会保持设置。

我的问题:是否可以从主函数体内清除子函数中的持久变量,如果可以,如何?

换句话说,我正在寻找一些可以在调用子函数之前放入clearPersistent 的代码,以便输出一致:

val is empty
val is not empty

附言

  1. 这是一个相关的过去问题(不涉及此特定用例):List/view/clear persistent variables in Matlab

  2. 我知道重写代码以完全不使用persistent 变量的可能性(例如,通过传递数据、使用appdata、向所有子函数添加'clear' 标志等)。

  3. 请注意,编辑函数的源代码并保存会隐式清除它(以及所有持久变量)。

  4. 我知道文档指出“clear 函数不会清除本地或嵌套函数中的持久变量。


问题的其他背景:

实际代码结构如下:

Main function (called once)
  └ Global optimization solver (called once)
    └ Objective function (called an unknown N≫1 times)
      └ 1st function that uses persistents
      └ 2nd function that uses persistents

如 cmets 中所述,一些变量被持久化的原因有几个:

  1. 松散耦合/SoC:目标函数不需要了解子函数的工作方式。
  2. 封装:它是一个实现细节。持久变量不需要存在于使用它们的函数范围之外(即没有其他人需要它们)。
  3. 性能:持久变量包含计算成本相当高的矩阵,但每次调用主函数时,此操作只需执行一次。

使用持久变量的一个(副作用?)效果是使整个代码有状态(具有两种状态:beforeafter 昂贵的计算)。最初的问题源于在调用 main 函数之间未正确重置状态,导致运行依赖于使用先前(因此无效)配置创建的状态。

可以通过计算主函数中的一次性值(目前仅解析用户提供的配置,调用求解器,最后存储/显示输出),然后将它们与用户配置一起传递来避免有状态进入目标函数,然后将它们传递给子函数。这种方法解决了状态清除问题,但会损害封装性并增加耦合性,进而可能会损害可维护性。

不幸的是,目标函数没有flag that says 'init' etc.,所以我们不知道它是在第 1st 次还是第 nth 次调用,没有跟踪这就是我们自己(AKA state)。

理想的解决方案应该有几个属性:

  1. 一次计算昂贵的数量。
  2. 无国籍。
  3. 不传递不相关的数据(即“需要知道基础”;各个函数工作区只包含他们需要的数据)。

【问题讨论】:

  • clear classes 实际上是最具破坏性的,但它仍然不会清除锁定的项目。
  • 乍一看,我相信解决方案超出了问题的范围。即没有一个好的解决方案最终不会变得不可维护,隐藏它的行为,不是很可测试,而且通常非常hacky。如果您为更高级别的问题提供更多范围,我们也许可以提供替代解决方案。
  • @LightCC 你说得对,我不清楚我到底为什么要问这个,替代解决方案更有意义。也许我出于问题的目的过度简化了我的问题,因此我将尝试添加更多上下文以确保完整性。
  • 由于您对问题的更改改变了答案的含义和有用性,让我收回我的赞成票。
  • 您是否考虑过使用类(面向对象编程)并将单例对象存储在全局持久数据中来访问它?然后你可以在课堂上做任何你想做的事情来存储昂贵数据的结果,存储状态,并在你想要的任何条件/方法调用上清除数据和状态......我才刚刚开始搞乱Matlab,所以不确定如何以您需要的方式最好地与全局存储交互。

标签: matlab global-variables state persistent-storage local-functions


【解决方案1】:

clear fnameclear functions 从内存中删除 M 文件。下次运行该函数时,它会再次从磁盘读取、解析并编译成字节码。因此,您会减慢函数的下一次执行速度。

因此无法从函数中清除函数或子函数。您正在运行该函数,您无法从内存中清除其文件。

我的解决方案是向subfunction 添加一个选项以清除其持久变量,如下所示:

function clearPersistent()

subfunction('clear');
subfunction();
subfunction();
end

function [] = subfunction(option)
persistent val

if nargin>0 && ischar(option) && strcmp(option,'clear')
   val = [];
   return
end

if isempty(val)
  disp("val is empty");
  val = 123;
else
  disp("val is not empty");
end

end

当然你可以在调用 subfunction('init') 时初始化你的值。


另一种可能适用于您的用例的解决方案是将val 的计算与其使用分开。我会发现这比任何其他解决方案都更易于阅读,并且性能也会更高。

function main()
val = computeval();
subfunction(val);
subfunction(val);
end

根据您的编辑,您可以将目标函数放在一个单独的文件中(在private 子目录中)。你可以clear它。

持久变量的替代方法是创建一个用户类,该类具有一个计算昂贵状态的构造函数,以及另一种计算目标函数的方法。这也可以是private 子目录中的classdef 文件。我认为这更好,因为你不需要记得打电话给clear

在这两种情况下,您不再有一个包含所有代码的文件。我认为你需要放弃这两个理想之一:要么打破数据封装,要么将代码拆分到两个文件中(代码封装?)。

【讨论】:

    【解决方案2】:

    为什么不使用全局变量? 您可以创建一个包含变量的全局结构,并且可以使用variable_manager 对其进行管理:

    function main
        variable_manager('init')
        subfunction1()
        subfunction2()
    end
    
    function variable_manager(action)
        global globals
        switch action
            case 'init'
                globals = struct('val',[],'foo',[]);
            case 'clear'
                globals = structfun(@(x)[],globals,'UniformOutput', false);
    %       case ....
    %       ...
        end
    end
    
    function subfunction1
        global globals
        if isempty(globals.val)
            disp("val is empty");
            globals.val = 123;
        else
            disp("val is not empty");
        end
    end
    
    function subfunction2
        global globals
        if isempty(globals.foo)
            disp("foo is empty");
            globals.foo = 321;
        else
            disp("foo is not empty");
        end
    end
    

    【讨论】:

    • 这是一种解决方案,但我不推荐。全局变量是有问题的,因为您可能有冲突的名称——2 个函数无意中使用了同名的全局变量。持久变量没有这个问题。全局变量在许多函数之间共享,导致代码难以阅读:您不知道它们在哪里被更改,很难跟踪信息流。持久变量也不存在这个问题。
    • 好点!我认为 OP 想要的更类似于全局变量而不是持久变量。此外,我建议在具有不同名称的结构中管理它们,以防止名称冲突。甚至可以将它们管理为globals.subfunction1.val
    • 是的,我有点喜欢您将它们放在这样的结构中的概念。它确实在一定程度上澄清了代码。但是仍然存在数据被函数调用更改的问题,您甚至可能没有意识到访问该数据。我总是更喜欢明确说明数据流,并将数据作为参数传递给函数。持久变量仅在一个函数中可见,因此无法在其他地方更改。
    • 更多上下文:val 之所以成为persistent,是因为在我的实际代码中,它不需要在subfunction 之外“存在”(即没有其他人使用它)。此外,计算它是计算密集型的,所以在第一次找到它之后,我更喜欢将它存储在内存中(类似于缓存/记忆)。在计算之后,该值在运行的其余部分不会改变。
    • 我喜欢不使用持久变量。但是,您需要一种管理所有此类变量的方法,因此我的建议是使用全局变量。换句话说,当一个人接受了使用持久变量时,他们的所有问题,为什么他们不转而使用全局变量?
    【解决方案3】:

    正如问题中提到的,其中一种可能性是使用appdata,它与global 并没有太大区别(至少在将它们与“object 0”相关联时 - 这是 MATLAB 实例本身)。避免与其他脚本/函数/等“冲突”。我们引入了一个随机字符串(如果我们在每个使用这种存储技术的函数中生成一个字符串,它几乎可以肯定地保证没有冲突)。这种方法的主要缺点是字符串必须在多个位置进行硬编码,或者应该更改代码结构,以便使用此appdata 的函数嵌套在定义它的函数中。

    这两种写法是:

    function clearPersistent()
    % Initialization - clear the first time:
    clearAppData();
    
    % "Business logic"
    subfunction();
    subfunction();
    
    % Clear again, just to be sure:
    clearAppData();
    end % clearPersistent
    
    function [] = subfunction()
    APPDATA_NAME = "pZGKmHt6HzkkakvdfLV8"; % Some random string, to avoid "global collisions"
    val = getappdata(0, APPDATA_NAME);
    
    if isempty(val)
      disp("val is empty");
      val = 123;
      setappdata(0, APPDATA_NAME, val);
    else
      disp("val is not empty");
    end
    end % subfunction
    
    function [] = clearAppData()
    APPDATA_NAME = "pZGKmHt6HzkkakvdfLV8";
    if isappdata(0, APPDATA_NAME)
      rmappdata(0, APPDATA_NAME);
    end
    end % clearAppData
    

    和:

    function clearPersistent()
    APPDATA_NAME = "pZGKmHt6HzkkakvdfLV8";
    % Initialization - clear the first time:
    clearAppData();
    
    % "Business logic"
    subfunction();
    subfunction();
    
    % Clear again, just to be sure:
    clearAppData();
    
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    
    function [] = subfunction()
      val = getappdata(0, APPDATA_NAME);
    
      if isempty(val)
        disp("val is empty");
        val = 123;
        setappdata(0, APPDATA_NAME, val);
      else
        disp("val is not empty");
      end
    end % subfunction
    
    function [] = clearAppData()
      if isappdata(0, APPDATA_NAME)
        rmappdata(0, APPDATA_NAME);
      end
    end % clearAppData
    
    end % clearPersistent
    

    【讨论】:

    • 如果要使用嵌套函数,只需在父函数中声明变量即可。此外,这比持久变量的性能要差得多。如果要多次调用这些子函数,请不要使用appdata
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-05-29
    • 2023-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-06
    • 2020-01-11
    相关资源
    最近更新 更多