2009-02-27 15 views
17

Recientemente leí las preguntas que recomiendan no usar las sentencias de cambio de mayúsculas y minúsculas en los idiomas que sí lo admiten. Por lo que va Python, he visto una serie de reemplazos de casos de conmutación, tales como:Elegir entre diferentes reemplazos de conmutadores en Python - dictionary o if-elif-else?

  1. uso de un diccionario (Muchas variantes)
  2. Usando una tupla
  3. El uso de un decorador de la función (http://code.activestate.com/recipes/440499/)
  4. Utilización de polimorfismo (método recomendado en lugar de la verificación de tipos de objetos)
  5. el uso de un si-elif-else escalera
  6. Alguien incluso se recomienda el patrón de visitantes (Posiblemente extrínseca)

Dada la amplia variedad de opciones, tengo un poco de dificultad para decidir qué hacer con un código en particular. Me gustaría aprender los criterios para seleccionar uno de estos métodos sobre el otro en general. Además, agradecería consejos sobre qué hacer en los casos específicos en los que tengo problemas para decidir (con una explicación de la elección).

Aquí está el problema específico:
(1)

def _setCurrentCurve(self, curve): 
     if curve == "sine": 
      self.currentCurve = SineCurve(startAngle = 0, endAngle = 14, 
      lineColor = (0.0, 0.0, 0.0), expansionFactor = 1, 
      centerPos = (0.0, 0.0)) 
     elif curve == "quadratic": 
      self.currentCurve = QuadraticCurve(lineColor = (0.0, 0.0, 0.0)) 

Este método es llamado por un qt-ranura en respuesta a elegir para dibujar una curva de un menú. El método anterior contendrá un total de 4-7 curvas una vez que la aplicación esté completa. ¿Está justificado usar un diccionario desechable en este caso? Dado que la forma más obvia de hacerlo es if-elif-else, ¿debería seguir así? También he considerar el uso ** Kargs aquí (con una ayuda amigos) ya que todas las clases de curvas utilizan ** Kargs ...

(2)
Esta segunda pieza de código es un qt-ranura que es llamado cuando el usuario cambia una propiedad de una curva. Básicamente, la ranura toma los datos del gui (spinBox) y los coloca en una variable de instancia de la clase de curva apropiada. En este caso, nuevamente tengo la misma pregunta: ¿Debería usar un dict?

Aquí es el ya mencionado de ranura

def propertyChanged(self, name, value): 
    """A Qt slot, to react to changes of SineCurve's properties.""" 
    if name == "amplitude": 
     self.amplitude = value 
    elif name == "expansionFactor": 
     self.expansionFactor = value 
    elif name == "startAngle": 
     self.startAngle = value 
    elif name == "endAngle": 
     self.endAngle = value 

Para referencia, aquí es el código para la conexión a la anterior ranura -

def _connectToPage(self, page): 
    for connectionData in page.getConnectibles(): 
     self.connect(connectionData["object"], 
        SIGNAL(connectionData["signal"]), 
        lambda value, name = connectionData["property"]:\ 
         self.currentCurve.propertyChanged(name, value)) 
     self.connect(connectionData["object"], 
        SIGNAL(connectionData["signal"]), 
        self.hackedDisplayArea.update) 

Nota - El self.endAngle etc. se inicializan en el constructor

Hasta donde yo sé, las razones para elegir un dict son para una búsqueda rápida. ¿Cuándo está eso garantizado? cuando tengo 100 cajas o más? ¿Es una buena idea seguir creando y tirando un diccionario cada vez que se llame a la función? Si construyo un dict para este propósito fuera de una función, ¿debería verificar si se necesita en otro lugar? ¿Qué pasa si no es necesario en otro lado?

Mi pregunta es ¿cuál es la mejor práctica si hay una? ¿Cuál es la mejor/más elegante forma de hacer las cosas? Dicho de otra manera, ¿cuándo usar if-elif-else, cuándo usar cada una de las otras opciones?

Respuesta

23

suspiro. Demasiado mano exprimiendo la parte incorrecta del problema. La declaración de cambio no es el problema. Hay muchas formas de expresar "alternativa" que no agregan , que significa.

El problema es que significa - no hay opciones de declaraciones técnicas.

Hay tres patrones comunes.

  • asignación de una clave para un objeto. Usa un diccionario si es casi totalmente estático y tienes un mapeo entre una clave simple y otra cosa más compleja. Crear un diccionario sobre la marcha cada vez que lo necesite es una tontería. Puede usar esto si es lo que significa: sus "condiciones" son valores de clave estáticos simples que se asignan a objetos.

  • Comportamiento de las variantes entre las subclases. Use polimorfismo en lugar de escribir objetos de comprobación. Correcto. Si tiene objetos similares en múltiples clases con comportamiento variante, deberían ser polimórficos. Use esto tan a menudo como sea posible.

  • Comportamiento de la otra variante. Usa una escalera if-elif-else. Úselo cuando no tenga un mapeo key-to-value en gran parte estático. Úselo cuando las condiciones sean complejas o significa procedimientos, no objetos.

Todo lo demás es solo un código complicado que puede lograr resultados similares.

Usando un Tuple. Esto es solo un diccionario sin el mapeo. Esto requiere búsqueda, y la búsqueda debe evitarse siempre que sea posible. No hagas esto, es ineficiente. Usa un diccionario.

Usando un decorador de funciones (http://code.activestate.com/recipes/440499/). Icky. Esto oculta la naturaleza if-elif-elif del problema que está resolviendo. No haga esto, no es obvio que las opciones son exclusivas. Usa cualquier cosa

Alguien incluso recomendó Patrón de visitante. Úselo cuando tenga un objeto que siga el patrón de diseño Composite. Esto depende de que el polimorfismo funcione, por lo que no es realmente una solución diferente.

+0

Si utilizo una clave estática simple para valorar el mapeo, sigue siendo una mala idea construir sobre la marcha ¿no? En ese caso, ¿no tendría que usar if-else, a menos que almacenara el diccionario en alguna parte y lo usara en dos o más lugares? Gracias por la respuesta detallada. – batbrat

+0

Solo para estar seguro, no debería hacer algo como {key1: function1, key2: function2} .get (..., ...) ¿no? – batbrat

+1

Construir un diccionario sobre la marcha no es lo que ** significa **. Si quieres decir if-elif-elif, solo di eso. Si su ** significado ** es "Esta clave siempre se asigna a esta función", dígala como parte oficial de primera clase de su programa. –

8

En el primer ejemplo, me quedaría con la declaración if-else. De hecho, no veo una razón para no utilizar si-persona, a menos

  1. Usted encontrar (por ejemplo, utilizando el módulo de perfil) que la sentencia if es un cuello de botella (muy poco probable que la OMI a menos que tenga un gran número de casos que hacen muy poco)

  2. El código que utiliza un diccionario es más claro/tiene menos repeticiones.

Su segundo ejemplo que en realidad reescribir

setattr(self, name, value) 

(probablemente la adición de una sentencia assert para capturar nombres no válidos).

+0

+1 No pensé en setattr. – batbrat

+0

Muchas gracias por la respuesta. Fue realmente útil. Si pudiera marcar dos respuestas como correctas, también habría marcado la tuya. Como es que no puedo :(De todos modos, realmente aprecio tu ayuda. – batbrat

1

Cada una de las opciones expuestas se ajustan bien algunos escenarios:

  1. si-elif-else: simplicidad, claridad
  2. diccionario: útil cuando se configura dinámicamente (imaginar que necesita una funcionalidad particular para ser ejecutado en una sucursal)
  3. tupla: simplicidad sobre el caso if-else para múltiples opciones por sucursal.
  4. polimorfismo: automática de objetos orientados ramificación
  5. etc.

Python es sobre la legibilidad y la coherencia e incluso si su decisión será siempre una subjetiva y que dependerá de su estilo, siempre se debe pensar en Python mantras

./alex

+0

Gracias por darme una guía. – batbrat

+0

Estoy aprendiendo y recordándolos todos los días. Un aspecto de una base de código (que a veces se ignora) se trata de colaboración y comunicación clara. – alexpopescu

+0

Una cosa más. ¿Qué quiere decir con configuración dinámica? Creo que sigo, solo para ser claro, ¿podría comentar? – batbrat

1

Estoy de acuerdo con df en relación con el segundo ejemplo. El primer ejemplo probablemente intente reescribir usando un diccionario, especialmente si todos los constructores de curva tienen la misma firma de tipo (quizás usando * args y/o ** kwargs). Algo así como

def _setCurrentCurve(self, new_curve): 
    self.currentCurve = self.preset_curves[new_curve](options_here) 

o quizás incluso

def _setCurrentCurve(self, new_curve): 
    self.currentCurve = self.preset_curves[new_curve](**preset_curve_defaults[new_curve]) 
+0

Lo he considerado. Sin embargo, en el primer ejemplo, tendría que incluir todas las opciones de construcción para cualquier curva que pudiera construirse. ¿Es eso una buena idea? eg: self.currentCurve = self.preset_curves [new_curve] (startAngle = 10, ...., endX = 120) – batbrat

+0

+1 para confirmar mi sospecha de que ** kargs podría ayudar. – batbrat

+0

Me parece que para eso son los argumentos predeterminados en el constructor. – kquinn

2

Teniendo en cuenta que esto se hace en respuesta a una acción del usuario (cosechas algo de un menú), y el número de opciones a anticipar es muy pequeña, lo Definitivamente iría con una escalera simple if-elif-else.

No tiene sentido optar por la velocidad, ya que solo ocurre tan rápido como el usuario puede hacer la selección de todos modos, esto no es "bucle interno de un raytracer" -territorio. Claro, importa dar al usuario comentarios rápidos, pero dado que el número de casos es muy pequeño, tampoco hay peligro de eso.

No tiene sentido optimizar la concisión, ya que de todos modos la escalera if (más clara, con cero legibilidad) if-ladder será muy corta.

+0

Estoy de acuerdo. No lo arregles si no está roto. – sykora

2

En cuanto a sus preguntas diccionario:

Por lo que yo sé, las razones para elegir un diccionario es para la búsqueda rápida. ¿Cuándo está eso garantizado? cuando tengo 100 cajas o más?¿Es una buena idea seguir creando y tirando un diccionario cada vez que se llame a la función? Si construyo un dict para este propósito fuera de una función, ¿debería verificar si se necesita en otro lugar? ¿Qué pasa si no es necesario en otro lado?

  1. Otro problema es el mantenimiento. Tener el diccionario string-> curveFunction le permite manejar el menú con datos. Luego, agregar otra opción es simplemente una cuestión de poner otra entrada string-> function en el diccionario (que vive en una parte del código dedicada a la configuración.

  2. Incluso si tiene pocas entradas, "separa las preocupaciones "; _setCurrentCurve es responsable de cableando en tiempo de ejecución, no la definición de la caja de componentes

  3. crear el diccionario, y aferrarse a él

  4. Incluso si no se utiliza en otros lugares, se obtiene los beneficios anteriores (.. ubicación, capacidad de mantenimiento).

Mi regla de oro es preguntar "¿Qué está pasando aquí?" Para cada componente de mi código. Si la respuesta es de la forma

... y ... y ...

(como en "la definición de la biblioteca de funciones y que asocian cada uno con una valor en el menú ") entonces hay algunas preocupaciones que piden separarse.

+0

+1 Buen punto. Me preguntaba sobre otros beneficios. Sin embargo, podría "manejar" el menú como dijiste, pero podría ser exagerado, ya que solo hay unas pocas curvas de todos modos (y es probable que sigan así en el futuro).D9o comparte tus pensamientos sobre esto. Gracias. – batbrat

+0

¡Gracias de nuevo! La idea del menú basado en datos será de gran ayuda en uno de mis otros proyectos. – batbrat

1

En Python, no piense en cómo reemplazar una instrucción de conmutación.

Use clases y polimorfismo en su lugar. Intente mantener la información sobre cada opción disponible y cómo implementarla en un solo lugar (es decir, la clase que lo implementa).

De lo contrario, terminará teniendo muchos lugares que contienen una pequeña fracción de cada opción, y la actualización/ampliación será una pesadilla de mantenimiento.

Este es exactamente el tipo de problema que OOD intenta resolver por abstracción, ocultamiento de información, polimorfismo y el lote.

Piensa en las clases de objetos que tienes y sus propiedades, luego crea una arquitectura OO a su alrededor. De esta forma, nunca más tendrás que preocuparte de perder una instrucción de "cambio".

Cuestiones relacionadas