Python-Interpretermuster

Das Interpreter-Muster ist ein Verhaltensentwurfsmuster, das die Grammatik einer Sprache definiert und einen Interpreter einrichtet, um Sätze in dieser Sprache zu interpretieren. Einfach gesagt, es ist wie ein kleiner Compiler, der Ausdrücke mit bestimmten syntaktischen Regeln analysieren und ausführen kann.

Kernidee

Stellen Sie sich ein Taschenrechner-Szenario vor: Wenn Sie eingeben"2 + 3 * 4"Bei einem Ausdruck wie diesem muss der Taschenrechner die Bedeutung der Zeichenkette verstehen und Berechnungen nach mathematischen Operationsregeln durchführen. Das Interpreter-Muster ist eine Entwurfslösung, die diesen Prozess des "Verstehens" und "Ausführens" implementiert.

Geeignete Szenarien

Das Interpreter-Muster eignet sich besonders für die folgenden Situationen:

  • Wenn eine Sprache oder ein Ausdruck interpretiert und ausgeführt werden muss
  • Wenn die Grammatik relativ einfach und nicht übermäßig komplex ist
  • Wenn die Ausführungseffizienz kein entscheidender Gesichtspunkt ist
  • Wenn Grammatikregeln häufig erweitert werden müssen

Musterstruktur

Lernen wir die Kernkomponenten des Interpreter-Musters anhand eines Klassendiagramms kennen:

Beschreibung der Komponenten

AbstractExpression

  • Definiert die Schnittstelle für Interpretationsoperationen
  • Enthält normalerweiseinterpret()method

TerminalExpression

  • Implementiert die Interpretationsoperationen für Terminalsymbole in der Grammatik
  • Kann nicht in kleinere Ausdrücke zerlegt werden

NonterminalExpression

  • Implementiert die Interpretationsoperationen für Nichtterminalsymbole in der Grammatik
  • Enthält normalerweise Verweise auf andere Ausdrücke

Kontext

  • Enthält einige globale Informationen, die der Interpreter benötigt
  • Speichert Eingabe- und Ausgabeergebnisse

Grundlegende Syntax und Implementierung

Abstrakte Ausdrucksklasse

Beispiel

from abc import ABC, abstractmethod

class Expression(ABC):
    """Abstrakte Ausdrucksklasse, definiert die Interpretationsschnittstelle"""
   
    @abstractmethod
    def interpret(self, context):
        """Interpretationsmethode, implementiert von konkreten Unterklassen"""
        pass

Terminaler Ausdruck

Beispiel

class NumberExpression(Expression):
    """Zahlenausdruck - Terminalausdruck"""
   
    def __init__(self, number):
        self.number = number
   
    def interpret(self, context):
        # Numerischen Wert direkt zurückgeben
        return self.number
   
    def __str__(self):
        return f"Number({self.number})"

Nichtterminaler Ausdruck

Beispiel

class AddExpression(Expression):
    """Additionsausdruck - Nichtterminalausdruck"""
   
    def __init__(self, left, right):
        self.left = left    # Linker Ausdruck
        self.right = right  # Rechter Ausdruck
   
    def interpret(self, context):
        # Interpretiere den linken und den rechten Ausdruck jeweils und addiere sie dann.
        return self.left.interpret(context) + self.right.interpret(context)
   
    def __str__(self):
        return f"({self.left} + {self.right})"

class SubtractExpression(Expression):
    """Subtraktionsausdruck - Nichtterminal-Ausdruck"""
   
    def __init__(self, left, right):
        self.left = left
        self.right = right
   
    def interpret(self, context):
        return self.left.interpret(context) - self.right.interpret(context)
   
    def __str__(self):
        return f"({self.left} - {self.right})"

class MultiplyExpression(Expression):
    """Multiplikationsausdruck - Nichtterminal-Ausdruck"""
   
    def __init__(self, left, right):
        self.left = left
        self.right = right
   
    def interpret(self, context):
        return self.left.interpret(context) * self.right.interpret(context)
   
    def __str__(self):
        return f"({self.left} * {self.right})"

Vollständiges Beispiel: Einfacher Taschenrechner

Lass uns einen vollständigen einfachen Taschenrechner implementieren, um mathematische Ausdrücke zu interpretieren:

Beispiel

class CalculatorContext:
    """Taschenrechner-Kontext, speichert berechnungsbezogene Informationen"""
   
    def __init__(self):
        self.variables = {}  # Variablenwerte speichern
   
    def set_variable(self, name, value):
        """Variablenwert setzen"""
        self.variables[name] = value
   
    def get_variable(self, name):
        """Variablenwert abrufen"""
        return self.variables.get(name, 0)

class VariableExpression(Expression):
    """Variablenausdruck - Terminalausdruck"""
   
    def __init__(self, name):
        self.name = name
   
    def interpret(self, context):
        # Variablenwert aus dem Kontext abrufen
        return context.get_variable(self.name)
   
    def __str__(self):
        return self.name

class Calculator:
    """Calculator-Klasse - erstellt und interpretiert Ausdrücke"""
   
    @staticmethod
    def parse_expression(expression_str, context):
        """
Zeichenkettenausdrücke in einen Ausdrucksbaum parsen
Hier vereinfacht; tatsächliche Anwendungen erfordern möglicherweise einen komplexeren Parser
        """

        # Einfaches Parsen von Ausdrücken (für echte Projekte wird die Verwendung einer dedizierten Parsing-Bibliothek empfohlen)
        tokens = expression_str.replace('(', ' ( ').replace(')', ' ) ').split()
        return Calculator._parse_tokens(tokens)
   
    @staticmethod
    def _parse_tokens(tokens):
        """Token-Liste rekursiv parsen"""
        if not tokens:
            return None
           
        token = tokens.pop(0)
       
        if token == '(':
            # Beginne mit dem Parsen zusammengesetzter Ausdrücke
            left = Calculator._parse_tokens(tokens)
            operator = tokens.pop(0)
            right = Calculator._parse_tokens(tokens)
            tokens.pop(0)  # ')' entfernen
           
            if operator == '+':
                return AddExpression(left, right)
            elif operator == '-':
                return SubtractExpression(left, right)
            elif operator == '*':
                return MultiplyExpression(left, right)
        else:
            # Grundlegende Ausdrücke parsen (Zahlen oder Variablen)
            if token.isdigit() or (token[0] == '-' and token[1:].isdigit()):
                return NumberExpression(int(token))
            else:
                return VariableExpression(token)

# Verwendungsbeispiel
def main():
    # Kontext erstellen
    context = CalculatorContext()
    context.set_variable('x', 10)
    context.set_variable('y', 5)
   
    # Ausdruck manuell erstellen: (x + 3) * (y - 2)
    expression = MultiplyExpression(
        AddExpression(VariableExpression('x'), NumberExpression(3)),
        SubtractExpression(VariableExpression('y'), NumberExpression(2))
    )
   
    print(f"Ausdruck: {expression}")
    result = expression.interpret(context)
    print(f"Ergebnis: {result}")  # Ausgabe: (10 + 3) * (5 - 2) = 39
   
    # Verwende den Parser
    simple_expr = AddExpression(NumberExpression(5), NumberExpression(3))
    print(f"Einfacher Ausdruck: {simple_expr} = {simple_expr.interpret(context)}")

if __name__ == "__main__":
    main()

Fortgeschrittene Anwendung: SQL-WHERE-Bedingungsinterpreter

Betrachten wir ein praktischeres Beispiel – die Implementierung eines vereinfachten SQL-WHERE-Bedingungsinterpreters:

Beispiel

class SQLContext:
    """SQL-Abfragekontext"""
   
    def __init__(self, record):
        self.record = record  # Datensätze
   
    def get_value(self, field):
        """Feldwert abrufen"""
        return self.record.get(field)

class FieldExpression(Expression):
    """Feldausdruck"""
   
    def __init__(self, field_name):
        self.field_name = field_name
   
    def interpret(self, context):
        return context.get_value(self.field_name)

class ConstantExpression(Expression):
    """Konstanter Ausdruck"""
   
    def __init__(self, value):
        self.value = value
   
    def interpret(self, context):
        return self.value

class EqualsExpression(Expression):
    """Gleichheitsvergleichsausdruck"""
   
    def __init__(self, left, right):
        self.left = left
        self.right = right
   
    def interpret(self, context):
        return self.left.interpret(context) == self.right.interpret(context)

class AndExpression(Expression):
    """UND-Ausdruck"""
   
    def __init__(self, left, right):
        self.left = left
        self.right = right
   
    def interpret(self, context):
        return self.left.interpret(context) and self.right.interpret(context)

class OrExpression(Expression):
    """ODER-Ausdruck"""
   
    def __init__(self, left, right):
        self.left = left
        self.right = right
   
    def interpret(self, context):
        return self.left.interpret(context) or self.right.interpret(context)

# Anwendungsbeispiel: SQL-WHERE-Bedingungsfilterung simulieren
def sql_demo():
    # Datensätze simulieren
    records = [
        {'name': 'Alice', 'age': 25, 'department': 'Engineering'},
        {'name': 'Bob', 'age': 30, 'department': 'Sales'},
        {'name': 'Charlie', 'age': 28, 'department': 'Engineering'},
        {'name': 'Diana', 'age': 35, 'department': 'Marketing'}
    ]
   
    # WHERE-Bedingung erstellen: (department = 'Engineering') AND (age > 26)
    class GreaterThanExpression(Expression):
        def __init__(self, left, right):
            self.left = left
            self.right = right
       
        def interpret(self, context):
            return self.left.interpret(context) > self.right.interpret(context)
   
    # Ausdrucksbaum erstellen
    condition = AndExpression(
        EqualsExpression(FieldExpression('department'), ConstantExpression('Engineering')),
        GreaterThanExpression(FieldExpression('age'), ConstantExpression(26))
    )
   
    print("Qualifizierte Datensätze:")
    for record in records:
        context = SQLContext(record)
        if condition.interpret(context):
            print(f"- {record['name']}, {record['age']} Jahre alt, {record['department']}")

if __name__ == "__main__":
    sql_demo()

Vor- und Nachteile des Musters

Vorteile

  1. Einfache Erweiterung der Grammatik: Das Hinzufügen neuer Ausdrucksklassen kann die Grammatikregeln erweitern
  2. Einfach zu implementieren: Jede Ausdrucksklasse ist relativ einfach und leicht zu implementieren und zu warten
  3. Hohe Flexibilität: Der Interpretationsansatz kann dynamisch geändert werden
  4. Folgt dem Open/Closed-Prinzip: Offen für Erweiterung, geschlossen für Modifikation

Nachteile

  1. Performance-Probleme: Das Interpreter-Muster ist normalerweise ineffizient, insbesondere bei komplexen Grammatiken
  2. Erhöhte Komplexität: Bei komplexen Grammatiken wird eine große Anzahl von Klassen erzeugt, was die Systemkomplexität erhöht
  3. Schwieriges Debugging: Das Debugging komplexer Ausdrucksbäume kann schwierig sein

Best Practices und Überlegungen

Wann verwenden

  • Wenn die Grammatik relativ einfach und stabil ist
  • Wenn die Ausführungseffizienz nicht die vorrangige Überlegung ist
  • Wenn häufig neue Grammatikregeln hinzugefügt werden müssen

Alternativen

Betrachten Sie für komplexe Grammatiken die folgenden Alternativen:

  • Bestehende Parser-Generatoren verwenden(wie z. B. ANTLR, PLY)
  • Compiler/Interpreter-Frameworks verwenden
  • Betrachten Sie andere Entwurfsmuster, wie z. B. das Visitor-Muster zur Behandlung abstrakter Syntaxbäume

Tipps zur Leistungsoptimierung

Beispiel

class OptimizedExpression(Expression):
    """Optimierte Basisklasse für Ausdrücke"""
   
    def __init__(self):
        self._cache = {}  # Caching-Mechanismus hinzufügen
   
    def interpret(self, context):
        # Cache verwenden, um wiederholte Berechnungen zu vermeiden
        cache_key = id(context)
        if cache_key not in self._cache:
            self._cache[cache_key] = self._do_interpret(context)
        return self._cache[cache_key]
   
    def _do_interpret(self, context):
        """Tatsächliche Interpretationslogik, implementiert von Unterklassen"""
        pass

Übungsaufgaben

Übung 1: Den Taschenrechner erweitern

Fügen Sie die folgende Funktionalität zum Taschenrechner hinzu:

  • Divisionsoperation
  • Modulo-Operation
  • Unterstützung der Klammerpriorität

Übung 2: Einen booleschen Ausdrucksinterpreter implementieren

Erstellen Sie einen Interpreter für boolesche Ausdrücke, der Folgendes unterstützt:

  • AND-, OR-, NOT-Operationen
  • Vergleichsoperationen (>, <, >=, <=, ==, !=)
  • Variablensubstitution

Übung 3: Eine Regel-Engine entwerfen

Verwenden Sie das Interpreter-Muster, um eine einfache Regel-Engine zu entwerfen, die Folgendes kann:

  • Geschäftsregeln parsen
  • Regeln anhand von Eingabedaten auswerten
  • Entscheidungsergebnisse ausgeben

Zusammenfassung

Das Interpreter-Muster bietet eine elegante Lösung für den Umgang mit domänenspezifischen Sprachen (DSL). Obwohl wir in tatsächlichen Projekten für komplexe Grammatiken professionelle Parsing-Tools verwenden, ist das Verständnis der Prinzipien des Interpreter-Musters von großer Bedeutung für die Beherrschung von Kompilierungsprinzipien und Sprachverarbeitung.

Erinnern Sie sich an die Kernideen von Entwurfsmustern:Nicht jedes Muster passt zu jedem Szenario; die Wahl der Lösung, die am besten zu den aktuellen Anforderungen passt, ist der Schlüssel。

Weitere Erweiterungen