这根本不重要。即使我们挖掘更多不同的属性,也应该可以将这些属性注入到动态创建的类中。
现在,即使没有源文件(从中,诸如inspect.getsource
可以按自己的方式进行,但请参见下文),类体语句应该有一个在某个时刻运行的相应“代码”对象。动态创建的类不会有代码体(但如果不是调用type(...)
你打电话types.new_class
您也可以为动态类拥有一个自定义代码对象 - 因此,对于我的第一句话:应该可以使这两个类无法区分。
至于在不依赖源文件的情况下定位代码对象(除了通过inspect.getsource
可以通过方法达到.__code__
注释的属性co_filename
and co_fistlineno
(我想人们必须解析该文件并找到class
上面的声明co_firstlineno
then)
是的,就是这样:
给定一个模块,您可以使用module.__loader__.get_code('full.path.tomodule')
- 这将返回一个 code_object。这个对象有一个co_consts
attribute 是一个包含在该模块中编译的所有常量的序列 - 其中包括类体本身的代码对象。这些也具有行号和嵌套声明方法的代码对象。
因此,一个简单的实现可能是:
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__
到方法。
update此版本比较候选类的所有方法内定义的所有字符串。这将适用于给定的示例类 - 通过比较其他类成员(例如类属性)和其他方法属性(例如变量名,甚至可能是字节码)可以实现更高的准确性。 (由于某种原因,模块代码对象和类主体中方法的代码对象是不同的实例,尽管 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