Это не должно иметь никакого значения. Даже если мы поищем больше атрибутов, которые могут отличаться, можно будет добавить эти атрибуты в динамически создаваемый класс.
Теперь, даже без исходного файла (из которого такие вещи, как inspect.getsource
могут пробиться, но см. Ниже), операторы тела класса должны иметь соответствующий объект "код", который выполняется в какой-то момент. Динамически созданный класс не будет иметь тела кода (но если вместо вызова type(...)
вы вызываете types.new_class
, вы также можете иметь собственный объект кода для динамического класса - так, что касается моего первого утверждения: это должно быть можно сделать оба класса неразличимыми.
Что касается определения местоположения объекта кода, не полагаясь на исходный файл (который, кроме inspect.getsource
, может быть достигнут через атрибут .__code__
метода, который аннотирует co_filename
и co_fistlineno
(я полагаю, нужно было бы проанализируйте файл и найдите оператор class
над co_firstlineno
затем)
И да, вот оно:
для данного модуля вы можете использовать module.__loader__.get_code('full.path.tomodule')
- это вернет code_object. Этот объект имеет атрибут co_consts
, который представляет собой последовательность со всеми константами, скомпилированными в этом модуле - среди них объекты кода для самих тел классов. И у них есть номер строки и объекты кода для вложенных объявленных методов.
Итак, наивная реализация может быть:
import sys, types
def was_defined_declarative(cls):
module_name = cls.__module__
module = sys.modules[module_name]
module_code = module.__loader__.get_code(module_name)
return any(
code_obj.co_name == cls.__name__
for code_obj in module_code.co_consts
if isinstance(code_obj, types.CodeType)
)
Для простых случаев. Если вам нужно проверить, находится ли тело класса внутри другой функции или вложено в другое тело класса, вам нужно выполнить рекурсивный поиск по всем объектам кода .co_consts
атрибут в файле> То же самое, если вы найдете, что безопаснее проверить на наличие каких-либо атрибуты за cls.__name__
, чтобы утверждать, что вы получили правильный класс.
И снова, хотя это будет работать для классов с "хорошим поведением", можно динамически создавать все эти атрибуты, если это необходимо, но для этого в конечном итоге потребуется заменить объект кода для модуля в sys.__modules__
- он запускается получить немного более обременительно, чем просто предоставить __qualname__
методам.
обновление
Эта версия сравнивает все строки, определенные внутри всех методов класса-кандидата. Это будет работать с классом данного примера - можно добиться большей точности, сравнивая другие члены класса, такие как атрибуты класса, и другие атрибуты метода, такие как имена переменных, и, возможно, даже байт-код. (По какой-то причине объект кода для методов в объекте кода модуля и в теле класса - это разные экземпляры, хотя code_objects должен быть неизменным).
Я оставлю реализацию выше, которая сравнивает только имена классов, так как это должно быть лучше для понимания происходящего.
def was_defined_declarative(cls):
module_name = cls.__module__
module = sys.modules[module_name]
module_code = module.__loader__.get_code(module_name)
cls_methods = set(obj for obj in cls.__dict__.values() if isinstance(obj, types.FunctionType))
cls_meth_strings = [string for method in cls_methods for string in method.__code__.co_consts if isinstance(string, str)]
for candidate_code_obj in module_code.co_consts:
if not isinstance(candidate_code_obj, types.CodeType):
continue
if candidate_code_obj.co_name != cls.__name__:
continue
candidate_meth_strings = [string for method_code in candidate_code_obj.co_consts if isinstance(method_code, types.CodeType) for string in method_code.co_consts if isinstance(string, str)]
if candidate_meth_strings == cls_meth_strings:
return True
return False