试图掌握 clang\cindex.py 中的 CachedPropery

Trying to get a grip on CachedPropery in clang\cindex.py

这与我的其他 question 有关,但没有答案... 我试图了解 Python binding to libclang 的幕后发生了什么,但真的很难做到。

为了理解 CachedProperty class in clang/cindex.py 的工作原理,我在 Python 中阅读了大量关于 decoratorsdescriptors 的文章,但仍然无法理解把所有的碎片放在一起。

我见过的最相关的文本是 one SO answer, and this code recipe 在 ActiveState 中。这对我有点帮助,但是 - 正如我提到的 - 我仍然不在那里。

所以,让我们切入正题: 我想了解为什么我在创建 CIndex 时得到 AssertionError。我这里只post相关的代码(cindex.py是3646行长..),我希望我不会遗漏任何与我相关的东西。 我的代码只有一行相关,即:

index = clang.cindex.Index.create()

这参考了 line 2291 in cindex.py,它产生:

return Index(conf.lib.clang_createIndex(excludeDecls, 0))

从现在开始,有一系列的函数调用,我无法解释它们的原因和来源。我将列出代码和 pdb 输出以及与每个部分相关的问题:

(提前注意的重要事项:conf.lib 定义如下:)

class Config:
    ...snip..

    @CachedProperty
    def lib(self):
        lib = self.get_cindex_library()
        ...
        return lib

缓存属性代码:

class CachedProperty(object):
    """Decorator that lazy-loads the value of a property.

    The first time the property is accessed, the original property function is
    executed. The value it returns is set as the new value of that instance's
    property, replacing the original method.
    """

    def __init__(self, wrapped):
        self.wrapped = wrapped
        try:
            self.__doc__ = wrapped.__doc__
        except:
            pass

    def __get__(self, instance, instance_type=None):
        if instance is None:
            return self

        value = self.wrapped(instance)
        setattr(instance, self.wrapped.__name__, value)

        return value

Pdb 输出:

-> return Index(conf.lib.clang_createIndex(excludeDecls, 0))
(Pdb) s
--Call--
> d:\project\clang\cindex.py(137)__get__()
-> def __get__(self, instance, instance_type=None):
(Pdb) p self
<clang.cindex.CachedProperty object at 0x00000000027982E8>
(Pdb) p self.wrapped
<function Config.lib at 0x0000000002793598>
  1. 为什么下次调用之后 Index(conf.lib.clang_createIndex(excludeDecls, 0)) 是为了 CachedProperty.__get__ 方法? __init__ 呢?
  2. 如果 __init__ 方法没有被调用,为什么 self.wrapped 有 值?

Pdb 输出:

(Pdb) r
--Return--
> d:\project\clang\cindex.py(144)__get__()-><CDLL 'libcla... at 0x27a1cc0>
-> return value
(Pdb) n
--Call--
> c:\program files\python35\lib\ctypes\__init__.py(357)__getattr__()
-> def __getattr__(self, name):
(Pdb) r
--Return--
> c:\program files\python35\lib\ctypes\__init__.py(362)__getattr__()-><_FuncPtr obj...000000296B458>
-> return func
(Pdb)
  1. CachedProperty.__get__ 应该 return 值在哪里? CDLL.__getattr__ 方法的调用从何而来?

对我来说最关键的部分

(Pdb) n
--Call--
> d:\project\clang\cindex.py(1970)__init__()
-> def __init__(self, obj):
(Pdb) p obj
40998256

这是 creation of ClangObject,class 索引继承自它。

  1. 但是 - 哪里有任何带有一个参数的 __init__ 调用?这是 conf.lib.clang_createIndex(excludeDecls, 0) return 的那个吗?
  2. 这个号码 (40998256) 是从哪里来的?我一遍又一遍地收到相同的号码。据我了解,它应该只是一个数字,但是 clang.cindex.LP_c_void_p object 这就是断言失败的原因。

总而言之,对我来说最好的是在此处逐步指导函数调用,因为我对这一切感到有点迷茫...

CachedProperty对象是descriptor object__get__ 方法在 Python 尝试访问仅在 class 具有 __get__方法。

使用 CachedProperty 作为装饰器意味着它被调用并创建一个 CachedProperty 的实例来替换 Config class 上的原始函数对象。正是 @CachedProperty 行导致 CachedProperty.__init__ 被调用,实例最终在 Config class 上作为 Config.lib。请记住,语法

@CachedProperty
def lib(self):
    # ...

本质上执行为

def lib(self):
    # ...
lib = CachedProperty(lib)

所以这创建了一个 CachedProperty() 的实例,其中 lib 作为 wrapped 参数传入,然后 Config.lib 设置为该对象。

你可以在调试器中看到这个;更进一步,您可以检查 type(config).lib:

(Pdb) type(config)
<class Config at 0x00000000027936E>
(Pdb) type(config).lib
<clang.cindex.CachedProperty object at 0x00000000027982E8>

在代码库的其余部分 configConfig class 的一个实例。起初,该实例在 __dict__ 对象中没有 lib 名称,因此该实例没有这样的属性:

(Pdb) 'lib' in config.__dict__
False

所以试图获取 config.lib 必须回退到 class,其中 Python 找到 Config.lib 属性,这是一个描述符对象。在这种情况下,Python return 不是直接使用 Config.lib,而是调用 Config.lib.__get__(config, Config) 的结果。

然后 __get__ 方法执行原始函数(由 wrapped 引用)并将其存储在 config.__dict__ 中。所以 future 访问 config.lib 会找到那个结果,class 上的描述符之后就不会再用了。

调用__getattr__方法满足conf.lib.clang_createIndex(excludeDecls, 0)表达式中的next属性; config.lib returns 来自 cdll.LoadLibrary() 的动态加载库(通过 CachedProperty.__get__()),并且 specific object type is handled by the Python ctypes libary. It translates attributes to specific C calls for you; here that's the clang_createIndex method; see Accessing functions from loaded dlls.

一旦对 conf.lib.clang_createIndex(excludeDecls, 0) 的调用完成,结果对象确实被传递给 Index()Index() class itself has no __init__ method, but the base class ClangObject 确实如此。

无论 return 值是什么,它都有一个看起来像整数的 表示 。但是,它几乎可以肯定不是 int。您可以使用 type() 查看它是什么类型的对象,使用 dir() 查看它具有什么属性,等等。我很确定它是 ctypes.c_void_p data type 代表 一个 clang.cindex.LP_c_void_p 值(它是一个 Python 对象,代表内存中的真实 C 值);它将表示为一个整数:

Represents the C void * type. The value is represented as integer. The constructor accepts an optional integer initializer.

clang Python 代码的其余部分将把这个值传递回更多由 config.lib 代理的 C 调用。