Matlab函数处理工作区恶作剧
Posted
技术标签:
【中文标题】Matlab函数处理工作区恶作剧【英文标题】:Matlab function handle workspace shenanigans 【发布时间】:2012-01-30 02:08:28 【问题描述】:简而言之:有没有一种优雅的方法来限制匿名函数的范围,或者这个例子中的 Matlab 是否被破坏?
我有一个函数可以创建用于管网求解器的函数句柄。它将网络状态作为输入,其中包括有关管道及其连接(或边缘和顶点,如果必须)的信息,构造一个大字符串,该字符串在函数形式时将返回一个大矩阵,并“评估”该字符串以创建句柄.
function [Jv,...] = getPipeEquations(Network)
... %// some stuff happens here
Jv_str = ['[listConnected(~endNodes,:)',...
' .* areaPipes(~endNodes,:);\n',...
anotherLongString,']'];
Jv_str = sprintf(Jv_str); %// This makes debugging the string easier
eval(['Jv = @(v,f,rho)', Jv_str, ';']);
此函数按预期工作,但每当我需要保存包含此函数句柄的后续数据结构时,它需要 可笑 的内存量 (150MB) - 巧合的是,大约与整个 Matlab 一样多创建此函数时的工作区(~150MB)。该函数句柄需要从 getPipeEquations 工作空间中获取的变量并不是特别大,但更疯狂的是,当我检查函数句柄时:
>> f = functions(Network.jacobianFun)
f =
function: [1x8323 char]
type: 'anonymous'
file: '...\pkg\+adv\+pipe\getPipeEquations.m'
workspace: 2x1 cell
...工作区字段包含 getPipeEquations 所拥有的所有内容(顺便说一下,这不是整个 Matlab 工作区)。
如果我将 eval 语句移动到子函数以尝试强制作用域,则句柄将保存得更紧凑(~1MB):
function Jv = getJacobianHandle(Jv_str,listConnected,areaPipes,endNodes,D,L,g,dz)
eval(['Jv = @(v,f,rho)', Jv_str, ';']);
这是预期的行为吗?有没有更优雅的方式来限制这个匿名函数的范围?
作为附录,当我多次运行包含此函数的模拟时,清除工作空间变得非常缓慢,这可能与 Matlab 对函数及其工作空间的处理有关,也可能无关。
【问题讨论】:
你试过evalin('base', ...)
吗?这有什么不同吗?
我没有,但是工作空间应该已经限制在getPipeEquations的范围内了。
【参考方案1】:
我可以重现:对我来说,匿名函数正在捕获封闭工作区中的 所有 变量的副本,而不仅仅是在匿名函数的表达式中引用的那些。
这是一个最小的复制。
function fcn = so_many_variables()
a = 1;
b = 2;
c = 3;
fcn = @(x) a+x;
a = 42;
事实上,它捕获了整个封闭工作区的副本。
>> f = so_many_variables;
>> f_info = functions(f);
>> f_info.workspace1
ans =
a: 1
>> f_info.workspace2
ans =
fcn: @(x)a+x
a: 1
b: 2
c: 3
起初这让我很惊讶。但仔细想想,它是有道理的:由于feval
和eval
的存在,Matlab 在构造时实际上无法知道匿名函数实际上最终会引用哪些变量。因此,它必须捕获范围内的所有内容,以防它们被动态引用,就像在这个人为的示例中一样。这使用了foo
的值,但在您调用返回的函数句柄之前,Matlab 不会知道这一点。
function fcn = so_many_variables()
a = 1;
b = 2;
foo = 42;
fcn = @(x) x + eval(['f' 'oo']);
您正在做的解决方法 - 将函数构造隔离在具有最小工作空间的单独函数中 - 听起来是正确的解决方法。
这是一种通用的方法,可以让受限工作区在其中构建您的匿名函数。
function eval_with_vars_out = eval_with_vars(eval_with_vars_expr, varargin)
% Assign variables to the local workspace so they can be captured
ewvo__reserved_names = 'varargin','eval_with_vars_out','eval_with_vars_expr','ewvo__reserved_names','ewvo_i';
for ewvo_i = 2:nargin
if ismember(inputname(ewvo_i), ewvo__reserved_names)
error('variable name collision: %s', inputname(ewvo_i));
end
eval([ inputname(ewvo_i) ' = vararginewvo_i-1;']);
end
clear ewvo_i ewvo__reserved_names varargin;
% And eval the expression in that context
eval_with_vars_out = eval(eval_with_vars_expr);
这里的长变量名会损害可读性,但会降低与调用者变量冲突的可能性。
您只需调用 eval_with_vars() 而不是 eval(),并将所有输入变量作为附加参数传入。然后,您不必为每个匿名函数构建器键入静态函数定义。只要您事先知道实际要引用哪些变量,这将起作用,这与 getJacobianHandle
方法的限制相同。
Jv = eval_with_vars_out(['@(v,f,rho) ' Jv_str],listConnected,areaPipes,endNodes,D,L,g,dz);
【讨论】:
您可能会在开始时使用 inputname 进行冲突检查,但这可能没有必要。 好主意 - 碰撞不太可能发生,但如果发生,您可能会得到一个无声的、难以诊断的错误。我已经修改了代码以添加碰撞检查。【参考方案2】:匿名函数捕获其范围内的所有内容并将它们存储在函数工作区中。请参阅anonymous functions 的 MATLAB 文档
特别是:
“在表达式主体中指定的变量。MATLAB 捕获这些变量并在函数句柄的整个生命周期内保持它们不变。
在您构造使用它们的匿名函数时,后面的变量必须具有分配给它们的值。在构造时,MATLAB 会捕获该函数主体中指定的每个变量的当前值。该函数将继续将此值与变量相关联,即使该值应在工作区中更改或超出范围。"
【讨论】:
您摘录和链接到的文档并不像您在此处所说的那样广泛。 doco 说它捕获表达式主体中引用的变量,但发布者说它不仅捕获那些,而且捕获封闭工作区中的 所有 变量。【参考方案3】:您的问题的另一种解决方法是使用 matlab save
函数可用于仅保存您需要的特定变量的事实。我遇到了 save
函数保存太多数据的问题(与您的上下文非常不同),但是一些司法命名约定以及在变量列表中使用通配符使我所有的问题都消失了。
【讨论】:
以上是关于Matlab函数处理工作区恶作剧的主要内容,如果未能解决你的问题,请参考以下文章
matlab中的SPM:如何在批处理编辑器中调用matlab函数