1

这与我遇到的其他问题有关,没有答案......我试图了解Python 绑定libclang的幕后发生的事情,并且很难做到这一点。

我已经阅读了大量关于PythondecoratorsdescriptorsPython 的文章,以了解clang/cindex.py 中的 CachedProperty 类是如何工作的,但仍然无法将所有部分放在一起。

我见过的最相关的文本是一个 SO answer和 ActiveState 中的这个代码配方。这对我有点帮助,但是——正如我所提到的——我仍然不在那里。

所以,让我们切入正题:我想了解我为什么要AssertionError创建 CIndex。我将只在这里发布相关代码(cindex.py 是 3646 行长..),我希望我不会错过任何与我相关的内容。我的代码只有一个相关行,即:

index = clang.cindex.Index.create()

这指的是 cindex.py 中的第 2291 行,它产生:

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

从现在开始,有一系列的函数调用,我无法解释为什么它们来自于 WTH。我将列出与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))是 to 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__应该将值返回到哪里?方法的调用CDLL.__getattr__来自哪里?

对我来说最关键的部分

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

这是Index 继承自的类的创建。ClangObject

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

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

4

1 回答 1

2

CachedProperty对象是一个描述符对象__get__每当 Python 尝试访问仅在类上可用具有方法的实例上的属性时,都会自动调用该__get__方法。

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

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

基本上执行为

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

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

您可以在调试器中看到这一点;上一步你可以检查type(config).lib

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

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

(Pdb) 'lib' in config.__dict__
False

因此,尝试获取config.lib必须回退到 Python 找到Config.lib属性的类,这是一个描述符对象。在这种情况下,Python 不会直接使用,而是Config.lib返回调用的结果Config.lib.__get__(config, Config)

然后该__get__方法执行原始函数(由 引用wrapped)并将其存储在config.__dict__. 所以未来的访问config.lib会找到那个结果,并且在那之后类上的描述符不会被使用。

调用该__getattr__方法以满足表达式中的下一个属性;从(via )返回一个动态加载的库,并且该特定对象类型由 Python ctypes libary处理。它为您将属性转换为特定的 C 调用;这就是方法;请参阅从加载的 dll 访问函数conf.lib.clang_createIndex(excludeDecls, 0)config.libcdll.LoadLibrary()CachedProperty.__get__()clang_createIndex

一旦调用conf.lib.clang_createIndex(excludeDecls, 0)完成,结果对象确实被传递给Index(); 类本身没有方法,但基类Index()__init__ClangObject

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

表示 Cvoid *类型。该值表示为整数。构造函数接受一个可选的整数初始值设定项。

其余的clangPython 代码只会将此值传递回由config.lib.

于 2016-09-01T18:12:39.693 回答