3

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

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

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,除非我们在外部使用顶部代码段中显示的替代方案之一,或修改.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

正如评论中提到的,有几个原因使某些变量保持不变:

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

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

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

不幸的是,目标函数没有表示'init'etc.的标志,所以我们不知道它是第一次调用还是第 n调用,而我们自己没有跟踪这一点(AKA state)。

理想的解决方案将具有几个属性:

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

3 回答 3

3

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

当然,您可以在调用 as 时初始化您的值subfunction('init')


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

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

鉴于您的编辑,您可以将目标函数放在一个单独的文件中(在private子目录中)。你将能够clear做到。

持久变量的替代方法是创建一个用户类,其中包含一个计算昂贵状态的构造函数,以及另一种计算目标函数的方法。这也可以是子目录classdef中的文件。private我认为这更好,因为您不需要记住 call clear

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

于 2019-08-15T14:36:58.123 回答
1

为什么不使用全局变量?您可以创建一个包含变量的全局结构,并且可以使用以下命令对其进行管理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
于 2019-08-15T14:37:12.900 回答
0

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

两种写法是:

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
于 2019-08-15T15:09:28.763 回答