Ответ 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 - В настоящее время я понятия не имею, что вызывает это.