【问题标题】:Trying to get a grip on CachedPropery in clang\cindex.py试图在 clang\cindex.py 中掌握 CachedPropery
【发布时间】:2017-01-09 17:30:49
【问题描述】:

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

我已经阅读了大量关于 Python 中的 decorators 和 descriptors 的文章,以了解 CachedProperty class in clang/cindex.py 的工作原理,但仍然无法将所有部分放在一起。

我见过的最相关的文字是one SO answer,而这个code recipe in ActiveState。这对我有点帮助,但是 - 正如我所提到的 - 我仍然不在那里。

那么,让我们切入正题: 我想了解为什么我在创建 CIndex 时会收到 AssertionError。我将只在这里发布相关代码(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

CachedProperty 代码:

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__ 应该将值返回到哪里? CDLL.__getattr__ 方法的调用来自哪里?

对我来说最关键的部分

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

这是creation of ClangObject,Index 类继承自。

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

总而言之,对我来说最好的将是这里的函数调用的逐步指导,因为我在这一切中感到有些失落......

【问题讨论】:

    标签: python clang decorator python-decorators python-descriptors


    【解决方案1】:

    CachedProperty 对象是descriptor object;每当 Python 尝试访问实例上的属性时,__get__ 方法就会自动调用,而该属性仅在类 中可用并且 具有 __get__ 方法。

    使用CachedProperty 作为装饰器意味着它被调用并创建了CachedProperty 的实例来替换Config 类上的原始函数对象。正是@CachedProperty 行导致CachedProperty.__init__ 被调用,并且实例在Config 类中以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>
    

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

    (Pdb) 'lib' in config.__dict__
    False
    

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

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

    调用__getattr__方法满足conf.lib.clang_createIndex(excludeDecls, 0)表达式中的next属性; config.lib 从cdll.LoadLibrary()(通过CachedProperty.__get__())返回一个动态加载的库,而specific object type 由Python ctypes libary 处理。它为您将属性转换为特定的 C 调用;这就是clang_createIndex 方法;见Accessing functions from loaded dlls。

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

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

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

    clang 的其余 Python 代码只会将此值传递回由config.lib 代理的更多 C 调用。

    【讨论】:

    • 好吧,实际上,最重要的部分并没有被您回答,因为 Python 给出的类型实际上是 int(请参阅我的其他问题),但您提供了帮助我非常了解发生了什么-我别无选择,只能接受您的回答...我将发布我的问题的解决方案(我自己找到了!!耶!!)作为另一个答案,但真的 - 谢谢。
    • @BakItzik:我实际上没有安装clang,你的问题更集中在CachedProperty在做什么(这是你问题的标题)。对于未来的问题,请尝试将您的问题分解为不同的部分。
    猜你喜欢
    • 2021-10-09
    • 1970-01-01
    • 1970-01-01
    • 2011-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多