问题
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