Patron d'interpréteur Python
Le patron Interpréteur est un patron de conception comportemental qui définit la grammaire d'un langage et établit un interpréteur pour interpréter les phrases dans ce langage. En termes simples, il ressemble à un petit compilateur capable d'analyser et d'exécuter des expressions avec des règles syntaxiques spécifiques.
Idée centrale
Imaginez un scénario de calculatrice : lorsque vous saisissez"2 + 3 * 4"pour une expression comme celle-ci, la calculatrice doit comprendre le sens de la chaîne et effectuer les calculs selon les règles des opérations mathématiques. Le motif Interpréteur est une solution de conception qui met en œuvre ce processus de « compréhension » et d'« exécution ».
Scénarios applicables
Le motif Interpréteur est particulièrement adapté aux situations suivantes :
- Lorsqu'il est nécessaire d'interpréter et d'exécuter un langage ou une expression
- Lorsque la grammaire est relativement simple et pas trop complexe
- Lorsque l'efficacité d'exécution n'est pas un critère essentiel
- Lorsque les règles de grammaire doivent être fréquemment étendues
Structure du patron
Comprenons les composants essentiels du motif Interpréteur à l'aide d'un diagramme de classes :

Description des composants
AbstractExpression
- Définit l'interface pour les opérations d'interprétation
- Contient généralement
interpret()method
TerminalExpression
- Implémente les opérations d'interprétation liées aux symboles terminaux de la grammaire
- Ne peut pas être décomposé en expressions plus petites
NonterminalExpression
- Implémente les opérations d'interprétation liées aux symboles non terminaux de la grammaire
- Contient généralement des références à d'autres expressions
Contexte
- Contient certaines informations globales nécessaires à l'interpréteur
- Stocke les résultats d'entrée et de sortie
Syntaxe de base et implémentation
Classe d'expression abstraite
Exemple
class Expression(ABC):
"""Classe d'expression abstraite, définit l'interface d'interprétation"""
@abstractmethod
def interpret(self, context):
"""Méthode d'interprétation, implémentée par les sous-classes concrètes"""
pass
Expression terminale
Exemple
"""Expression numérique - expression terminale"""
def __init__(self, number):
self.number = number
def interpret(self, context):
# Retourner directement la valeur numérique
return self.number
def __str__(self):
return f"Number({self.number})"
Expression non terminale
Exemple
"""Expression d'addition - expression non terminale"""
def __init__(self, left, right):
self.left = left # Expression gauche
self.right = right # Expression droite
def interpret(self, context):
# Interpréter les expressions gauche et droite respectivement, puis les additionner
return self.left.interpret(context) + self.right.interpret(context)
def __str__(self):
return f"({self.left} + {self.right})"
class SubtractExpression(Expression):
"""Expression de soustraction - expression non terminale"""
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):
"""Expression de multiplication - expression non terminale"""
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})"
Exemple complet : Calculatrice simple
Implémentons une calculatrice simple complète pour interpréter des expressions mathématiques :
Exemple
"""Contexte de calculatrice, stocke les informations relatives au calcul"""
def __init__(self):
self.variables = {} # Stocker les valeurs des variables
def set_variable(self, name, value):
"""Définir la valeur d'une variable"""
self.variables[name] = value
def get_variable(self, name):
"""Obtenir la valeur d'une variable"""
return self.variables.get(name, 0)
class VariableExpression(Expression):
"""Expression de variable - expression terminale"""
def __init__(self, name):
self.name = name
def interpret(self, context):
# Récupérer la valeur de la variable depuis le contexte
return context.get_variable(self.name)
def __str__(self):
return self.name
class Calculator:
"""Classe Calculator - construit et interprète des expressions"""
@staticmethod
def parse_expression(expression_str, context):
"""
Analyser des expressions de chaîne en un arbre d'expression
Simplifié ici ; les applications réelles peuvent nécessiter un analyseur plus complexe
"""
# Analyse simple d'expression (pour de vrais projets, il est recommandé d'utiliser une bibliothèque d'analyse dédiée)
tokens = expression_str.replace('(', ' ( ').replace(')', ' ) ').split()
return Calculator._parse_tokens(tokens)
@staticmethod
def _parse_tokens(tokens):
"""Analyser récursivement la liste des jetons"""
if not tokens:
return None
token = tokens.pop(0)
if token == '(':
# Commencer à analyser les expressions composées
left = Calculator._parse_tokens(tokens)
operator = tokens.pop(0)
right = Calculator._parse_tokens(tokens)
tokens.pop(0) # Supprimer ')'
if operator == '+':
return AddExpression(left, right)
elif operator == '-':
return SubtractExpression(left, right)
elif operator == '*':
return MultiplyExpression(left, right)
else:
# Analyser les expressions de base (nombres ou variables)
if token.isdigit() or (token[0] == '-' and token[1:].isdigit()):
return NumberExpression(int(token))
else:
return VariableExpression(token)
# Exemple d'utilisation
def main():
# Créer le contexte
context = CalculatorContext()
context.set_variable('x', 10)
context.set_variable('y', 5)
# Construire manuellement l'expression : (x + 3) * (y - 2)
expression = MultiplyExpression(
AddExpression(VariableExpression('x'), NumberExpression(3)),
SubtractExpression(VariableExpression('y'), NumberExpression(2))
)
print(f"Expression : {expression}")
result = expression.interpret(context)
print(f"Résultat : {result}") # Sortie : (10 + 3) * (5 - 2) = 39
# Utiliser l'analyseur
simple_expr = AddExpression(NumberExpression(5), NumberExpression(3))
print(f"Expression simple : {simple_expr} = {simple_expr.interpret(context)}")
if __name__ == "__main__":
main()
Application avancée : Interpréteur de condition SQL WHERE
Regardons un exemple plus pratique - implémenter un interpréteur simplifié de condition SQL WHERE :
Exemple
"""Contexte de requête SQL"""
def __init__(self, record):
self.record = record # Enregistrements de données
def get_value(self, field):
"""Obtenir la valeur du champ"""
return self.record.get(field)
class FieldExpression(Expression):
"""Expression de champ"""
def __init__(self, field_name):
self.field_name = field_name
def interpret(self, context):
return context.get_value(self.field_name)
class ConstantExpression(Expression):
"""Expression constante"""
def __init__(self, value):
self.value = value
def interpret(self, context):
return self.value
class EqualsExpression(Expression):
"""Expression de comparaison d'égalité"""
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):
"""Expression ET"""
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):
"""Expression OU"""
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)
# Exemple d'utilisation : simuler le filtrage des conditions WHERE SQL
def sql_demo():
# Simuler des enregistrements de données
records = [
{'name': 'Alice', 'age': 25, 'department': 'Ingénierie'},
{'name': 'Bob', 'age': 30, 'department': 'Sales'},
{'name': 'Charlie', 'age': 28, 'department': 'Ingénierie'},
{'name': 'Diana', 'age': 35, 'department': 'Marketing'}
]
# Construire la condition WHERE : (department = 'Ingénierie') 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)
# Construire l'arbre d'expression
condition = AndExpression(
EqualsExpression(FieldExpression('department'), ConstantExpression('Ingénierie')),
GreaterThanExpression(FieldExpression('age'), ConstantExpression(26))
)
print("Enregistrements qualifiés :")
for record in records:
context = SQLContext(record)
if condition.interpret(context):
print(f"- {record['name']}, {record['age']} ans, {record['department']}")
if __name__ == "__main__":
sql_demo()
Avantages et inconvénients du patron
Avantages
- Facile d'étendre la grammaire: L'ajout de nouvelles classes d'expression peut étendre les règles de grammaire
- Facile à implémenter: Chaque classe d'expression est relativement simple et facile à implémenter et à maintenir
- Grande flexibilité: Peut modifier dynamiquement l'approche d'interprétation
- Respecte le principe Open/Closed: Ouvert à l'extension, fermé à la modification
Inconvénients
- Problèmes de performance: Le patron Interpreter est généralement inefficace, surtout pour les grammaires complexes
- Complexité accrue: Pour les grammaires complexes, un grand nombre de classes sont générées, ce qui augmente la complexité du système
- Débogage difficile: Le débogage d'arbres d'expressions complexes peut être difficile
Bonnes pratiques et considérations
Quand l'utiliser
- Lorsque la grammaire est relativement simple et stable
- Lorsque l'efficacité d'exécution n'est pas la considération principale
- Lorsque de nouvelles règles de grammaire doivent être ajoutées fréquemment
Alternatives
Pour les grammaires complexes, envisagez les alternatives suivantes :
- Utilisez des générateurs d'analyseurs syntaxiques existants(tels que ANTLR, PLY)
- Utilisez des frameworks de compilateur/interpréteur
- Envisagez d'autres patrons de conception, tels que le patron Visiteur pour gérer les arbres de syntaxe abstraite
Conseils d'optimisation des performances
Exemple
"""Classe de base d'expression optimisée"""
def __init__(self):
self._cache = {} # Ajouter un mécanisme de mise en cache
def interpret(self, context):
# Utiliser le cache pour éviter les calculs répétés
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):
"""Logique d'interprétation réelle, implémentée par les sous-classes"""
pass
Exercices pratiques
Exercice 1 : Étendre la calculatrice
Ajoutez les fonctionnalités suivantes à la calculatrice :
- Opération de division
- Opération modulo
- Prise en charge de la priorité des parenthèses
Exercice 2 : Implémenter un interpréteur d'expressions booléennes
Créez un interpréteur d'expressions booléennes qui prend en charge :
- Opérations AND, OR, NOT
- Opérations de comparaison (>, <, >=, <=, ==, !=)
- Substitution de variables
Exercice 3 : Concevoir un moteur de règles
Utilisez le patron Interpréteur pour concevoir un moteur de règles simple qui peut :
- Analyser les règles métier
- Évaluer les règles en fonction des données d'entrée
- Produire les résultats de décision
Résumé
Le patron Interpréteur fournit une solution élégante pour gérer les langages dédiés (DSL). Bien que, dans les projets réels, pour les grammaires complexes, nous ayons tendance à utiliser des outils d'analyse syntaxique professionnels, comprendre les principes du patron Interpréteur est très important pour maîtriser les principes de compilation et le traitement du langage.
Rappelez-vous les idées essentielles des patrons de conception :Chaque patron ne convient pas à tous les scénarios ; choisir la solution qui convient le mieux aux besoins actuels est la clé。
Autres extensions