2011-08-18 9 views
5

Ho un "interfaccia" che sarà attuato dal codice client:Come posso bilanciare "Pythonic" e "convenient" in questo caso?

class Runner: 
    def run(self): 
     pass 

run dovrebbe in generale restituire un docutilsnode ma perché il caso comune molto molto più è testo normale, il chiamante permette run per tornare una stringa, che sarà controllata utilizzando type() e trasformata in node.

Tuttavia, il modo in cui ho capito "Pythonic", questo non è "Pythonic", perché verifica della type() di qualcosa di non lasciare che "essere" un tipo da "agire" come uno - cioè "Pythonic" il codice dovrebbe usare la digitazione anatra.

ho considerato

def run_str(self): 
    pass 

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

ma non mi importa di questo perché mette il non-così-interessante tipo di ritorno proprio lì nel nome; è fonte di distrazione.

Ci sono idee che ho perso? Inoltre, ci sono problemi che potrei incontrare lungo la strada con il mio sistema "cattivo" (sembra più o meno sicuro per me)?

+0

Sono un po 'confuso. Stai parlando del valore passato a 'run' (tramite' arg')? O stai parlando di come gestire il valore ipotetico restituito dal metodo 'run' di un oggetto che implementa l'interfaccia' Runner'? – senderle

+0

Intendo il ritorno. Ho modificato il 'arg' in modo da non distrarre (grazie per averlo indicato). – Owen

risposta

5

Penso che questo sia un esempio leggermente ingannevole; c'è qualcosa che non hai dichiarato. Immagino che quando dici "hai un'interfaccia", ciò che intendi è che hai un codice che accetta un oggetto e chiama il suo metodo run.

Se non si esegue il test del tipo di tale oggetto prima di chiamare il suo metodo run, si sta utilizzando la digitazione anatra, semplice e semplice! (In questo caso, se ha un metodo run, allora è un Runner.) Finché non usi type o isinstance sull'oggetto con un metodo run, allora sei Pythonic.

La domanda se accettare stringhe semplici o solo oggetti nodo è una domanda leggermente diversa. Le stringhe e gli oggetti node probabilmente non implementano affatto la stessa interfaccia! Le stringhe fondamentalmente non sono ciarlatano come un node, quindi non è necessario trattarle come se fossero una. Questo è come un elefante che arriva, e se vuoi che cova come un'anatra, devi dare all'elefante un registratore e addestrare l'elefante a usarlo prima.

Quindi non si tratta più di "digitazione anatra", ma del design dell'interfaccia. Stai cercando di decidere quanto vuoi che la tua interfaccia sia severa.

Per dare una risposta, quindi, a questo livello, penso che sia più Pythonic a supporre che run restituisca un oggetto node. Non è necessario utilizzare isinstance o type per verificarlo. Fai finta che sia un oggetto node e se il programmatore che usa la tua interfaccia si sbaglia e vede un'eccezione, allora dovranno leggere la tua docstring, che dirà loro che run deve passare un oggetto node.

Quindi, se si si desidera che accetti anche le stringhe o le cose che smettono di funzionare come stringhe, è possibile farlo. E poiché le stringhe sono tipi piuttosto primitivi, direi che non è inappropriato utilizzare isinstance(obj, basestring) (ma nontype(obj) == str perché rifiuta le stringhe unicode, ecc.). Essenzialmente, sei un tipo molto liberale e gentile con gli utenti pigri del tuo programma; stai già andando oltre e accettando gli elefanti e le cose che fanno schifo come le anatre.

(Più concretamente, direi che questo è un po 'come chiamare iter su un argomento all'inizio di una funzione che si desidera accettare entrambi i generatori e le sequenze.)

+0

Questo è un buon compromesso tra "purezza pitonica" e design pragmatico dell'API. Vorrei anche anticipare che i tuoi utenti "restituiranno" Nessuno non restituendo nulla, quindi dovresti anche scrivere il codice per quello. – PaulMcG

1

Partenza Errors and Exceptions. Si potrebbe fare qualcosa di simile:

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

Dentro la funzione make_node, lo hanno sollevare un AlreadyNodeError se l'argomento è già un nodo.

+0

+1 I miei pensieri esattamente (beh quasi esattamente). – zeekay

+0

Questo non sposta la chiamata unkosher a 'type()' nella funzione 'make_node()'? – Owen

+0

Non se la funzione make_node ha un blocco Try: Raise: in cui tenta di trasformarlo in un nodo e, dopo aver scoperto che è già un nodo, genera un errore. – Jonathanb

2

Non è necessario disporre di metodi per gestire ciascun tipo, soprattutto se è sufficiente un'operazione semplice. Un approccio Pythonic comune sarebbe quello di fare qualcosa di simile:

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

Questo segue lo stile Easier to ask for forgiveness than permission (EAFP) di codifica, che è generalmente più veloce e più semplice.

+0

Penso che stiamo parlando di cose diverse, intendevo il tipo di ritorno; ma la tua risposta si applicherebbe anche lì - potrebbe funzionare bene nel mio caso. – Owen

+0

Penso che sia generalmente una cattiva idea avere un metodo per restituire tipi di oggetti completamente diversi. Preferirei molto trattare due metodi separati in quel caso. – zeekay

+0

No hai ragione, * è * cattivo; è solo così * conveniente * che è allettante in questo momento. – Owen

1

Utilizzando type() per rilevare il tipo di una variabile è davvero una cattiva pratica, in quanto non permetterebbe un oggetto che eredita dal tipo desiderato (str nel tuo caso), un modo migliore è quello di utilizzare isinstance():

if isinstance(my_var, str): 
    my_code_here() 

Inoltre, un modo pietoso per farlo sarebbe digitare il dattilografo come hai menzionato, perché non inserisci il codice in un blocco try/except? Quindi un'eccezione verrebbe rilevata solo se il valore non è atto come previsto (se cessa e cammina come un'anatra, è un'anatra).

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))) 

Ora è doesn' t importa se il corridore restituisce un nodo o una stringa.

Problemi correlati