2011-08-18 8 views
5

tengo una "interfaz" que será implementado por el código de cliente:¿Cómo puedo equilibrar "Pythonic" y "conveniente" en este caso?

class Runner: 
    def run(self): 
     pass 

run lo general debería devolver un docutilsnode, pero debido a que el caso común muy, muy más es texto plano, la persona que llama permite run para volver una cadena, que será marcada usando type() y convertida en node.

Sin embargo, la forma en que entiendo "Pythonic", esto no es "Pythonic" porque comprobar el type() de algo no lo deja "ser" un tipo de "actuar" como uno - es decir, "Pythonic" el código debería usar tipado de pato.

que considera

def run_str(self): 
    pass 

def run_node(self): 
    return make_node(self.run_str()) 

pero no me importa para esto porque pone el retorno no tan interesante tipo allí mismo, en el nombre; es una distracción

¿Alguna idea que me he perdido? Además, ¿hay problemas con los que pueda encontrarme más adelante con mi sistema "malo" (me parece más o menos seguro)?

+0

Estoy un poco confundido. ¿Estás hablando del valor pasado a 'ejecutar' (a través de' arg')? ¿O está hablando de cómo manejar el valor hipotético devuelto por el método 'run' de un objeto que implementa la interfaz' Runner'? – senderle

+0

Me refiero a la devolución. He editado el 'arg' para que no distraiga (gracias por señalar esto). – Owen

Respuesta

5

Creo que este es un ejemplo un poco engañoso; hay algo que no has declarado. Supongo que cuando dices que "tienes una interfaz", lo que quieres decir es que tienes un código que acepta un objeto y llama a su método run.

Si no está probando el tipo de ese objeto antes de llamar a su método run, ¡está utilizando el tipo de pato, simple y llanamente! (En este caso, si tiene un método run, entonces es un Runner.) Mientras no uses type o isinstance en el objeto con un método run, entonces estarás siendo Pythonic.

La cuestión de si debe aceptar strings simples o solo objetos de nodo es una pregunta sutilmente diferente. ¡Las cadenas y los objetos node probablemente no implementan la misma interfaz en absoluto! Las cadenas fundamentalmente no codifican como node, por lo que no es necesario tratarlas como tal. Esto es como un elefante que aparece, y si quieres que grazné como un pato, debes darle al elefante un reproductor de cintas y entrenar al elefante para que lo use primero.

Así que esto ya no es una cuestión de "tipa pato", sino de diseño de interfaz. Estás tratando de decidir qué tan estricto quieres que sea tu interfaz.

Para darle una respuesta, entonces, en este nivel, creo que es más pitónico suponer que run devuelve un objeto node. No es necesario utilizar isinstance o type para probarlo. Simplemente pretenda que es un objeto node y si el programador que usa su interfaz se equivoca y ve una excepción, entonces tendrán que leer su docstring, que les dirá que run debe pasar un objeto node.

Luego, si quiere para aceptar cadenas, o cosas que graznan como cadenas, puede hacerlo. Y como las cadenas son tipos bastante primitivos, diría que no es inapropiado usar isinstance(obj, basestring) (pero notype(obj) == str porque eso rechaza las cadenas de unicode, etc.). Básicamente, esto es que eres muy liberal y amable con los usuarios perezosos de tu programa; ya vas más allá aceptando elefantes y cosas que graznan como patos.

(Más concretamente, yo diría que esto es un poco como llamar iter en un argumento al comienzo de una función que desea aceptar ambos generadores y secuencias.)

+0

Esta es una buena compensación de "Pureza Pythonic" frente al diseño API pragmático. También me gustaría anticipar que sus usuarios "devolverán" None al no devolver nada, por lo que también deben codificar para eso. – PaulMcG

1

Salida Errors and Exceptions. Se podría hacer algo como esto:

def run(self,arg): 
    try: 
     return make_node(arg) 
    except AlreadyNodeError: 
     pass 

Dentro de su función make_node, tiene que levantar una AlreadyNodeError si el argumento es ya un nodo.

+0

+1 Mis pensamientos exactamente (bueno casi exactamente). – zeekay

+0

¿No cambia esto la llamada unkosher a 'tipo()' en la función 'make_node()'? – Owen

+0

No si la función make_node tiene un bloque Try: Raise: en el que intenta convertirlo en un nodo y, al descubrir que ya es un nodo, provoca un error. – Jonathanb

2

No necesita necesariamente tener métodos para manejar cada tipo, especialmente si una operación simple es todo lo que ocurrirá. Un enfoque Pythonic común sería hacer algo como:

def run(self): 
    try: 
     ...assume it's a str 
    except TypeError: 
     ...oops, not a str, we'll address that 

Esto sigue el estilo Easier to ask for forgiveness than permission (EAFP) de codificación, que es generalmente más rápido y más simple.

+0

Creo que hablamos de cosas diferentes, me refería al tipo de devolución; pero su respuesta se aplicaría también allí; podría funcionar bien en mi caso. – Owen

+0

Creo que generalmente es una mala idea tener un método que devuelva tipos de objetos completamente diferentes. Preferiría tratar con dos métodos separados en ese caso. – zeekay

+0

No, tienes razón, es * malo *; es tan conveniente que es tentador en este momento. – Owen

1

Usando type() para detectar el tipo de una variable es realmente una mala práctica, ya que no permitiría que un objeto que hereda de la clase deseada (str en su caso), una mejor manera es utilizar isinstance():

if isinstance(my_var, str): 
    my_code_here() 

Además, una manera pitón de hacer esto sería escribir patos como mencionaste, ¿por qué no pones el código en un bloque try/except? Por lo tanto, una excepción sería atrapada solo si el valor no es actúe como se espera (si grazna y camina como un pato, es un pato).

0
class Node(object): 
    def __new__(cls, contents): 
    return contents if isinstance(contents, cls) else object.__new__(cls) 
    def __init__(self, contents): 
    # construct from string... 

class Manager(object): 
    def do_something(self, runner, *args): 
    do_something_else(Node(runner(*args))) 

Ahora que doesn' Importa si el corredor devuelve un nodo o una cadena.

Cuestiones relacionadas