2016-06-28 11 views
2

Ich bin auf einem Google App Engine-Projekt arbeiten, und ich meine Pylint Version vor kurzem aufgerüstet:Pylint, Koroutinen, Dekorateure und die Typenanalyse

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) 

Dies scheint eine Art Inferenz gebrochen zu haben. Insbesondere GAE ndb uses a decorator and a generator function to return a "Future" object wie folgt aus:

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

Ich könnte es so etwas wie dieses nennen:

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

Bisher habe ich keine Probleme hier mit dem Linter haben. Jetzt erhalte ich:

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) 

Ich weiß, dass ich diese Zeilen einzeln # pylint: disable=no-member kann aber das wäre umständlich. Ich erkenne auch, dass ich diese Warnung auf der Modulebene unterdrücken kann, indem ich den Unterdrückungscode auf Modulebene hinzufüge, und ich kann die Warnung global unterdrücken, indem ich meine pylintrc-Datei modifiziere. Ich will diese Dinge nicht wirklich machen. Ich würde viel lieber (irgendwie) pylint sagen, dass die mit dem @ndb.tasklet Dekorator dekorierten Objekte ndb.Future Instanzen zurückgeben. Ich habe gesehen, dass es ways to register type-inferencing helpers für pylint gibt, aber ich bin nicht sicher, wie man sie mit meinem Dekorateur einer Generatorfunktion arbeiten lässt.

Beachten Sie, dass eine ziemlich alte Blog-Post ist ... Ich denke, dass logilab.astng nicht mehr in Gebrauch ist, und jetzt würden Sie astroid stattdessen verwenden, aber das macht mich nicht zu viel näher an die Antwort, die ich suche ...

Antwort

1

Dieser Blogbeitrag ist definitiv sehr alt, Sachen haben sich für eine Weile jetzt geändert.

Sie können sich ansehen, wie die Gehirnmodule von Astroid implementiert werden (https://github.com/PyCQA/astroid/tree/master/astroid/brain). In der Regel handelt es sich um AST-Transformatoren, die auf bestimmte ASTs angewendet werden. Dabei werden Änderungen vorgenommen, damit pylint erkennt, was genau mit Ihrem Code passiert.

Eine Transformation ist normalerweise eine Funktion, die einen Knoten empfängt und einen neuen Knoten oder denselben modifizierten Knoten zurückgeben soll (seien Sie gewarnt, dass wir in Zukunft die Unterstützung für die Änderung desselben Knotens entfernen werden unveränderlich)

Sie ein durch

astroid.MANAGER.register_transform(type_of_node, transform_function) 

registrieren kann, ist aber in der Regel in Ordnung, einen Filter bereitzustellen, um register_transform, so dass sie nur auf bestimmte Knoten angewendet werden, würden Sie interessiert sind. der Filter ist das dritte Argument von register_transform und es ist eine Funktion, die einen Knoten empfängt und einen booleschen Wert zurückgeben soll, true wenn th Der Knoten sollte transformiert werden, ansonsten falsch. Sie können diese Transformation auch als Inferenz-Tipp verwenden, der anstelle des normalen Inferenzmechanismus verwendet wird, indem Sie das zweite Argument in astroid.inference_tip(...) einschließen. Dies ist wahrscheinlich, was Sie wollen, da Sie helfen wollen, pylint diese Funktion richtig abzuleiten, anstatt Konstrukte zum AST selbst hinzuzufügen. In diesem speziellen Fall könnte die Transformation eine Instanz von ndb.Return zurückgeben, die mit den in Ihrer Funktion vorhandenen Fließpunkten initialisiert wurde.Beachten Sie auch, dass Sie den AST aus einem String bauen, nur mit der Code-Darstellung, wie in:

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

Aber wenn man einen feinkörnigen Ansatz will, können Sie das AST selbst (grobes Beispiel) bauen:

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 

Beachten sie auch, aber, dass die Schaffung neuer Knoten mit der neuesten Version ändern wird, indem für den Aufbau von ihnen richtige Konstruktor Methoden verwenden, anstelle der Zugabe Attribute manuell.

Hoffe, das hilft.

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

Ich weiß nicht, ob dies ideal ist, oder wenn es zu diesem Ansatz keine größeren Nachteile sind, aber:

+0

Danke für diese Antwort. Ich verdaue es immer noch in den wenigen Momenten, die ich hier und da habe. Gibt es irgendwo ein Repository von Beispielen (vielleicht in der pylint Codebase?), Wo ich einen Blick darauf werfen kann, wie diese Dinge gemacht werden? – mgilson

+0

Leider ist der beste Ort, wo Sie einige strukturierte Beispiele finden können, immer noch das sogenannte Gehirn von Astroid (https://github.com/PyCQA/astroid/tree/master/astroid/brain) und generell durch die Astrobasiscodebasis. Sie könnten auch einige Ergebnisse auf # pylint-dev (freenode) – PCManticore

+0

Awesome erhalten. Danke für diese Beispiele. Ich denke, dass ich in der Lage war, etwas zu bearbeiten, das zusammen funktioniert (als separate Antwort unten veröffentlicht). Wenn Sie etwas sehen, das dort fischig aussieht, wenden Sie sich bitte an mich. Wenn alles vernünftig aussieht, werde ich vielleicht weiter versuchen, mehr von 'ndb' und' pylint' zu machen und ein separates Repo für den Rest der OSS-Community erstellen ... – mgilson

0

Mit der Beratung von PCManticore oben, ich habe dies zu hacken zusammen in der Lage vorausgesetzt, Sie haben Ihren Pfad richtig eingerichtet - z das obige Skript ist (derzeit) bei /path/to/pylint_helpers/ndb_brain.py und ich habe dev_appserver.py in /usr/local/google_appengine installiert ist) und die folgende Pylint-config-Datei:

[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 

Es scheint, die Tasklet Warnungen zum Schweigen zu bringen (woohoo!). Die Grundidee ist, dass ich jeder Funktion, die mit ndb.tasklet dekoriert ist, eine explizite Inferenz hinzufügen. Die Inferenzspitze sagt im Grunde nur pylint, dass die Funktion eine ndb.Future zurückgibt, anstatt sich als eine Generatorfunktion zu verhalten. I denke,, dass, da dies eine Inferenz Tipp ist, anstatt eine Neuschreibung des AST (durch die Transformation des Knotens), dass es keine anderen schädlichen Auswirkungen haben sollte, soweit Pyylint betroffen ist.

+0

Das sieht ziemlich gut aus! Vergessen Sie nicht, den Kontext an den igetattr-Aufruf zu übergeben, '' igetattr ('tasklet_function', context = context) ''. Der Kontext kann die Inferenz in den meisten Fällen verbessern, da er den lokalen Kontext mit dem durchlaufenen Inferenzpfad unterstützt. Ich verstehe jedoch nicht, warum musst du ndb_brain innerhalb von init-hook übergeben. Normalerweise machen wir das über '' pylint --load-plugins = pylt_plugin''. Ein 'plugin' muss diese Funktion in seinem globalen Geltungsbereich https://github.com/PyCQA/pylint/blob/master/pylint/extensions/bad_builtin.py#L61 haben, aber in Ihrem Fall kann es leer sein. – PCManticore

+0

@PCManticore - Danke nochmal für die Hilfe hier. Ich habe aktualisiert. – mgilson