Entonces, he creado un motor de reglas en python, pero soy bastante nuevo en python... mi motor es bastante agradable de usar, pero agregar una nueva regla es bastante feo, y me pregunto si hay un manera de limpiarlo.
La clave para recordar es que las reglas tienen efectos secundarios, las reglas se pueden combinar con ands, ors, etc., y solo aplica los efectos secundarios si la regla completa tuvo éxito, es decir, la verificación si la regla tuvo éxito no se puede combinar con realizando el efecto secundario.
Entonces, cada regla termina luciendo algo como esto:
def sample_rule(): def check( item ): if item.doesnt_pass_some_condition(): return None def action_to_perform(): item.set_some_value() item.set_some_other_value() return action_to_perform return Rule(check)lo que parece terriblemente feo, pero parece que no puedes hacer lambdas multilínea o lambas de línea cero ... Supongo que estoy buscando algo como:
def sample_rule(): return Rule( lambda x: x.passes_condition(), lambda x: {x.set_some_value(), x.set_some_other_value)}pero tanto la condición como el efecto secundario pueden ser varias líneas, y el efecto secundario suele estar vacío.
Entonces, ¿hay un patrón más simple que pueda aplicar que se aplique a todos los casos? (Realmente no quiero usar el patrón anterior cuando tengo exactamente una línea de condición y una línea de efecto secundario, y un patrón completamente diferente en los otros casos)
solo por interés, al final terminas con algo como
rule1 = sample_rule().andalso( other_rule_1().or(other_rule_2)).butnot( other_rule_3) ... ... for thing_to_check in lots_of_things: for rule in lots_of_rules: if rule.apply_to( thing_to_check): break # take the first rule that appliesEn lugar de definir lambdas de varias líneas (que Python no permite), puede definir varias lambdas en una lista y luego usar todas las lambdas en la lista según sea necesario:
class Rule: def __init__(self, checks=None, actions=None): self.checks = checks if checks else [] self.actions = actions if actions else [] def apply_to(self, item): if all([check(item) for check in self.checks]): return self.actions else: return None sample_rule = Rule(checks=[lambda x: x.passes_condition()], actions=[lambda x: x.set_some_value(), lambda x: x.set_some_other_value()]) # simpler rule when no actions/side effects are needed simple_rule = Rule(checks=[lambda x: x.passes_condition()]) Dar a las checks y actions un valor predeterminado de None en la clase también significa que no tiene que definirlas si no hay comprobaciones/acciones para una regla determinada; simplificando aún más la definición de nuevas normas.
Probablemente definiría la clase Rule para que sea algo como esto y definiría check y perform_action en subclases:
class Rule: def check(self, item): # do some checking logic pass def perform_action(self, item): # do some action pass def apply_to(self, item): if self.check(item): self.perform_action(item) return True else: return False class SomeSpecificRule(Rule): def check(self, item): # do some specific check pass def perform_action(self, item): # do some specific action passEs un poco detallado, pero creo que es sencillo de leer/mantener. Su editor también debería poder completar los resguardos del método automáticamente, lo que ahorra algo de tiempo si está creando muchas reglas. También puede usar la herencia para compartir el comportamiento de diferentes tipos de reglas, como las que realizan la misma verificación o la misma acción.
No estoy seguro de cómo estaba definiendo Rule.andalso, etc., antes, pero podría definir una clase AndRule como esta:
class AndRule(Rule): def __init__(self, rule_1, rule_2): self.rule_1 = rule_1 self.rule_2 = rule_2 def check(self, item): return self.rule_1.check(item) and self.rule_2.check(item) def apply_to(self, item): if self.check(item): self.rule_1.perform_action(item) self.rule_2.perform_action(item) return True else: return FalseLo que significa que Rule.andalso podría ser simplemente:
class Rule: ... def andalso(self, other_rule): return AndRule(self, other_rule)