检测 class 是否定义为声明式或函数式 - 可能吗?
Detect if class was defined declarative or functional - possible?
这是一个简单的 class 声明式创建:
class Person:
def say_hello(self):
print("hello")
这里有一个类似的 class,但它是通过手动调用 metaclass 定义的:
def say_hello(self):
print("sayolala")
say_hello.__qualname__ = 'Person.say_hello'
TalentedPerson = type('Person', (), {'say_hello': say_hello})
我很想知道它们是否无法区分。是否可以从 class 对象本身检测到这种差异?
>>> def was_defined_declaratively(cls):
... # dragons
...
>>> was_defined_declaratively(Person)
True
>>> was_defined_declaratively(TalentedPerson)
False
使用 python 无法在运行时检测到这种差异。
您可以使用第三方应用程序检查文件,但不能使用该语言检查文件,因为无论您如何定义 类,它们都应简化为解释器知道如何管理的对象。
其他一切都是语法糖,它会在文本操作的预处理步骤中消失。
整个元编程是一种让您接近compiler/interpreter工作的技术。
揭示一些类型特征,让您可以自由地使用代码处理类型。
有可能——有点。
inspect.getsource(TalentedPerson)
将失败并显示 OSError
,而它将成功并显示 Person
。这仅在您在定义它的文件中没有该名称的 class 时才有效:
如果您的文件包含这两个定义,并且 TalentedPerson
也认为它是 Person
,那么 inspect.getsource
将简单地找到 Person
的定义。
显然,这依赖于源代码仍然存在并且可以通过检查找到——这不适用于编译代码,例如在 REPL 中,可以被欺骗,有点作弊。实际的代码对象没有区别 AFAIK。
这根本不重要。即使我们挖掘更多不同的属性,也应该可以将这些属性注入动态创建的 class.
现在,即使没有源文件(inspect.getsource
之类的东西可以从源文件中找到,但见下文),class 主体语句也应该有相应的 "code"在某个时候是 运行 的对象。动态创建的 class 不会有代码体(但如果不是调用 type(...)
你调用 types.new_class
你也可以为动态 class 有一个自定义代码对象- 因此,至于我的第一个陈述:应该可以使两者 class 无法区分。
至于在不依赖源文件的情况下定位代码对象(除了 inspect.getsource
之外,可以通过方法的 .__code__
属性来实现,该属性注释 co_filename
和 co_fistlineno
(我想必须解析文件并找到 co_firstlineno
上方的 class
语句)
是的,就是这样:
给定一个模块,您可以使用 module.__loader__.get_code('full.path.tomodule')
- 这将 return 变成 code_object。这个对象有一个 co_consts
属性,它是一个序列,其中包含在该模块中编译的所有常量 - 其中包括 class 主体本身的代码对象。这些还有嵌套声明方法的行号和代码对象。
所以,一个天真的实现可能是:
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)
)
对于简单的情况。如果你必须检查 class 主体是否在另一个函数内部,或者嵌套在另一个 class 主体内,你必须在文件中的所有代码对象 .co_consts
属性中进行递归搜索>如果您发现检查 cls.__name__
之外的任何属性以断言您得到正确的 class 是否更安全,则相同。
同样,虽然这适用于 "well behaved" classes,但如果需要,可以动态创建所有这些属性 - 但这最终需要一个人替换代码对象sys.__modules__
中的模块 - 它开始变得比简单地向方法提供 __qualname__
更麻烦一些。
更新
此版本比较候选 class 上所有方法中定义的所有字符串。这将适用于给定的示例 classess - 通过比较其他 class 成员(例如 class 属性)和其他方法属性(例如变量名,甚至可能是字节码)可以实现更高的准确性. (出于某种原因,模块代码对象和 class 主体中方法的代码对象是不同的实例,尽管 code_objects 应该是不可变的)。
我将保留上面的实现,它只比较 class 名称,因为它应该更好地理解正在发生的事情。
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
这是一个简单的 class 声明式创建:
class Person:
def say_hello(self):
print("hello")
这里有一个类似的 class,但它是通过手动调用 metaclass 定义的:
def say_hello(self):
print("sayolala")
say_hello.__qualname__ = 'Person.say_hello'
TalentedPerson = type('Person', (), {'say_hello': say_hello})
我很想知道它们是否无法区分。是否可以从 class 对象本身检测到这种差异?
>>> def was_defined_declaratively(cls):
... # dragons
...
>>> was_defined_declaratively(Person)
True
>>> was_defined_declaratively(TalentedPerson)
False
使用 python 无法在运行时检测到这种差异。 您可以使用第三方应用程序检查文件,但不能使用该语言检查文件,因为无论您如何定义 类,它们都应简化为解释器知道如何管理的对象。
其他一切都是语法糖,它会在文本操作的预处理步骤中消失。
整个元编程是一种让您接近compiler/interpreter工作的技术。 揭示一些类型特征,让您可以自由地使用代码处理类型。
有可能——有点。
inspect.getsource(TalentedPerson)
将失败并显示 OSError
,而它将成功并显示 Person
。这仅在您在定义它的文件中没有该名称的 class 时才有效:
如果您的文件包含这两个定义,并且 TalentedPerson
也认为它是 Person
,那么 inspect.getsource
将简单地找到 Person
的定义。
显然,这依赖于源代码仍然存在并且可以通过检查找到——这不适用于编译代码,例如在 REPL 中,可以被欺骗,有点作弊。实际的代码对象没有区别 AFAIK。
这根本不重要。即使我们挖掘更多不同的属性,也应该可以将这些属性注入动态创建的 class.
现在,即使没有源文件(inspect.getsource
之类的东西可以从源文件中找到,但见下文),class 主体语句也应该有相应的 "code"在某个时候是 运行 的对象。动态创建的 class 不会有代码体(但如果不是调用 type(...)
你调用 types.new_class
你也可以为动态 class 有一个自定义代码对象- 因此,至于我的第一个陈述:应该可以使两者 class 无法区分。
至于在不依赖源文件的情况下定位代码对象(除了 inspect.getsource
之外,可以通过方法的 .__code__
属性来实现,该属性注释 co_filename
和 co_fistlineno
(我想必须解析文件并找到 co_firstlineno
上方的 class
语句)
是的,就是这样:
给定一个模块,您可以使用 module.__loader__.get_code('full.path.tomodule')
- 这将 return 变成 code_object。这个对象有一个 co_consts
属性,它是一个序列,其中包含在该模块中编译的所有常量 - 其中包括 class 主体本身的代码对象。这些还有嵌套声明方法的行号和代码对象。
所以,一个天真的实现可能是:
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)
)
对于简单的情况。如果你必须检查 class 主体是否在另一个函数内部,或者嵌套在另一个 class 主体内,你必须在文件中的所有代码对象 .co_consts
属性中进行递归搜索>如果您发现检查 cls.__name__
之外的任何属性以断言您得到正确的 class 是否更安全,则相同。
同样,虽然这适用于 "well behaved" classes,但如果需要,可以动态创建所有这些属性 - 但这最终需要一个人替换代码对象sys.__modules__
中的模块 - 它开始变得比简单地向方法提供 __qualname__
更麻烦一些。
更新 此版本比较候选 class 上所有方法中定义的所有字符串。这将适用于给定的示例 classess - 通过比较其他 class 成员(例如 class 属性)和其他方法属性(例如变量名,甚至可能是字节码)可以实现更高的准确性. (出于某种原因,模块代码对象和 class 主体中方法的代码对象是不同的实例,尽管 code_objects 应该是不可变的)。
我将保留上面的实现,它只比较 class 名称,因为它应该更好地理解正在发生的事情。
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