【问题标题】:Is there a way to find all children of a Matlab class?有没有办法找到一个 Matlab 类的所有孩子?
【发布时间】:2016-06-15 08:02:24
【问题描述】:

Matlab 函数superclasses 返回给定类的所有父类的名称。

是否有一个等价物可以从给定的类中找到所有派生类,子类?函数allchild 似乎仅限于图形句柄。

如果不是,可以采取什么策略来获得这样的列表?暴力路径扫描是唯一的选择吗?

让我们限制在 Matlab 路径中的类。

【问题讨论】:

  • 我只是想知道你想用这个做什么?
  • @Bernhard 我正在为自定义程序构建文档引擎。我发现在班级页面上同时列出父母班级和儿童班级的列表会非常有用。
  • 您是否首先寻找现有的解决方案?如果我正在搜索 doxygen 和 matlab,我会找到一些可行的解决方案。做那些不适合你的。
  • @Ratbert - 我的回答有什么不满意的地方吗...?
  • @Ratbert 是的,很高兴收到一些反馈:)

标签: matlab oop subclass class-hierarchy


【解决方案1】:

简介:

在解决方案的过程中,我似乎找到了 meta.class 类的一个未记录的静态方法,它返回所有缓存的类(当有人调用 clear classes 时几乎所有被删除的东西)以及(完全是偶然的)制作了一个工具来检查classdef 文件是否有错误。


由于我们想要找到 所有 子类,因此确定的方法是列出 所有已知 em> 类,然后检查每个类是否派生自任何其他类。为了实现这一点,我们将我们的工作分为两类:

  • "Bulk classes" - 这里我们使用 what 函数来创建一个文件列表,这些文件只是“放置”在 MATLAB 路径上,它输出一个结构 s(描述在what 的文档中具有以下字段:'path' 'm' 'mlapp' 'mat' 'mex' 'mdl' 'slx' 'p' 'classes' 'packages'。然后我们将选择其中一些来构建类列表。识别 .m 或 .p 文件具有哪些类型的内容(什么 我们关心的是类/非类),我们使用existThis method is demonstrated by Loren in her blog。在我的代码中,这是mb_list
  • 包类” - 这包括作为其内部包结构的一部分被 MATLAB 索引的类文件。获取此列表所涉及的算法涉及调用meta.package.getAllPackages,然后递归遍历此顶级包列表以获取所有子包。然后从每个包中提取一个类列表,并将所有列表连接成一个长列表 - mp_list

该脚本有两个输入标志(includeBulkFiles,includePackages),用于确定每种类型的类是否应包含在输出列表中。

完整代码如下

function [mc_list,subcls_list] = q37829489(includeBulkFiles,includePackages)
%% Input handling
if nargin < 2 || isempty(includePackages)
  includePackages = false;
  mp_list = meta.package.empty;
end
if nargin < 1 || isempty(includeBulkFiles)
  includeBulkFiles = false;
  mb_list = meta.class.empty; %#ok
  % `mb_list` is always overwritten by the output of meta.class.getAllClasses; 
end
%% Output checking
if nargout < 2
  warning('Second output not assigned!');
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Get classes list from bulk files "laying around" the MATLAB path:
if includeBulkFiles
  % Obtain MATLAB path:
  p = strsplit(path,pathsep).';
  if ~ismember(pwd,p)
    p = [pwd;p];
  end
  nPaths = numel(p);
  s = what; s = repmat(s,nPaths+20,1); % Preallocation; +20 is to accomodate rare cases 
  s_pos = 1;                           %  where "what" returns a 2x1 struct.
  for ind1 = 1:nPaths  
    tmp = what(p{ind1});
    s(s_pos:s_pos+numel(tmp)-1) = tmp;
    s_pos = s_pos + numel(tmp);
  end
  s(s_pos:end) = []; % truncation of placeholder entries.
  clear p nPaths s_pos tmp
  %% Generate a list of classes:
  % from .m files:
  m_files = vertcat(s.m);
  % from .p files:
  p_files = vertcat(s.p);
  % get a list of potential class names:
  [~,name,~] = cellfun(@fileparts,[m_files;p_files],'uni',false);
  % get listed classes:
  listed_classes = s.classes;
  % combine all potential class lists into one:
  cls_list = vertcat(name,listed_classes);
  % test which ones are actually classes:
  isClass = cellfun(@(x)exist(x,'class')==8,cls_list); %"exist" method; takes long
  %[u,ia,ic] = unique(ext(isClass(1:numel(ext)))); %DEBUG:

  % for valid classes, get metaclasses from name; if a classdef contains errors,
  % will cause cellfun to print the reason using ErrorHandler.
  [~] = cellfun(@meta.class.fromName,cls_list(isClass),'uni',false,'ErrorHandler',...
     @(ex,in)meta.class.empty(0*fprintf(1,'The classdef for "%s" contains an error: %s\n'...
                                         , in, ex.message)));
  % The result of the last computation used to be assigned into mc_list, but this 
  % is no longer required as the same information (and more) is returned later
  % by calling "mb_list = meta.class.getAllClasses" since these classes are now cached.
  clear cls_list isClass ind1 listed_classes m_files p_files name s
end
%% Get class list from classes belonging to packages (takes long!):

if includePackages
  % Get a list of all package classes:
  mp_list = meta.package.getAllPackages; mp_list = vertcat(mp_list{:});  
  % see http://www.mathworks.com/help/matlab/ref/meta.package.getallpackages.html

  % Recursively flatten package list:
  mp_list = flatten_package_list(mp_list);

  % Extract classes out of packages:
  mp_list = vertcat(mp_list.ClassList);
end
%% Combine lists:
% Get a list of all classes that are in memory:
mb_list = meta.class.getAllClasses; 
mc_list = union(vertcat(mb_list{:}), mp_list);
%% Map relations:
try
  [subcls_list,discovered_classes] = find_superclass_relations(mc_list);
  while ~isempty(discovered_classes)
    mc_list = union(mc_list, discovered_classes);
    [subcls_list,discovered_classes] = find_superclass_relations(mc_list);
  end
catch ex % Turns out this helps....
  disp(['Getting classes failed with error: ' ex.message ' Retrying...']);
  [mc_list,subcls_list] = q37829489;
end

end

function [subcls_list,discovered_classes] = find_superclass_relations(known_metaclasses)
%% Build hierarchy:
sup_list = {known_metaclasses.SuperclassList}.';
% Count how many superclasses each class has:
n_supers = cellfun(@numel,sup_list);
% Preallocate a Subclasses container: 
subcls_list = cell(numel(known_metaclasses),1); % should be meta.MetaData
% Iterate over all classes and 
% discovered_classes = meta.class.empty(1,0); % right type, but causes segfault
discovered_classes = meta.class.empty;
for depth = max(n_supers):-1:1
  % The function of this top-most loop was initially to build a hierarchy starting 
  % from the deepest leaves, but due to lack of ideas on "how to take it from here",
  % it only serves to save some processing by skipping classes with "no parents".
  tmp = known_metaclasses(n_supers == depth);
  for ind1 = 1:numel(tmp)
    % Fortunately, SuperclassList only shows *DIRECT* supeclasses. Se we
    % only need to find the superclasses in the known classees list and add
    % the current class to that list.
    curr_cls = tmp(ind1);
    % It's a shame bsxfun only works for numeric arrays, or else we would employ: 
    % bsxfun(@eq,mc_list,tmp(ind1).SuperclassList.');
    for ind2 = 1:numel(curr_cls.SuperclassList)
      pos = find(curr_cls.SuperclassList(ind2) == known_metaclasses,1);
      % Did we find the superclass in the known classes list?
      if isempty(pos)
        discovered_classes(end+1,1) = curr_cls.SuperclassList(ind2); %#ok<AGROW>
  %       disp([curr_cls.SuperclassList(ind2).Name ' is not a previously known class.']);
        continue
      end      
      subcls_list{pos} = [subcls_list{pos} curr_cls];
    end    
  end  
end
end

% The full flattened list for MATLAB R2016a contains about 20k classes.
function flattened_list = flatten_package_list(top_level_list)
  flattened_list = top_level_list;
  for ind1 = 1:numel(top_level_list)
    flattened_list = [flattened_list;flatten_package_list(top_level_list(ind1).PackageList)];
  end
end

此函数的输出是 2 个向量,在 Java 术语中可以认为是Map&lt;meta.class, List&lt;meta.class&gt;&gt;

  • mc_list - meta.class 类的对象向量,其中每个条目包含有关 MATLAB 已知的一个特定类的信息。这些是我们Map 的“钥匙”。
  • subcls_list - 一个(相当稀疏的)单元向量,包含出现在mc_list 对应位置的类的已知直接子类。这些是我们Map 的“值”,本质上是List&lt;meta.class&gt;

一旦我们有了这两个列表,只需在mc_list 中找到您感兴趣的类的位置,并从subcls_list 获取其子类列表。如果需要间接子类,则对子类也重复相同的过程。

或者,可以使用例如表示层次结构。一个logicalsparse邻接矩阵A,其中ai,j==1表示类ij的子类。那么这个矩阵的转置可以表示相反的关系,即aTi,j==1表示i是一个超级j。牢记邻接矩阵的这些属性允许非常快速地搜索和遍历层次结构(避免需要对meta.class 对象进行“昂贵的”比较)。

几点说明:

  • 由于未知原因(缓存?),代码可能由于错误(例如Invalid or deleted object.)而失败,在这种情况下重新运行它会有所帮助。我添加了一个try/catch,它会自动执行此操作。
  • 代码中有 2 个实例,其中数组在循环内增长。这当然是不需要的,应该避免。由于缺乏更好的想法,代码就这样留下了。
  • 如果算法的“发现”部分无法避免(通过某种方式首先找到所有类),则可以(并且应该)对其进行优化,以便每次迭代只对以前未知的类进行操作。李>
  • 运行此代码的一个有趣的意想不到的好处是它会扫描所有已知的classdefs 并报告其中的任何错误 - 这可能是一个有用的工具,可以在而对于任何使用 MATLAB OOP 的人 :)
  • 感谢@Suever 提供一些有用的建议。

与奥列格的方法比较:

为了将这些结果与 Oleg 的示例进行比较,我将使用在我的计算机上运行上述脚本的输出(包含约 20k 类;上传 here 作为 .mat 文件)。然后我们可以通过以下方式访问类映射:

hRoot = meta.class.fromName('sde');
subcls_list{mc_list==hRoot}

ans = 

  class with properties:

                     Name: 'sdeddo'
              Description: ''
      DetailedDescription: ''
                   Hidden: 0
                   Sealed: 0
                 Abstract: 0
              Enumeration: 0
          ConstructOnLoad: 0
         HandleCompatible: 0
          InferiorClasses: {0x1 cell}
        ContainingPackage: [0x0 meta.package]
             PropertyList: [9x1 meta.property]
               MethodList: [18x1 meta.method]
                EventList: [0x1 meta.event]
    EnumerationMemberList: [0x1 meta.EnumeratedValue]
           SuperclassList: [1x1 meta.class]

subcls_list{mc_list==subcls_list{mc_list==hRoot}} % simulate recursion

ans = 

  class with properties:

                     Name: 'sdeld'
              Description: ''
      DetailedDescription: ''
                   Hidden: 0
                   Sealed: 0
                 Abstract: 0
              Enumeration: 0
          ConstructOnLoad: 0
         HandleCompatible: 0
          InferiorClasses: {0x1 cell}
        ContainingPackage: [0x0 meta.package]
             PropertyList: [9x1 meta.property]
               MethodList: [18x1 meta.method]
                EventList: [0x1 meta.event]
    EnumerationMemberList: [0x1 meta.EnumeratedValue]
           SuperclassList: [1x1 meta.class]

在这里我们可以看到最后一个输出只有 1 个类 (sdeld),而我们期望其中有 3 个 (sdeld,sdemrd,heston) - 这意味着 某些类此列表中缺少1

相比之下,如果我们检查一个共同的父类,例如handle,我们会看到完全不同的画面:

subcls_list{mc_list==meta.class.fromName('handle')}

ans = 

  1x4059 heterogeneous class (NETInterfaceCustomMetaClass, MetaClassWithPropertyType, MetaClass, ...) array with properties:

    Name
    Description
    DetailedDescription
    Hidden
    Sealed
    Abstract
    Enumeration
    ConstructOnLoad
    HandleCompatible
    InferiorClasses
    ContainingPackage
    PropertyList
    MethodList
    EventList
    EnumerationMemberList
    SuperclassList

总而言之:此方法尝试索引 MATLAB 路径上的所有已知类。构建类列表/索引需要几分钟,但这是一个 1 次过程,稍后在搜索列表时会得到回报。它似乎遗漏了一些类,但找到的关系并不局限于相同的包、路径等。因此,它天生就支持多重继承。


1 - 我目前不知道是什么原因造成的。

【讨论】:

  • 我建议使用pathseppath 的输出拆分给您没有任何跨平台问题。不错的解决方案。此外,除了路径上的所有文件夹外,我还会查看 pwd,因为从技术上讲,它也在路径上。
  • 这是否适用于包内的类?
  • @Dev-iL:感谢您在这个问题上花费这么多时间。尽管您的答案仍然有一些限制(例如速度方面),但它已经是一段不错的代码了。
  • @Ratbert 这是一个有趣的问题!也许我们所有人都需要联合起来(还有@Suever),像 Oleg 建议的那样打开一个 Github 存储库,组合所有代码并将其变成一个漂亮而完整的工具。 ...感谢您的赏金:)
  • 是的,我同意,以目前的形式,我无法运行您的代码。我想与我的结果进行比较。
【解决方案2】:

代码

我将超过 200 行的代码移至 Github 存储库 getSubclasses。欢迎您申请功能并发布错误报告。

想法

给定 root 类名或meta.class 和文件夹路径,它将遍历文件夹结构并构建一个包含从根派生的所有子类的图(无限深度)。如果未提供路径,则它将从根类所在的文件夹向下递归。

请注意,该解决方案是本地解决方案,这就是它速度快的原因,并且依赖于假设子类嵌套在所选路径的某个子文件夹下。

示例

列出sde 类的所有子类。您将需要 R2015b 才能生成图,或者您可以使用输出和 FEX 提交 plot_graph() 来生成依赖图。

getSubclasses('sde','C:\Program Files\MATLAB\R2016a\toolbox\finance\finsupport')

以及带有边和节点名称的输出:

 names      from    to
________    ____    __
'sde'        1      1 
'bm'         2      3 
'sdeld'      3      6 
'cev'        4      3 
'gbm'        5      4 
'sdeddo'     6      1 
'heston'     7      6 
'cir'        8      9 
'sdemrd'     9      6 
'hwv'       10      9 

我们可以将结果与官方文档进行比较,本例中列出了SDE hierarchy,即

时间

在 Win7 64b R2016a 上

  • 小于0.1秒getSubclasses('sde','C:\Program Files\MATLAB\R2016a\toolbox\finance\finsupport')
  • 如果扫描整个 matlabroot,大约 13 秒getSubclasses('sde',matlabroot);

【讨论】:

    【解决方案3】:

    不是一个完整的解决方案,但是,您可以将路径中的所有 .m 文件解析为文本,并使用正则表达式查找子类定义。

    类似^\s*classdef\s*(\w*)\s*&lt;\s*superClassName\s*(%.*)?

    请注意,这在使用任何花哨的任何子类定义时静默失败,例如eval

    【讨论】:

    • .p 文件也会失败。
    • 而且它需要一个更复杂的正则表达式来匹配多重继承。
    猜你喜欢
    • 2011-12-27
    • 1970-01-01
    • 2022-07-18
    • 2010-10-01
    • 1970-01-01
    • 2019-12-24
    • 2013-06-18
    • 2019-08-20
    • 2018-08-19
    相关资源
    最近更新 更多