Python pylint、协同程序、装饰器和类型推断

Python pylint、协同程序、装饰器和类型推断,python,pylint,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.tasklet def coroutine_like(item_id): # do

我正在从事一个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.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()
我可以这样称呼它:

@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有1个,但我不知道如何让它们与我的生成器函数装饰器一起工作


1请注意,这是一篇相当古老的博文。。。我认为
logilab.astng
已经不再使用了,现在你可以用
astroid
来代替,但这并不能让我离我想要的答案太近……

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

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

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

你可以通过

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

ast = astroid.parse('''...'''
return ast
但是如果您想要更细粒度的方法,您可以自己构建AST(粗略的示例):

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


希望这能有所帮助。

根据上面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\u helpers/ndb\u brain.py
上,我在
/usr/local/google\u appengine
中安装了
dev\u appserver.py
)和以下pylint配置文件:

[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

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

感谢您的回答。我仍然在这里和那里的空闲时间里消化它。在任何地方(可能在pylint代码库中?)都有示例库,在那里我可以看看这些事情是如何完成的?不幸的是,你能找到一些结构化示例的最好地方仍然是所谓的astroid大脑()和astroid的代码库。在#pylint dev(freenode)Awesome上,您可能也会得到一些结果。谢谢你的例子。我想我能够一起破解一些可行的东西(作为一个单独的答案发布在下面)。如果你在那里看到任何可疑的东西,请随时告诉我。如果一切看起来合理,我可能会继续尝试让
ndb
pylint
更开心,并为OSS社区的其他成员创建一个单独的repo…这看起来非常棒!不要忘记将上下文传递给IGETTR调用,
IGETTR('tasklet_function',context=context)
。在大多数情况下,上下文可以改善推理,因为它会通过遍历的推理路径破坏局部上下文。我不明白,为什么你必须在init hook中通过ndb_大脑。通常我们通过
pylint--load plugins=pylint\u plugin
来实现这一点。
插件
需要在其全局范围内具有此函数,但在您的情况下,它可以为空。@PCManticore--再次感谢您的帮助。我已经更新了。