Mit VIsitor und Ast mehrere Textstellen filtern

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Moin moin,

der folgende Code durchsucht eine vorgegebene Textdatei nach dem Methodenaufruf "ipaddresses.update()" und gibt die dort angegebenen Parameter zurück.
Aus dem Textinhalt von
ipaddresses.update({'xas33315': u'10.81.3.182',
'xas33316': u'10.81.3.183',
'xas33317': u'10.81.3.181'})
macht der Code zum Beispiel: 10.81.3.182 10.81.3.183 10.81.3.181

Code: Alles auswählen

import ast

            tempFile = open("../test.txt").read()
            if "ipaddresses.update" in tempFile:
                ipAddressVisitor = mkFileVisitor.Visitor("ipaddresses", "update")
                ipAddressVisitor.visit(ast.parse(tempFile))
                
                ipAddresses = ipAddressVisitor.result
Und hier die Klasse Visitor:

Code: Alles auswählen

import ast

class Visitor(ast.NodeVisitor):

    def __init__(self, paramID, paramAttribute):
        ast.NodeVisitor.__init__(self)
        self.result = None
        self.paramID = paramID
        self.paramAttribute = paramAttribute

    def visit_Call(self, call):
        if not isinstance(call.func, ast.Attribute):
            return
        attribute = call.func
        if not isinstance(attribute.value, ast.Name):
            return
        name = attribute.value
        if name.id == self.paramID and attribute.attr == self.paramAttribute:
            self.result = ast.literal_eval(call.args[0])
Jetzt habe ich das Problem, dass in der Textdatei auch mehrere Aufrufe enthalten sein können. Zum Beispiel:
ipaddresses.update({'psxeflee30': u'10.82.3.180'})
ipaddresses.update({'psxeflee301: u'10.82.3.181'})

An dieser Stelle wird mir allerdings nur der letzte Aufruf zurück gegeben: 10.82.3.181

Die anderen Aufrufe werden missachtet. Ich habe nun erfolglos versucht eine Schleife um den Aufruf der Methode visit() zu legen, jedoch erfolglos.....

Weiß jemand sonst weiter?
Zuletzt geändert von Anonymous am Freitag 13. November 2015, 10:04, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@chpo7234: Du brauchst keine Schleife, der Visitor besucht doch jeden Aufruf, merkt sich halt bloss immer nur den letzten weil jede Fundstelle das Ergebnis der vorhergehenden überschreibt. Wenn Du alle haben willst darf die jeweils aktuelle Fundstelle nicht das Ergebnis der vorherigen überschreiben sondern Du musst die alle beispielweise in einer Liste sammeln. Also aus `result` eine Liste machen und aus der Zuweisung ein `append()` und schon hast Du am Ende alle Fundstellen.
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Recht herzlichen Dank!!

Bild
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Huhu,

ich habe noch eine Frage zu dem Thema..

Es kann auch vorkommen, dass der Methodenaufruf, dessen Parameter ich auslesen will, anders aussieht, bzw. mit einer weiteren Methode verknüpft ist:
extra_host_conf.setdefault('alias', []).extend(
[(u'host1', ['alias1']),
(u'host2', ['alias2']),
(u'host3', ['alias3'])])
Ich denke, dass sich die Parameter von conf.setdefault nicht ändern werden.

Wie kann ich meine Klasse Visitor aber aufrufen, so dass ich an die Parameter von extend komme?
Folgender Befehl (und ähnliche) klappt leider nicht..
ipAddressVisitor = mkFileVisitor.Visitor("extra_host_conf.setdefault", "extend")

MfG
BlackJack

@chpo7234: Du müsstest den Visitor halt entsprechend erweitern so das er auch das dort erkennt. An der Stelle würde ich aber wieder sagen das ist einfach das falsche Format Daten zu speichern. Ich weiss das die von einem Programm generiert werden was Du nicht unter Kontrolle hast, aber es bleibt halt trotzdem falsch, weil letztendlich sehr problematisch aus im Grunde beliebigem Quelltext Daten herauszuziehen die dort nicht einmal im Programmquelltext getrennt vom Code stehen sondern tatsächlich aus Methodenaufrufen herausgeprokelt werden müssen. Das ist ja auch kein spezifiziertes Format sondern kann sich mit einer neuen Programmversion auch komplett ändern.

Was genau ist das denn für eine Datei? Macht der Code irgend etwas was Dich daran hindern würde ihn tatsächlich auszuführen und danach dann die Daten abzufragen?
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Hey BlackJack,

danke für deine Antwort!

Das Programm, welches nicht von mir entwickelt wurde, wurde letztendlich auch in Python entwickelt. Ich möchte da aber aus vielerlei Gründe nicht drin herum wischen.

Zu der Datei:
sie gehört der Software CheckMK an. CheckMK ist eine Monitoring(Netzwerk-Überwachungs)-Software. Wenn man mit der Software neue Hosts hinzufügt oder ändert, so landen jede Eigenschaften dazu in der Datei "hosts.mk".
Es gibt auch Dateien für Kontaktgruppen, Hostgruppen, Zeitperioden etc. Alle haben die gleiche Eigenschaft, dass die zu verwaltenden Daten in diesem Python-Methodenaufruf-Stil gehalten werden.

Ich möchte die Software halt erweitern. Nach einem Netzwerk-Scan kann der Benutzer seine gewünschten Hosts durch einfaches Anklicken schnell hinzufügen oder wieder entfernen.

Mittlerweile funktioniert das Hinzufügen und Entfernen der Hosts auch. Jedoch verwende ich für manche zu ändernden Eigenschaften (wie hier zum Beispiel Host-Aliase) kein direktes parsen. Ich suche manuell nach Schlüsselwörtern(zB extra_host_conf.setdefault('alias', []).extend) und baue mir meinen benötigten "string" denn selber zusammen. Der Code fängt dabei irgendwie an zu müffeln und ich bin noch nicht ganz zufrieden. :)

Den Code kann ich nicht direkt ausführen. Die darin enthaltenden Variablen finden in irgendeiner anderen Datei Bezug, so erhalte ich zählige "ist nicht definiert"-Meldungen.
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Moin moin,

ich bin es noch mal...

Bis jetzt bin ich leider noch nicht weiter gekommen, mit dem Abändern der Visitor-Klasse.

Ich möchte ja den Parameter von dem Methodenaufruf extend() erhalten.
Beispiel des Aufrufs:
extra_host_conf.setdefault('alias', []).extend(
[(u'host1', ['alias1']),
(u'host2', ['alias2']),
(u'host3', ['alias3'])])
Ich habe ungefähr eine Vorstellung davon, wo ich etwas ändern muss. Ich weiß nur nicht wie.

Code: Alles auswählen

import ast
 
class Visitor(ast.NodeVisitor):
 
    def __init__(self, paramID, paramAttribute): #hier neuen Übergabe-Parameter ergänzen
        ast.NodeVisitor.__init__(self)
        self.result = None
        self.paramID = paramID
        self.paramAttribute = paramAttribute
        # hier neue Instanzvariable anlegen
 
    def visit_Call(self, call):
        if not isinstance(call.func, ast.Attribute):
            return
        attribute = call.func
        if not isinstance(attribute.value, ast.Name):
            return
        name = attribute.value
        # Zusätzliche Bedingung prüfen:
        if name.id == self.paramID and attribute.attr == self.paramAttribute:
            self.result = ast.literal_eval(call.args[0])
Ich weiß allerdings nicht, wie ich in der letzten if-Abfrage nun die entsprechende Bedingung setze.

Mein bisheriges Verständnis:
name.id wird wohl das Objekt sein, auf dass die Methode aufgerufen wird. Zum Beispiel also "Auto".
attribute.attr ist dann der Methodenaufruf an sich. Beispielsweise .Fahre()
ast.literal_eval(call.args[0]) liefert mir den Parameter aus der Methode zurück: .Fahre(5) -> 5

Was aber tun, wenn ein weiterer Methodenaufruf erfolgt und man nur den zweiten Parameter haben möchte? "Auto.Repariere("Aussenspiegel").Fahre(5)"

Ich kann mir auch leider nicht die Werte von name.id, attribute.attr korrekt anzeigen lassen...
BlackJack

@chpo7234: Du müsstest Dich mal näher mit dem `ast`-Modul beschäftigen und dem Baum der da aufgebaut wird. Am besten interaktiv in einer Python-Shell mal den Code der so aussieht wie Du ihn erkennen willst untersuchen. Wenn es über mehrere Knoten im Baum geht würde ich vielleicht sogar den Umweg über XML gehen, also erst den AST in geeignetes XML überführen und dann mit XPath die entsprechende Stelle suchen.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Hier eine etwas ausführlichere Doku zum ast Modul: https://greentreesnakes.readthedocs.org ... index.html
In specifications, Murphy's Law supersedes Ohm's.
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Moin moin,

ich bin es noch mal. Danke für eure Tipps. Leider stehe ich gerade auf der Stelle. Ich versuche den Parameter des folgenden extends()-Methodenaufruf auszulesen:
# Settings for alias
extra_host_conf.setdefault('alias', []).extend(
[(u'alias1', ['host1']),
(u'alias2', ['host2']),
(u'alias3', ['host3'])])
Ich habe einige Methoden erstellt, um mir erst mal ausgeben zu lassen, welche Teile welche Typen sind:

Code: Alles auswählen

import ast

class MyVisitor(ast.NodeVisitor):
    def generic_visit(self, node):
        print 'Type: ', type(node).__name__
        ast.NodeVisitor.generic_visit(self, node)

    def visit_Load(self, node): pass

    def visit_Name(self, node):
        print 'Name: ', node.id
        self.generic_visit(node)

    def visit_Str(self, node):
        print 'String: ', node.s
        self.generic_visit(node)

    def visit_List(self, node):
        print "List: ", node.elts
        self.generic_visit(node)

    def visit_Tuple(self, node):
        print "Tuple: ", node.elts
        self.generic_visit(node)

    def visit_NameConstant(self, node):
        print "NameConstant: ", node.value
        self.generic_visit(node)

    def visit_Expr(self, node):
        print "Expression: ", node.value
        self.generic_visit(node)

    # def visit_BinOp(self, node):
    #     print "Binary Operation: ", node.op
    #     self.generic_visit(node)

    def visit_Call(self, node):
        #print "Call-Arguments: ", node.args
        print "ast.literal: ", ast.literal_eval(node.args[0])
        self.generic_visit(node)

    def visit_Attribute(self, node):
        #print "Attribute-Value: ", node.value
        print "Attribute: ", node.attr
        self.generic_visit(node)

def main():
    x = MyVisitor()
    node = ast.parse(open("/scan/python/test").read())
    node = ast.parse(node)
    x.visit(node)

if __name__ == '__main__':
    main()
Damit erhalte ich für das Eingangsbeispiel (Text) folgende Ausgabe:
Type: Module
Expression: <_ast.Call object at 0x7ff27c934fd0>
Type: Expr
ast.literal: [(u'alias1', ['host1']), (u'alias2', ['host2']), (u'alias3', ['host3'])]
Type: Call
Attribute: extend
Type: Attribute
ast.literal: alias
Type: Call
Attribute: setdefault
Type: Attribute
Name: extra_host_conf
Type: Name
String: alias
Type: Str
List: []
Type: List
List: [<_ast.Tuple object at 0x7ff27c8d4b10>, <_ast.Tuple object at 0x7ff27c8d4c10>, <_ast.Tuple object at 0x7ff27c8d4d10>]
Type: List
Tuple: [<_ast.Str object at 0x7ff27c8d4b50>, <_ast.List object at 0x7ff27c8d4b90>]
Type: Tuple
String: alias1
Type: Str
List: [<_ast.Str object at 0x7ff27c8d4bd0>]
Type: List
String: host1
Type: Str
Tuple: [<_ast.Str object at 0x7ff27c8d4c50>, <_ast.List object at 0x7ff27c8d4c90>]
Type: Tuple
String: alias2
Type: Str
List: [<_ast.Str object at 0x7ff27c8d4cd0>]
Type: List
String: host2
Type: Str
Tuple: [<_ast.Str object at 0x7ff27c8d4d50>, <_ast.List object at 0x7ff27c8d4d90>]
Type: Tuple
String: alias3
Type: Str
List: [<_ast.Str object at 0x7ff27c8d4dd0>]
Type: List
String: host3
Type: Str
Dabei ist für mich folgendes relevant:
ast.literal: [(u'alias1', ['host1']), (u'alias2', ['host2']), (u'alias3', ['host3'])]
Type: Call

Attribute: extend
Type: Attribute

ast.literal: alias
Type: Call

Attribute: setdefault
Type: Attribute

Name: extra_host_conf
Type: Name

String: alias
Type: Str
Anhand des Namens "extra_host_conf" könnte ich später in dessen ast-Literal springen. Nun weiß ich aber nicht, wie ich das ast-Literal dort aufrufen kann. Der Compiler sagt mir, dass das Namensobjekt kein Attribut 'args' hat.


Code: Alles auswählen

import ast

class MyVisitor(ast.NodeVisitor):

    def visit_Name(self, node):
        if not isinstance(node.args, ast.Name): return

        if node.id == "extra_host_conf":
            print ast.literal_eval(node.args[0])
        self.generic_visit(node)

def main():
    x = MyVisitor()
    node = ast.parse(open("/scan/python/test").read())
    node = ast.parse(node)
    x.visit(node)

if __name__ == '__main__':
    main()
Nun gut, die Alternative wäre die Methode an Hand des Attributes "setdefault" abzufangen:

Code: Alles auswählen

import ast

class MyVisitor(ast.NodeVisitor):
    def visit_Call(self, node):
        #print "Call-Arguments: ", node.args
        #if not isinstance(node.func, ast.Name): return
        if node.func.attr == "setdefault":
            print "Attr vorhanden"
            print ast.literal_eval(node.args[0])
        self.generic_visit(node)
#
#     def visit_Attribute(self, node):
#         print "Attribute-Value: ", node.value
#         print "Attribute: ", node.attr
#         self.generic_visit(node)

def main():
    x = MyVisitor()
    node = ast.parse(open("/scan/python/test").read())
    node = ast.parse(node)
    x.visit(node)

if __name__ == '__main__':
    main()
Ergebnis: "Attr vorhanden", "alias". Nun wird mir zwar der erste Parameter des Eingangsbeispiels ausgegeben, aber leider nicht der Zweite. Wenn ich den Index von ast.literal_eval(node.args[0]) auf 1 setze, denn erhalte ich eine leere Liste: []
chpo7234
User
Beiträge: 35
Registriert: Dienstag 29. September 2015, 10:19

Noch mal eine Rückmeldung.. Ich kann den letzten Beitrag leider nicht editieren..
# Settings for alias
extra_host_conf.setdefault('alias', []).extend(
[(u'alias1', ['host1']),
(u'alias2', ['host2']),
(u'alias3', ['host3'])])
Hiermit kann ich mir zumindest den Parameter vom zweiten Methodenaufruf ausgeben lassen:

Code: Alles auswählen

def visit_Call(self, node):
        if node.func.attr == "extend":
            print ast.literal_eval(node.args[0])
        self.generic_visit(node)
Ausgabe:
(u'alias1', ['host1']),
(u'alias2', ['host2']),
(u'alias3', ['host3'])
Bis hier hin prima!

Ich muss nur halt zusätzlich den Parameter aus dem ersten Methodenaufruf auf "alias" überprüfen, da dieser sich ändern kann...

Beides in Einem habe ich so noch nicht hinbekommen..
Antworten