【问题标题】:pylint, coroutines, decorators and type inferencingpylint、协程、装饰器和类型推断
【发布时间】:2016-06-28 23:14:28
【问题描述】:

我正在开发一个 Google AppEngine 项目,我最近将我的 pylint 版本升级到:

No config file found, using default configuration
pylint 1.5.6, 
astroid 1.4.6
Python 2.7.10 (default, Oct 23 2015, 19:19:21)

这似乎破坏了某些类型推断。具体来说,GAE 的ndb uses a decorator and a generator function to return a "Future" object 是这样的:

@ndb.tasklet
def coroutine_like(item_id):
    # do something here...
    item = yield EntityType.get_by_id_async(item_id)
    raise ndb.Return(item)

我可以这样称呼它:

future = coroutine_like('12345')
# Do other stuff
entity = future.get_result()

以前,我在这里的 linter 没有任何问题。现在我得到了:

E: 42,17: Generator 'generator' has no 'get_result' member (no-member)
E: 48,17: Generator 'generator' has no 'get_result' member (no-member)
E: 60,25: Generator 'generator' has no 'get_result' member (no-member)
E: 74, 8: Generator 'generator' has no 'wait' member (no-member)
E: 88, 8: Generator 'generator' has no 'wait' member (no-member)
E: 95,17: Generator 'generator' has no 'get_result' member (no-member)

我意识到我可以单独# pylint: disable=no-member 这些行,但这会很麻烦。我还意识到我可以通过在模块级别添加抑制代码来抑制模块级别的警告,并且我可以通过修改我的 pylintrc 文件来全局抑制警告。我真的不想做那些事。我宁愿(不知何故)告诉 pylint 用 @ndb.tasklet 装饰器装饰的东西返回 ndb.Future 实例。我已经看到有 ways to register type-inferencing helpers1 用于 pylint,但我不确定如何使它们与我的生成器函数装饰器一起使用。

1请注意,这是一篇相当古老的博文...我认为 logilab.astng 已不再使用,现在您可以改用 astroid,但事实并非如此没有让我更接近我正在寻找的答案......

【问题讨论】:

    标签: python pylint


    【解决方案1】:

    那篇博文肯定很老了,现在事情已经改变了一段时间。

    您可以看看 astroid 的大脑模块是如何实现的 (https://github.com/PyCQA/astroid/tree/master/astroid/brain)。它们通常是 AST 转换器,应用于特定的 AST,提供修改以便 pylint 了解您的代码到底发生了什么。

    转换通常是一个函数,它接收一个节点并应该返回一个新节点或修改过的同一节点(请注意,尽管将来我们将删除对修改同一节点的支持,它们将变得不可变)

    您可以通过以下方式注册一个

    astroid.MANAGER.register_transform(type_of_node, transform_function)
    

    但是通常可以为 register_transform 提供一个过滤器,以便它只应用于您感兴趣的特定节点。过滤器是 register_transform 的第三个参数,它是一个接收节点的函数,应该返回一个布尔值,如果应该转换节点,则为 true,否则为 false。您还可以通过将第二个参数包装在astroid.inference_tip(...) 中,将此转换作为推理提示,而不是普通推理机制。这可能是您想要的,因为您想帮助 pylint 正确推断此函数,而不是向 AST 本身添加构造。 在这种特殊情况下,转换可能会返回 ndb.Return 的实例,并使用函数中的屈服点进行初始化。另外,请注意,您可以从字符串构建 AST,仅使用代码表示,如下所示:

    ast = astroid.parse('''...'''
    return ast
    

    但如果您想要更细粒度的方法,您可以自己构建 AST(粗略示例):

    from astroid import MANAGER
    module = MANAGER.ast_from_module_name('ndb')
    cls = next(module.igetattr('Return'))
    instance = cls.instantiate_class()
    node = astroid.Return(...)
    node.value = ... node
    return node
    

    另外,请注意,创建新节点会随着最新版本的变化而变化,通过使用适当的构造方法来构建它们,而不是手动添加属性。

    希望这会有所帮助。

    【讨论】:

    • 感谢您的回答。我仍然在我在这里和那里的空闲时间消化它。是否有任何地方的示例存储库(可能在 pylint 代码库中?)在那里我可以看看这些事情是如何完成的?
    • 不幸的是,您可以找到一些结构化示例的最佳位置仍然是所谓的 astroid 大脑 (github.com/PyCQA/astroid/tree/master/astroid/brain),并且通常贯穿 astroid 的代码库。你也可能在#pylint-dev (freenode) 上得到一些结果
    • 太棒了。感谢这些例子。我认为我能够一起破解一些可行的东西(在下面作为单独的答案发布)。如果您在那里看到任何看起来可疑的东西,请随时告诉我。如果这一切看起来合理,我可能会继续尝试让ndbpylint 开心,并为 OSS 社区的其他成员创建一个单独的 repo...
    【解决方案2】:

    根据上面 PCManticore 的建议,我已经能够一起破解:

    """Brains for helping silence pylint errors/warnings from ndb."""
    import astroid
    
    
    def _is_tasklet(node):
        """Check whether a FunctionDef node is decorated with ndb.tasklet."""
        if not node.decorators:
            return False
        return 'google.appengine.ext.ndb.tasklets.tasklet' in node.decoratornames()
    
    @astroid.inference_tip
    def _infer_tasklet(node, context=None):  # pylint: disable=unused-argument
        """Infer the type of tasklets."""
    
        # Does the name of the function matter?  Should it be global?
        module = astroid.parse("""
        import google.appengine.ext.ndb.tasklets
        def tasklet_function(*args, **kwargs):
            return google.appengine.ext.ndb.tasklets.Future()
        """)
        tasklet_function = next(
            module.igetattr('tasklet_function', context=context))
        return iter([tasklet_function])
    
    
    astroid.MANAGER.register_transform(
        astroid.FunctionDef,
        _infer_tasklet,
        _is_tasklet)
    
    def register(linter):  # pylint: disable=unused-argument
        """Register the plugin with the linter."""
    

    我不知道这是否理想,或者这种方法是否存在任何主要缺点,但是,假设您的路径设置正确 - 例如上面的脚本(当前)位于/path/to/pylint_helpers/ndb_brain.py,我在/usr/local/google_appengine 中安装了dev_appserver.py)和以下pylint-config 文件:

    [MASTER]
    init-hook='import sys; sys.path[0:0] = ("/usr/local/google_appengine", "/path/to/pylint_helpers"); import dev_appserver; dev_appserver.fix_sys_path()'
    load-plugins=ndb_brain
    

    它似乎使 tasklet 警告静音(哇哦!)。基本思想是我为每个用ndb.tasklet 装饰的函数添加一个显式推理提示。推理技巧基本上只是告诉pylint,该函数返回一个ndb.Future,而不是作为生成器函数。我认为,因为这是一个推理技巧,而不是对 AST 的重写(通过转换节点),所以就 pylint 而言,它不应该有任何其他不利影响。

    【讨论】:

    • 这看起来非常棒!不要忘记将上下文传递给 igetattr 调用 igetattr('tasklet_function', context=context)。在大多数情况下,上下文可以改善推理,因为它将本地上下文与遍历的推理路径相结合。不过,我不明白,为什么必须在 init-hook 中传递 ndb_brain。通常我们通过pylint --load-plugins=pylint_plugin 执行此操作。 plugin 需要在其全局范围内具有此功能,github.com/PyCQA/pylint/blob/master/pylint/extensions/…,但在您的情况下,它可以为空。
    • @PCManticore -- 再次感谢您的帮助。我已经更新了。
    猜你喜欢
    • 2018-08-02
    • 2016-07-23
    • 2020-01-17
    • 1970-01-01
    • 1970-01-01
    • 2018-07-16
    • 1970-01-01
    • 2018-06-01
    • 1970-01-01
    相关资源
    最近更新 更多