Есть ли способ найти всех детей класса Matlab?

Функция Matlab superclasses возвращает имена всех родителей данного класса.

Есть ли эквивалент, чтобы найти все классы, полученные из данного класса, то есть дочерние классы? Функция allchild, по-видимому, ограничена графическими дескрипторами.

Если нет, то какая стратегия может быть принята для получения такого списка? Сканирование единственного пути - это единственный вариант?

Ограничимся классами в пути Matlab.

Ответ 1

Введение:

В ходе решения я, похоже, нашел недокументированный статический метод класса meta.class, который возвращает все кэшированные классы (почти все, что стирается, когда кто-то звонит clear classes), а также (полностью случайно) сделал инструмент, который проверяет файлы classdef на наличие ошибок.


Поскольку мы хотим найти подклассы all, верный путь - это сделать список всех известных классов, а затем проверить каждый из них, если он получен из любого другой. Для этого мы отделяем наши усилия на 2 типа классов:

  • "Массовые классы" - здесь мы используем функцию what, чтобы составить список файлов, которые просто "лежат вокруг" на MATLAB, который выводит структуру s (описанную в документах what со следующими полями: 'path' 'm' 'mlapp' 'mat' 'mex' 'mdl' 'slx' 'p' 'classes' 'packages'. выберите некоторые из них, чтобы создать список классов. Чтобы определить, какое содержимое имеет файл .m или .p(что нас интересует, это класс/не-класс), мы используем exist Этот метод демонстрирует Лорен в своем блоге. В моем коде это mb_list.
  • "Классы пакетов" - это включает в себя файлы классов, индексированные MATLAB как часть внутренней структуры пакета. Алгоритм, участвующий в получении этого списка, включает вызов meta.package.getAllPackages, а затем рекурсивное перемещение этого списка пакетов верхнего уровня для получения всех подпакетов. Затем список классов извлекается из каждого пакета, и все списки объединяются в один длинный список - mp_list.

script имеет два входных флага (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 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<meta.class, List<meta.class>>:

  • mc_list - вектор объекта класса meta.class, где каждая запись содержит информацию об одном конкретном классе, известном MATLAB. Это "ключи" нашего Map.
  • subcls_list - (довольно разреженный) вектор ячеек, содержащий известные прямые подклассы классов, появляющиеся в соответствующей позиции mc_list. Это "значения" нашего Map, которые по существу List<meta.class>.

Как только у нас появятся эти два списка, нужно только найти позицию вашего класса интереса в mc_list и получить список его подклассов из subcls_list. Если требуются косвенные подклассы, тот же процесс повторяется и для подклассов.

В качестве альтернативы можно представить иерархию, например, a logical sparse матрица смежности A, где a i, j == 1 означает, что класс i является подклассом j. Тогда транспонирование этой матрицы может означать противоположное отношение, т.е. A T i, j == 1 означает i является super класс j. Сохранение этих свойств матрицы смежности позволяет очень быстро выполнять поиск и обход иерархии (избегая необходимости "дорогого" сравнения объектов meta.class).

Несколько примечаний:

  • По причинам, неизвестным (кэширование?), код может завершиться ошибкой из-за ошибки (например, Invalid or deleted object.), в этом случае повторное выполнение этого помогает. Я добавил try/catch, который делает это автоматически.
  • В коде, где массивы растут внутри цикла, есть 2 экземпляра. Это, конечно, нежелательно и его следует избегать. Код был оставлен таким образом из-за отсутствия лучших идей.
  • Если не удается избежать "открытия" части алгоритма (как-то найти все классы в первую очередь), можно (и должно) оптимизировать его, чтобы каждая итерация работала только на ранее неизвестных классах.
  • Интересным непредвиденным преимуществом запуска этого кода является то, что он сканирует все известные classdef и сообщает о любых ошибках в них - . Это может быть полезным инструментом для запуска каждый раз для любого, кто работает с OAT MATLAB:)
  • Спасибо @Suever за некоторые полезные указатели.

Сравнение с методом Олега:

Чтобы сравнить эти результаты с примером Олега, я буду использовать вывод прогона вышеуказанного script на моем компьютере (содержащий классы ~ 20k, загруженный здесь в качестве файла .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 - В настоящее время я понятия не имею, что вызывает это.

Ответ 2

Код

Я переместил код, так как он был > 200 строк в репозиторий Github getSubclasses. Вы можете повторно использовать функции и сообщать об ошибках.

Идея

Учитывая имя класса root или meta.class и путь к папке, он пересечет структуру папок и построит график со всеми подклассами, полученными из корня (на бесконечной глубине). Если путь не указан, то он переместится из папки, где находится корневой класс.

Обратите внимание, что решение является локальным, и поэтому оно быстро и полагается на предположение, что подклассы вложены под какую-либо подпапку выбранного пути.

Пример

Перечислить все подклассы класса sde. Для получения графика вам понадобится R2015b, или вы можете использовать вывод и представление FEX plot_graph(), чтобы создать граф зависимостей,

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

введите описание изображения здесь

И вывод с краями и node имена:

 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 

Мы можем сравнить результат с официальной документацией, которая в этом случае перечисляет иерархию введите описание изображения здесь

Timing

В Win7 64b R2016a

  • меньше 0,1 секунды: getSubclasses('sde','C:\Program Files\MATLAB\R2016a\toolbox\finance\finsupport')
  • около 13 секунд, если сканировать весь матабрут: getSubclasses('sde',matlabroot);

Ответ 3

Не полное решение, но вы можете анализировать все .m файлы в пути как текст и использовать регулярные выражения для поиска определений подкласса.

Что-то вроде ^\s*classdef\s*(\w*)\s*<\s*superClassName\s*(%.*)?

Обратите внимание, что это будет терпеть неудачу в любых определениях подклассов, которые используют что-либо фантастическое, например eval.