2016-06-28 31 views
2

我工作的谷歌的AppEngine上的项目,我最近升级我的pylint的版本: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 ,但我不知道如何使它们与我的装饰器的发电机功能一起工作。

请注意,是一个很老的博客帖子...我认为,logilab.astng是不再使用,现在你可以使用astroid代替,但是,这并不让我太更接近我正在寻找的答案...

回答

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,它是一个接收节点的函数,并且应该返回一个布尔值,如果是th,则返回true e节点应该被转换,否则为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 

而且,不过,请注意创造新的节点将与最新版本的变化,通过建立他们适当的构造方法,而不是手动添加属性。

希望这会有所帮助。

+0

感谢您的回答。我在这里和那里的闲暇时刻仍然消化它。是否有任何地方的实例库(可能在pylint代码库?),我可以看看这些东西是如何完成的? – mgilson

+0

不幸的是,你可以找到一些结构化例子的最好的地方仍然是所谓的astroid大脑(https://github.com/PyCQA/astroid/tree/master/astroid/brain),并且通常是通过astroid的代码库。您可能会在#pylint-dev(freenode)上获得一些结果 – PCManticore

+0

太棒了。感谢这些例子。我_思考_我能够破解一起可行的事情(在下面单独发布答案)。如果你看到任何看起来很腥的东西,请随时告诉我。如果这一切看起来合理,我可能会继续努力让更多的'ndb'和'pylint'开心,并为其他OSS社区创建单独的回购... – mgilson

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,我有安装在/usr/local/google_appenginedev_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 

它似乎沉默微进程警告(哇噢!)。基本思想是,我为每个用ndb.tasklet装饰的功能添加一个明确的推理提示。推断提示基本上只是告诉pylint该函数返回ndb.Future而不是作为生成器函数。 I 认为,因为这是一个推断技巧,而不是AST的重写(通过转换节点),因此就pylint而言,它不应该有任何其他有害影响。

+0

这看起来非常棒!不要忘记将上下文传递给igetattr调用,''igetattr('tasklet_function',context = context)''。在大多数情况下,上下文可以改进推理,因为它通过遍历推理路径剔除本地上下文。不过,我不明白为什么你必须在init-hook内部传递ndb_brain。通常我们通过''pylint --load-plugins = pylint_plugin''来做到这一点。一个'plugin'需要在其全局范围内有这个函数,https://github.com/PyCQA/pylint/blob/master/pylint/extensions/bad_builtin.py#L61,但在你的情况下它可以是空的。 – PCManticore

+0

@PCManticore - 再次感谢您的帮助。我已更新。 – mgilson