pylint, coroutines, decorators and type inferencing

拜拜、爱过 提交于 2019-12-12 10:07:04

问题


I'm working on a Google AppEngine project and I recently upgraded my pylint version to:

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)

This seems to have broken some type inferencing. Specifically, GAE's ndb uses a decorator and a generator function to return a "Future" object like this:

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

I might call it something like this:

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

Previously, I didn't have any problems with the linter here. Now I'm getting:

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)

I realize that I can # pylint: disable=no-member those lines individually but that would be cumbersome. I also realize that I can suppress that warning at the module level by adding the suppression code at the module level and I can globally suppress the warning by modifying my pylintrc file. I don't really want to do those things. I would much rather (somehow) tell pylint that things decorated with the @ndb.tasklet decorator return ndb.Future instances. I've seen that there are ways to register type-inferencing helpers1 for pylint, but I'm not sure how to make them work with my decorator of a generator function.

1Note that is a pretty old blog post... I think that logilab.astng is no longer in use and now you would use astroid instead, but that doesn't get me too much closer to the answer that I'm looking for...


回答1:


That blog post is definitely very old, things have changed for a while now.

You might take a look at the way how astroid's brain modules are implemented (https://github.com/PyCQA/astroid/tree/master/astroid/brain). They usually are AST transformers, which are applied to particular ASTs, providing modifications in order for pylint to understand what exactly is happening with your code.

A transform is usually a function, which receives a node and is supposed to return a new node or the same node modified (be warned though that in the future, we will remove support for modifying the same node, they will become immutable)

You can register one through

astroid.MANAGER.register_transform(type_of_node, transform_function)

but is usually okay to provide a filter to register_transform, so that it would be applied only to particular nodes you are interested in. The filter is the third argument of register_transform and it is a function that receives a node and should return a boolean, true if the node should be transformed, false otherwise. You can also this transform as an inference tip, that would be used instead of the normal inference mechanism, by wrapping the second argument in astroid.inference_tip(...). This is probably what you want, since you want to help pylint infer this function properly, rather than adding constructs to the AST itself. In this particular case, the transform could return an instance of ndb.Return, initialized with the yield points you have in your function. Also, note that you can build the AST from a string, with only the code representation, as in:

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

But if you want a more fine grained approach, you can build the AST yourself (crude example):

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

Also, note though that creating new nodes will change with the newest release, by using proper constructor methods for building them, instead of adding attributes manually.

Hope this helps.




回答2:


With the advice from PCManticore above, I've been able to hack this together:

"""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."""

I don't know if this is ideal or if there are any major drawbacks to this approach, but, assuming that you have your path set up properly -- e.g. the above script is (currently) at /path/to/pylint_helpers/ndb_brain.py and I have dev_appserver.py installed in /usr/local/google_appengine) and the following pylint-config file:

[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

It seems to silence the tasklet warnings (woohoo!). The basic idea is that I add an explicit inference tip to every function that is decorated with ndb.tasklet. The inference tip basically just tells pylint that the function returns an ndb.Future rather than behaving as a generator function. I think that since this is an inference tip rather than a re-write of the AST (by transforming the node), that it shouldn't have any other detrimental effects as far as pylint is concerned.



来源:https://stackoverflow.com/questions/38087760/pylint-coroutines-decorators-and-type-inferencing

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!