Tom Hale
Tom Hale

Reputation: 46795

Printing current code line and specific variables' names, types and dimensions

To debug my Octave/MATLAB code, I want to be able to do something like:

A = magic(3);
b = 42;
describe(@A, @b);

and get output like:

filename.m line 3: "A" is a 3x3 matrix
filename.m line 3: "b" is a scalar of value: 42

For multiple variables, how can I print the:

Upvotes: 4

Views: 216

Answers (3)

Wolfie
Wolfie

Reputation: 30046

Overview

In this answer I list 3 subtly different versions of the function describe.

  1. Takes any number of variables and creates an output string and displays it using fpritnf
  2. Takes any number of variables and creates an output cell array and displays it using disp
  3. Takes any number of variable names and creates an output string as in 1. This has the advantage of being able to deal with indexing like describe('myvar{1}').

Main functionality description and version 1.

You can use various standard functions to get the info you want:

  • varargin to accept variable number of input variables
  • dbstack to get the file name / current line
  • inputname to get the names of inputs as they are passed into describe
  • fprintf to display with new line characters
  • varargout to optionally return or display the result

So create your describe function like so:

function varargout = describe(varargin)
    % varargin used to accomodate variable number of inputs
    % By default, at least get functions stack (even if no variables are passed in)
    st = dbstack;
    % Convert cell output to string (excluding describe.m itself)
    outstring = '';
    % Loop backwards for more logical order (most recent last)
    for ii = size(st, 1):-1:2
        % new line character at end only works with fprintf, not disp
        outstring = [outstring, st(ii).file, ' > ', st(ii).name, ...
                     ', line ', num2str(st(ii).line), '\n'];
    end
    % Loop over variables and get info
    for n = 1:nargin
        % Use inputname to get name of input variables
        outstring = [outstring, '"', inputname(n), '" is a ', ...
                     class(varargin{n}), ' of size ', mat2str(size(varargin{n})), '\n'];
    end
    % If output variable is requested then assign to output variable
    % If not, just display output string to command window 
    if nargout 
        varargout{1} = outstring;
    else
        fprintf(outstring)
    end
end 

The only tweaking required here really is formatting, all of your requested functionality is here and hopefully enough flexibility is built in to handle your needs.


Example output:

% In myMainFunction.m, in the subfunction mySubFunction
% Could store output using 'd = describe(A,B);' and use 'fprintf' later 
describe(A, B);
% In the command window
myMainFunction.m > myMainFunction, line 3
myMainFunction.m > mySubFunction, line 39
"A" is a double of size [1 3]
"B" is a double of size [1 5 9 7]

Tested in Matlab R2015b, all functions listed above existed since before R2006a according to documentation so I assume it's likely that they have Octave equivalents.


Version 2.

Cell instead of string with line separators.

This has a less pretty output but is also perhaps a less clunky method, assigning the strings to a cell array rather than having to rely on fprintf for new lines. It can be done easily using the following (uncommented for brevity) version.

function varargout = describe(varargin)
    st = dbstack; outcell = {};
    for ii = size(st, 1):-1:2
        outcell{end+1} = [st(ii).file, ' > ', st(ii).name, ', line ', num2str(st(ii).line)];
    end
    for n = 1:nargin
        outcell{end+1} = ['"', inputname(n), '" is a ',  class(varargin{n}), ' of size [', size(varargin{n}), ']'];
    end
    outcell = outcell.'; % Transpose to make it a column cell array
    disp(outcell)
end 

Version 3.

Passing variable names as strings, so things like 'myvar(1)' are displayed.

This uses evalin to evaluate the variables in the caller workspace (where describe was called from). NOTE: This could be more memory intensive as you are recreating the variables in this describe function to get their attributes.

function varargout = describe(varargin)
    % varargin used to accomodate variable number of input names
    st = dbstack;
    outstring = '';
    for ii = size(st, 1):-1:2
        outstring = [outstring, st(ii).file, ' > ', st(ii).name, ', line ', num2str(st(ii).line), '\n'];
    end
    % Loop over variables and get info
    for n = 1:nargin
        % Variables are passed by name, so check if they exist
        try v = evalin('caller', varargin{n});
            outstring = [outstring, '"', varargin{n}, '" is a ', class(v), ' of size ', mat2str(size(v)), '\n'];   
        catch
            outstring = [outstring, 'Variable "', varargin{n}, '" not found!\n'];
        end
    end
    fprintf(outstring)
end 

Example use:

% This can be used with indexed variables too. MUST PASS STRINGS!
describe('C{1}', 'B(1:2, :)')
% In the command window
myMainFunction.m > myMainFunction, line 3
myMainFunction.m > mySubFunction, line 39
"C{1}" is a double of size [1 3]
"B(1:2, :)" is a double of size [2 5]

% Because you're passing strings, you can use command syntax if you want
% Gives same result but don't have to pass strings 
% as that's how inputs are interpreted anyway for command syntax.
describe C{1} B(1:2, :)

Upvotes: 5

Tasos Papastylianou
Tasos Papastylianou

Reputation: 22225

I use something similar myself. Here's mine:

function describe(A)
  fprintf('       Class : %s\n',class(A));
  fprintf('  Num. Elems : %s\n',num2str(numel(A)));  
  fprintf('        Size : %s\n',num2str(size(A)));
  fprintf('   Total Min : %s\n',num2str(min (A(:))));
  fprintf('   Total Max : %s\n',num2str(max (A(:))));  
  fprintf('   Total Sum : %s\n',num2str(sum (A(:))));
  fprintf('  Total Mean : %s\n',num2str(mean(A(:))));
  fprintf('Total St.Dev : %s\n',num2str(std (A(:), 1)));
  fprintf(' Unique vals : %s\n',num2str(length(unique(A))));  
end

Edit: I'm aware this isn't a literal answer to what you ask, but I use this all the time and thought it might be useful to share. :)


PS. Having said that, it has never occurred to me that I might ever want to use such a function in a non-interactive way: if I need to inspect variables in this manner, I just put a breakpoint (or keyboard instruction) in the code and then inspect stuff in the terminal at the point where it's most relevant, so reporting a filename and a linenumber manually has never occurred to me! What is your use-case that you need to perform non-interactive debugging like this? If it's for post-mortem "testing" purposes, you really should be writing proper tests and sanity checks instead anyway!

Also, this is only for single variables, but I find that preferable; it's an extremely simple one-liner loop if you wanted more anyway.

Upvotes: 1

Théo P.
Théo P.

Reputation: 181

You can use size to have the size of your variable, for the type use class and for the name use inputname. For example:

function describe(a)
  s = size(a); % Return the size of a
  t = class(a); % Return the type of a
  name = inputname(1); % Return the name of a

  printf(['filename.m line 3: ''''' name ''''' size:' s(1) 'x' s(2) ' and type: ' t]);
end

I don't know how to use the name of file and the line, and if you want another way to display this you can can use if condition to seperate scalar from vector from matrix.

Upvotes: 0

Related Questions