¿Cómo implementar un decorador con igualdad no local?
-
02-07-2019 - |
Pregunta
Saludos, actualmente estoy refactorizando uno de mis programas y encontré un problema interesante.
Tengo transiciones en un autómata. Las transiciones siempre tienen un estado inicial y un estado final. Algunas transiciones tienen una etiqueta, que codifica una determinada acción que se debe realizar al atravesar. Ninguna etiqueta significa ninguna acción. Algunas transiciones tienen una condición, que debe cumplirse para atravesar esta condición, si no hay una condición, la transición es básicamente una transición épsilon en una NFA y se recorrerá sin consumir un símbolo de entrada.
Necesito las siguientes operaciones:
- compruebe si la transición tiene una etiqueta
- obtener esta etiqueta
- agregar una etiqueta a una transición
- compruebe si la transición tiene una condición
- obtener esta condición
- comprobar la igualdad
A juzgar por los primeros cinco puntos, esto suena como un decorador claro, con una transición de base y dos decoradores: Etiquetado y Condición. Sin embargo, este enfoque tiene un problema: dos transiciones se consideran iguales si su estado inicial y el estado final son iguales, las etiquetas en ambas transiciones son iguales (o no existen) y ambas condiciones son las mismas (o no existen) . Con un decorador, podría tener dos transiciones etiquetadas ("foo", Condicional ("barra", Transición ("baz", "qux")) y Condicional ("barra", etiquetada ("foo" ;, Transición (" baz " ;, " qux "))) que necesita una igualdad no local, es decir, los decoradores necesitarían recopilar todos los datos y la Transición debe comparar estos datos recopilados en una base determinada:
class Transition(object):
def __init__(self, start, end):
self.start = start
self.end = end
def get_label(self):
return None
def has_label(self):
return False
def collect_decorations(self, decorations):
return decorations
def internal_equality(self, my_decorations, other):
try:
return (self.start == other.start
and self.end == other.end
and my_decorations = other.collect_decorations())
def __eq__(self, other):
return self.internal_equality(self.collect_decorations({}), other)
class Labeled(object):
def __init__(self, label, base):
self.base = base
self.label = label
def has_label(self):
return True
def get_label(self):
return self.label
def collect_decorations(self, decorations):
assert 'label' not in decorations
decorations['label'] = self.label
return self.base.collect_decorations(decorations)
def __getattr__(self, attribute):
return self.base.__getattr(attribute)
¿Es este un enfoque limpio? ¿Me estoy perdiendo algo?
Estoy más confundido, porque puedo resolver esto - con nombres de clase más largos - usando herencia múltiple cooperativa:
class Transition(object):
def __init__(self, **kwargs):
# init is pythons MI-madness ;-)
super(Transition, self).__init__(**kwargs)
self.start = kwargs['start']
self.end = kwargs['end']
def get_label(self):
return None
def get_condition(self):
return None
def __eq__(self, other):
try:
return self.start == other.start and self.end == other.end
except AttributeError:
return False
class LabeledTransition(Transition):
def __init__(self, **kwargs):
super(LabeledTransition).__init__(**kwargs)
self.label = kwargs['label']
def get_label(self):
return self.label
def __eq__(self):
super_result = super(LabeledTransition, self).__eq__(other)
try:
return super_result and self.label == other.label
except AttributeError:
return False
class ConditionalTransition(Transition):
def __init__(self, **kwargs):
super(ConditionalTransition, self).__init__(**kwargs)
self.condition = kwargs['condition']
def get_condition(self):
return self.condition
def __eq__(self, other):
super_result = super(ConditionalTransition, self).__eq__(other)
try:
return super_result and self.condition = other.condition
except AttributeError:
return False
# ConditionalTransition about the same, with get_condition
class LabeledConditionalTransition(LabeledTransition, ConditionalTransition):
pass
la clase LabledConditionalTransition se comporta exactamente como se esperaba, y no tener código es atractivo y no creo que MI sea confuso en este tamaño.
Por supuesto, la tercera opción sería juntar todo en una sola clase de transición con un montón de has_label / has_transition.
Entonces ... estoy confundido. ¿Me estoy perdiendo de algo? ¿Qué implementación se ve mejor? ¿Cómo maneja los casos similares, es decir, los objetos que parecen un Decorador podrían manejarlos, pero luego, aparece un método no local?
EDIT : Se agregó la clase ConditionalTransition. Básicamente, este tipo de comportamiento se comporta como el decorador, menos el orden creado por el orden de creación de los decoradores, las verificaciones de transición para el inicio y el final son correctas, la clase de Transición Etiquetada verifica que la etiqueta sea correcta y la Transición Condicional verifica que la condición sea correcta.
Solución
Creo que está claro que nadie entiende realmente tu pregunta. Sugeriría ponerlo en contexto y hacerlo más corto. A modo de ejemplo, aquí hay una posible implementación del patrón de estado en python, estudie para obtener una idea.
class State(object):
def __init__(self, name):
self.name = name
def __repr__(self):
return self.name
class Automaton(object):
def __init__(self, instance, start):
self._state = start
self.transitions = instance.transitions()
def get_state(self):
return self._state
def set_state(self, target):
transition = self.transitions.get((self.state, target))
if transition:
action, condition = transition
if condition:
if condition():
if action:
action()
self._state = target
else:
self._state = target
else:
self._state = target
state = property(get_state, set_state)
class Door(object):
open = State('open')
closed = State('closed')
def __init__(self, blocked=False):
self.blocked = blocked
def close(self):
print 'closing door'
def do_open(self):
print 'opening door'
def not_blocked(self):
return not self.blocked
def transitions(self):
return {
(self.open, self.closed):(self.close, self.not_blocked),
(self.closed, self.open):(self.do_open, self.not_blocked),
}
if __name__ == '__main__':
door = Door()
automaton = Automaton(door, door.open)
print 'door is', automaton.state
automaton.state = door.closed
print 'door is', automaton.state
automaton.state = door.open
print 'door is', automaton.state
door.blocked = True
automaton.state = door.closed
print 'door is', automaton.state
la salida de este programa sería:
door is open
closing door
door is closed
opening door
door is open
door is open
Otros consejos
A partir del código publicado, la única diferencia entre Transición y Transición etiquetada es la devolución de get_lable () y has_label (). En cuyo caso puede comprimir estos dos una sola clase que establece un atributo de etiqueta en Ninguno y
return self.label is not None
en la función has_label ().
¿Puede publicar el código para la clase ConditionalTransition
? Creo que esto lo haría más claro.