matlab subsref: {} 与字符串参数失败,为什么?

发布于 2024-12-24 17:55:50 字数 3451 浏览 2 评论 0原文

Mathworks 文件交换存储库中有一些哈希或字典类的实现。我所看到的所有内容都使用括号重载来进行关键引用,例如,

d = Dict;
d('foo') = 'bar';
y = d('foo');

这似乎是一个合理的接口。不过,如果您想轻松拥有包含其他词典的词典,最好使用大括号 {} 而不是圆括号,因为这可以让您绕过 MATLAB 的(似乎是任意的)语法限制是不允许使用多个括号,但允许使用多个大括号,即

t{1}{2}{3}  % is legal MATLAB
t(1)(2)(3)  % is not legal MATLAB

,如果您希望能够轻松地在字典中嵌套字典,

dict{'key1'}{'key2'}{'key3'}

这是 Perl 中的常见习惯用法,并且在其他语言中可能且经常有用,包括Python,那么除非你想使用 n-1 中间变量来提取 n 层深度的字典条目,否则这似乎是一个不错的选择。重写类的 subsrefsubsasgn 操作似乎很容易,以便对 {} 执行与之前对 执行的操作相同的操作(),一切都应该正常。

但当我尝试时却没有。

这是我的代码。 (我已将其减少到最小情况。这里没有实现实际的字典,每个对象都有一个键和一个值,但这足以证明问题。)

classdef TestBraces < handle

    properties
        % not a full hash table implementation, obviously
        key
        value
    end

    methods(Access = public)


        function val = subsref(obj, ref)
            % Re-implement dot referencing for methods.
            if strcmp(ref(1).type, '.')
                % User trying to access a method             
                % Methods access
                if ismember(ref(1).subs, methods(obj))
                    if length(ref) > 1
                        % Call with args
                        val = obj.(ref(1).subs)(ref(2).subs{:});
                    else
                        % No args
                        val = obj.(ref.subs);
                    end
                    return;
                end                
                % User trying to access something else.
                error(['Reference to non-existant property or method ''' ref.subs '''']);
            end
            switch ref.type
                case '()'
                    error('() indexing not supported.');
                case '{}'
                    theKey = ref.subs{1};
                    if isequal(obj.key, theKey)
                        val = obj.value;
                    else
                        error('key %s not found', theKey);
                    end
                otherwise
                    error('Should never happen')
            end
        end    

        function obj = subsasgn(obj, ref, value)
            %Dict/SUBSASGN  Subscript assignment for Dict objects.
            %
            %  See also: Dict
            %

            if ~strcmp(ref.type,'{}')
                error('() and dot indexing for assignment not supported.');
            end

            % Vectorized calls not supported
            if length(ref.subs) > 1
                error('Dict only supports storing key/value pairs one at a time.');
            end
            theKey = ref.subs{1};
            obj.key = theKey;
            obj.value = value;
        end % subsasgn        
    end     
end

使用这段代码,我可以按预期分配:(

t = TestBraces;
t{'foo'} = 'bar'

并且它很明显,分配工作来自 t 的默认显示输出。)因此 subsasgn 似乎可以正常工作。

但我无法检索该值(subsref 不起作用):

t{'foo'}
??? Error using ==> subsref
Too many output arguments.

错误消息对我来说毫无意义,并且在我的 subsref 的第一个可执行行处有一个断点handler 从未被命中,所以至少表面上这看起来像是 MATLAB 问题,而不是我的代码中的错误。

显然,() 括号下标的字符串参数是允许的,因为如果您将代码更改为使用 () 而不是 {}。 (除非你不能嵌套下标操作,这是练习的对象。)

要么深入了解我在代码中做错了什么,使我正在做的事情不可行的任何限制,要么嵌套字典的替代实现将不胜感激。

There are a few implementations of a hash or dictionary class in the Mathworks File Exchange repository. All that I have looked at use parentheses overloading for key referencing, e.g.

d = Dict;
d('foo') = 'bar';
y = d('foo');

which seems a reasonable interface. It would be preferable, though, if you want to easily have dictionaries which contain other dictionaries, to use braces {} instead of parentheses, as this allows you to get around MATLAB's (arbitrary, it seems) syntax limitation that multiple parentheses are not allowed but multiple braces are allowed, i.e.

t{1}{2}{3}  % is legal MATLAB
t(1)(2)(3)  % is not legal MATLAB

So if you want to easily be able to nest dictionaries within dictionaries,

dict{'key1'}{'key2'}{'key3'}

as is a common idiom in Perl and is possible and frequently useful in other languages including Python, then unless you want to use n-1 intermediate variables to extract a dictionary entry n layers deep, this seems a good choice. And it would seem easy to rewrite the class's subsref and subsasgn operations to do the same thing for {} as they previously did for (), and everything should work.

Except it doesn't when I try it.

Here's my code. (I've reduced it to a minimal case. No actual dictionary is implemented here, each object has one key and one value, but this is enough to demonstrate the problem.)

classdef TestBraces < handle

    properties
        % not a full hash table implementation, obviously
        key
        value
    end

    methods(Access = public)


        function val = subsref(obj, ref)
            % Re-implement dot referencing for methods.
            if strcmp(ref(1).type, '.')
                % User trying to access a method             
                % Methods access
                if ismember(ref(1).subs, methods(obj))
                    if length(ref) > 1
                        % Call with args
                        val = obj.(ref(1).subs)(ref(2).subs{:});
                    else
                        % No args
                        val = obj.(ref.subs);
                    end
                    return;
                end                
                % User trying to access something else.
                error(['Reference to non-existant property or method ''' ref.subs '''']);
            end
            switch ref.type
                case '()'
                    error('() indexing not supported.');
                case '{}'
                    theKey = ref.subs{1};
                    if isequal(obj.key, theKey)
                        val = obj.value;
                    else
                        error('key %s not found', theKey);
                    end
                otherwise
                    error('Should never happen')
            end
        end    

        function obj = subsasgn(obj, ref, value)
            %Dict/SUBSASGN  Subscript assignment for Dict objects.
            %
            %  See also: Dict
            %

            if ~strcmp(ref.type,'{}')
                error('() and dot indexing for assignment not supported.');
            end

            % Vectorized calls not supported
            if length(ref.subs) > 1
                error('Dict only supports storing key/value pairs one at a time.');
            end
            theKey = ref.subs{1};
            obj.key = theKey;
            obj.value = value;
        end % subsasgn        
    end     
end

Using this code, I can assign as expected:

t = TestBraces;
t{'foo'} = 'bar'

(And it is clear that the assignment work from the default display output for t.) So subsasgn appears to work correctly.

But I can't retrieve the value (subsref doesn't work):

t{'foo'}
??? Error using ==> subsref
Too many output arguments.

The error message makes no sense to me, and a breakpoint at the first executable line of my subsref handler is never hit, so at least superficially this looks like a MATLAB issue, not a bug in my code.

Clearly string arguments to () parenthesis subscripts are allowed, since this works fine if you change the code to work with () instead of {}. (Except then you can't nest subscript operations, which is the object of the exercise.)

Either insight into what I'm doing wrong in my code, any limitations that make what I'm doing unfeasible, or alternative implementations of nested dictionaries would be appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

宣告ˉ结束 2024-12-31 17:55:50

简短的答案,将此方法添加到您的类中:

function n = numel(obj, varargin)
    n = 1;
end

编辑:长答案。

尽管 subsref 的函数签名出现在文档中,但它实际上是一个 varargout 函数 - 它可以生成可变数量的输出参数。大括号和点索引都可以产生多个输出,如下所示:

>> c = {1,2,3,4,5};
>> [a,b,c] = c{[1 3 5]}
a =
     1
b =
     3
c =
     5

subsref 预期的输出数量是根据索引数组的大小确定的。在本例中,索引数组的大小为 3,因此有三个输出。

现在,再看看:

t{'foo'}

索引数组的大小是多少?另外 3. MATLAB 并不关心您打算将其解释为字符串而不是数组。它只是看到输入的大小是 3 并且你的 subsref 一次只能输出 1 个东西。因此,论点不匹配。幸运的是,我们可以通过重载 numel 来更改 MATLAB 确定预期输出数量的方式来纠正问题。引用自文档链接:

重要的是要注意 numel 对于
重载的 subsref 和 subsasgn 函数。在这种情况下
用于大括号和点索引的重载 subsref 函数(如所述
在最后一段中),numel 用于计算
从 subsref 返回的预期输出 (nargout)。对于超载者
subsasgn 函数,numel 用于计算期望的数量
使用 subsasgn 分配的输入 (nargin)。的 nargin 值
重载的subsasgn函数是numel返回的值加2
(一个用于分配给的变量,一个用于结构
下标数组)。

作为类设计者,您必须确保返回的 n 值
内置的 numel 函数与类设计一致
那个物体。如果 n 与 nargout 不同
重载的 subsref 函数或重载的 subsasgn 的 nargin
函数,那么你需要重载 numel 以返回 n 的值
与类的 subsref 和 subsasgn 函数一致。
否则,MATLAB 在调用这些函数时会产生错误。

现在你就得到了它。

Short answer, add this method to your class:

function n = numel(obj, varargin)
    n = 1;
end

EDIT: The long answer.

Despite the way that subsref's function signature appears in the documentation, it's actually a varargout function - it can produce a variable number of output arguments. Both brace and dot indexing can produce multiple outputs, as shown here:

>> c = {1,2,3,4,5};
>> [a,b,c] = c{[1 3 5]}
a =
     1
b =
     3
c =
     5

The number of outputs expected from subsref is determined based on the size of the indexing array. In this case, the indexing array is size 3, so there's three outputs.

Now, look again at:

t{'foo'}

What's the size of the indexing array? Also 3. MATLAB doesn't care that you intend to interpret this as a string instead of an array. It just sees that the input is size 3 and your subsref can only output 1 thing at a time. So, the arguments mismatch. Fortunately, we can correct things by changing the way that MATLAB determines how many outputs are expected by overloading numel. Quoted from the doc link:

It is important to note the significance of numel with regards to the
overloaded subsref and subsasgn functions. In the case of the
overloaded subsref function for brace and dot indexing (as described
in the last paragraph), numel is used to compute the number of
expected outputs (nargout) returned from subsref. For the overloaded
subsasgn function, numel is used to compute the number of expected
inputs (nargin) to be assigned using subsasgn. The nargin value for
the overloaded subsasgn function is the value returned by numel plus 2
(one for the variable being assigned to, and one for the structure
array of subscripts).

As a class designer, you must ensure that the value of n returned by
the built-in numel function is consistent with the class design for
that object. If n is different from either the nargout for the
overloaded subsref function or the nargin for the overloaded subsasgn
function, then you need to overload numel to return a value of n that
is consistent with the class' subsref and subsasgn functions.
Otherwise, MATLAB produces errors when calling these functions.

And there you have it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文