pylint,coroutines,decorators和类型推理

时间:2016-06-28 23:14:28

标签: python pylint

我正在开发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()

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

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个实例。我已经看到pylint有ways to register type-inferencing helpers 1 ,但我不确定如何让它们与我的生成器函数的装饰器一起工作。

1 请注意,这是一篇非常古老的博文...我认为logilab.astng已不再使用,现在您将使用astroid代替,但这并没有让我更接近我正在寻找的答案......

2 个答案:

答案 0 :(得分: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的第三个参数,它是一个接收节点的函数,应该返回一个boolean,如果要转换节点,则为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

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

希望这有帮助。

答案 1 :(得分:0)

根据上面的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并且dev_appserver.py中已安装/usr/local/google_appengine)以及以下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而言它不应该有任何其他不利影响