检测是否将类定义为声明式或函数式-可能吗? [英] Detect if class was defined declarative or functional - possible?

查看:87
本文介绍了检测是否将类定义为声明式或函数式-可能吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

解决方案

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

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

至于在不依赖源文件的情况下定位代码对象(可以通过方法的.__code__修饰符(co_filenameco_fistlineno来实现,而不是通过inspect.getsource到达)(我想一个人必须这样做)解析文件,然后在co_firstlineno上方找到class语句)

是的,它是: 给定一个模块,您可以使用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

Here's a simple class created declaratively:

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

And here's a similar class, but it was defined by invoking the metaclass manually:

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

say_hello.__qualname__ = 'Person.say_hello'

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

I'm interested to know whether they are indistinguishable. Is it possible to detect such a difference from the class object itself?

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

解决方案

This should not matter, at all. Even if we dig for more attributes that differ, it should be possible to inject these attributes into the dynamically created class.

Now, even without the source file around (from which, things like inspect.getsource can make their way, but see below), class body statements should have a corresponding "code" object that is run at some point. The dynamically created class won't have a code body (but if instead of calling type(...) you call types.new_class you can have a custom code object for the dynamic class as well - so, as for my first statement: it should be possible to render both classes indistinguishable.

As for locating the code object without relying on the source file (which, other than by inspect.getsource can be reached through a method's .__code__ attibute which anotates co_filename and co_fistlineno (I suppose one would have to parse the file and locate the class statement above the co_firstlineno then)

And yes, there it is: given a module, you can use module.__loader__.get_code('full.path.tomodule') - this will return a code_object. This object has a co_consts attribute which is a sequence with all constants compiled in that module - among those are the code objects for the class bodies themselves. And these, have the line number, and code objects for the nested declared methods as well.

So, a naive implementation could be:

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)
    )

For simple cases. If you have to check if the class body is inside another function, or nested inside another class body, you have to do a recursive search in all code objects .co_consts attribute in the file> Samething if you find if safer to check for any attributes beyond the cls.__name__ to assert you got the right class.

And again, while this will work for "well behaved" classes, it is possible to dynamically create all these attributes if needed - but that would ultimately require one to replace the code object for a module in sys.__modules__ - it starts to get a little more cumbersome than simply providing a __qualname__ to the methods.

update This version compares all strings defined inside all methods on the candidate class. This will work with the given example classess - more accuracy can be achieved by comparing other class members such as class attributes, and other method attributes such as variable names, and possibly even bytecode. (For some reason, the code object for methods in the module's code object and in the class body are different instances,though code_objects should be imutable) .

I will leave the implementation above, which only compares the class names, as it should be better for understanding what is going on.

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

这篇关于检测是否将类定义为声明式或函数式-可能吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆