是否定义了可能的声明类?

2024-04-19 21:27:05 发布

您现在位置:Python中文网/ 问答频道 /正文

下面是一个以声明方式创建的简单类:

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

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

^{pr2}$

我很想知道它们是否无法区分。是否有可能检测到与类对象本身的差异?在

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

Tags: self声明hello定义def方式手动class
3条回答

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

其他的一切都是语法糖和在文本操作的预处理步骤的死亡。在

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

这是可能的-有点。在

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

如果您的文件包含这两个定义,并且TalentedPerson还认为它是Person,那么{}将只找到{}的定义。在

显然,这依赖于源代码仍然存在,并且可以被inspect找到——这对编译后的代码不起作用,例如在REPL中,可以被欺骗,这是一种欺骗。实际的代码对象没有什么不同。在

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

现在,即使没有源文件(例如inspect.getsource可以从源文件中找到自己的方法,但请参见下文),类主体语句应该有一个在某个时刻运行的相应的“code”对象。动态创建的类没有代码体(但是如果不调用type(...)而调用types.new_class,那么动态类也可以有一个自定义的代码对象—因此,对于我的第一个语句:应该可以使这两个类不可区分。在

至于在不依赖源文件的情况下定位代码对象(除了inspect.getsource之外,它可以通过一个方法的.__code__属性来实现,该属性声明了co_filename和{}(我想人们必须解析文件并在co_firstlineno上方定位{}语句)

是的,就是这样: 给定一个模块,您可以使用module.__loader__.get_code('full.path.tomodule')-这将返回一个code\u对象。这个对象有一个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__多了一点麻烦。在

更新 此版本比较候选类的所有方法中定义的所有字符串。这将适用于给定的classess示例—通过比较其他类成员(如类属性)和其他方法属性(如变量名),甚至可能是字节码,可以获得更高的精确度。(由于某些原因,模块的code对象和类主体中的方法的code对象是不同的实例,尽管code\u对象应该是可模拟的)。在

我将离开上面的实现,它只比较类名,因为它应该更好地理解正在发生的事情。在

^{pr2}$

相关问题 更多 >