检测类是否定义为声明性或功能性-可能吗?

时间:2018-11-05 16:24:10

标签: python metaclass inspect

这是一个声明式创建的简单类:

class Person:
    def say_hello(self):
        print("hello")

这是一个类似的类,但是它是通过手动调用元类来定义的:

def say_hello(self):
    print("sayolala")

say_hello.__qualname__ = 'Person.say_hello'

TalentedPerson = type('Person', (), {'say_hello': say_hello})

我想知道它们是否难以区分。是否可以检测到与类对象本身的差异?

>>> def was_defined_declaratively(cls):
...     # dragons
...
>>> was_defined_declaratively(Person)
True
>>> was_defined_declaratively(TalentedPerson)
False

3 个答案:

答案 0 :(得分:5)

这根本不重要。即使我们挖掘更多不同的属性,也应该有可能将这些属性注入动态创建的类中。

现在,即使没有源文件(inspect.getsource之类的东西也可以从中找到,但请参见下文),类主体语句应该具有在某个时间运行的相应“代码”对象。动态创建的类将没有代码主体(但是如果不调用type(...)而是调用types.new_class,则可以为动态类也具有自定义代码对象-因此,就我的第一条语句而言:应该可以使两个类都无法区分。

在不依赖源文件的情况下查找代码对象(可以通过方法inspect.getsource来修饰.__code__和{{1},而不必依靠源文件来定位代码对象}(我想必须解析该文件,然后在co_filename上方找到co_fistlineno语句)

是的,它是: 给定一个模块,您可以使用class-这将返回一个code_object。该对象具有co_firstlineno属性,该属性是在该模块中编译的所有常量的序列-其中包括类主体本身的代码对象。这些,还有行号和嵌套声明方法的代码对象。

因此,一个简单的实现可能是:

module.__loader__.get_code('full.path.tomodule')

对于简单的情况。如果您必须检查类主体是否在另一个函数内,或者嵌套在另一个类主体内,则必须在文件>文件中的所有代码对象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__更加麻烦。

更新 此版本比较候选类的所有方法中定义的所有字符串。这将与给定的示例类一起工作-通过比较其他类成员(例如类属性)和其他方法属性(例如变量名,甚至可能是字节码)可以实现更高的准确性。 (由于某种原因,模块代码对象和类主体中方法的代码对象是不同的实例,尽管code_objects应该是可插入的)。

我将保留上面的实现,该实现仅比较类名,因为它应该更好地了解正在发生的事情。

sys.__modules__

答案 1 :(得分:2)

使用python在运行时无法检测到这种差异。 您可以使用第三方应用程序检查文件,但不能使用语言检查,因为无论您如何定义类,都应将它们简化为解释器知道如何管理的对象。

其他所有内容都是语法糖及其在文本操作的预处理步骤中的消失。

整个元编程是一种使您接近编译器/解释器工作的技术。 揭示一些类型特征,使您可以自由地使用代码来处理类型。

答案 2 :(得分:2)

可能-有点。

inspect.getsource(TalentedPerson)将以OSError失败,而将以Person成功。仅当定义文件中没有该名称的类时,此方法才有效:

如果您的文件包含这两个定义,并且TalentedPerson也认为它是Person,那么inspect.getsource只会找到Person的定义。

显然,这依赖于源代码仍然存在并且可以通过检查找到-这不适用于编译后的代码,例如在REPL中,可以被欺骗,并且有点作弊。实际的代码对象没有AFAIK。