代码之家  ›  专栏  ›  技术社区  ›  wim

检测类的定义是声明性的还是函数性的-可能吗?

  •  8
  • wim  · 技术社区  · 6 年前

    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 回复  |  直到 6 年前
        1
  •  5
  •   jsbueno    6 年前

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

    现在,即使没有源文件(例如 inspect.getsource 类主体语句应该有一个相应的“代码”对象,该对象在某个点上运行。动态创建的类没有代码体(但是如果 type(...) types.new_class 您也可以为动态类提供一个自定义代码对象—因此,对于我的第一条语句:应该可以使这两个类不可区分。

    至于定位代码对象而不依赖源文件(除了 检查.getsource 可以通过方法的 .__code__ 阿诺塔特 co_filename co_fistlineno class 上面的语句 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__

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

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

    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
    
        2
  •  2
  •   Petar Velev    6 年前

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

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

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

        3
  •  2
  •   L3viathan gboffi    6 年前

    这是有可能的。

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

    如果文件同时包含这两个定义,以及 TalentedPerson 也相信是的 ,那么 inspect.getsource 只会发现 的定义。

    很明显,这依赖于源代码仍然存在并且可以通过inspect找到,这对编译后的代码不起作用,例如在REPL中,可能会被欺骗,这是一种欺骗。实际的代码对象没有区别。