2010-12-14 12 views
7

Ho una funzione che, nel più semplice dei casi, opera su un iterabile di elementi.Gestisce in modo elegante diversi tipi di parametri

def foo(items): 
    for item in items: 
     # do stuff 

A volte, io non voglio passare un iterabile di voci direttamente, ma piuttosto un oggetto che fornisce un metodo per ottenere il iterabili:

def foo(obj): 
    for item in obj.iteritems(): 
     # do same stuff as above 

posso unire questi due casi come questo :

Questo funziona bene. Ora ho una terza caso d'uso che assomiglia a questo:

def foo(objs): 
    for item in itertools.chain.from_iterable(obj.iteritems() for obj in objs): 
     # do same stuff again 

posso ancora usare l'approccio try-except, dal momento che le interfacce sono incompatibili. Tuttavia, il tentativo di cattura annidato inizierà a diventare molto brutto. Ancora di più quando voglio aggiungere un quarto caso d'uso. C'è un modo per risolvere questo problema senza annidare i blocchi try?

+1

Ah, I * so * vorrei che esistessero sovraccarichi di funzione in Python! –

+3

@ André: quasi sempre in Python, se vuoi quel tipo di sovraccarico di funzione/metodo, lo stai facendo nel modo sbagliato. –

+0

Ma qual è il problema con l'invio esplicito del iterable? troppo prolisso? – tokland

risposta

2

Così com'è, probabilmente si dovrebbe utilizzare almeno due metodi qui, il terzo basta chiamare il primo con il risultato itertools.chain.from_iterable. Potresti anche potenzialmente avere un uso per * args; dipende dal tuo caso preciso (fornire un esempio reale è utile). È inoltre possibile utilizzare una semplice funzione di supporto per restituire il giusto tipo di iteratore.

Forse questo potrebbe funzionare:

def _foo_iter(obj): 
    try: 
     return obj.iteritems() 
    except AttributeError: 
     return obj 

def foo(*objs): 
    for obj in objs: 
     for item in _foo_iter(obj): 
+0

Il codice che ho fornito è abbastanza vicino alla realtà, solo i nomi sono cambiati. –

+0

@ Space_C0wb0y: ma non hai spiegato con che precisione lo stai usando e per quale motivo. Per alcune cose potrebbe esserci un modo migliore che non ti è mai venuto in mente (che spesso accade, ad es.in una domanda stavo usando una funzione personalizzata quando 'x in y' avrebbe funzionato altrettanto bene ma per qualche ragione non mi era venuto in mente) –

+0

La funzione è un generatore che calcola una certa proprietà per ciascuno degli elementi. –

0

Per verificare se un oggetto è iterabile:

import collections 
isinstance(obj, collections.Iterable) 

Questo dovrebbe farti liberarsi di alcuni problemi.

Inoltre, un modo per gestire questi problemi è quello di utilizzare ricorsività (non so se è applicabile però):

def foo(obj): 
    if isinstance(obj, collections.Iterable): 
     for el in obj: 
      foo(el) 
    # now do stuff 
0

hasattr(object, name)

if hasattr(obj, '__iter__'): 
     return [o for o in obj] 
    elif hasattr(obj, 'iteritems'): 
     return [o for o in obj.iteritems()] 
    elif isinstance(obj, list) or isinstance(obj, tuple): 
     return [o.iteritems() for o in [o for o in obj]] 
    else: 
     raise TypeError 

class Bar(list): 
    def iteritems(self): 
     return self.__iter__() 

print foo([1,2,3]) 
print foo(Bar([1,2,3])) 
print foo([[1,2,3], [1,2,3]]) 
print foo(1) 

[1, 2, 3] 
[1, 2, 3] 
[[1, 2, 3], [1, 2, 3]] 
Traceback (most recent call last): 
    File "test.py", line 20, in <module> 
    print foo(1) 
    File "test.py", line 11, in foo 
    raise TypeError 
TypeError 
+0

'hasattr' è essenzialmente solo un' try' around' getattr'. –

+0

@chris No, non impone la nidificazione di eccezioni, ad esempio è più leggibile. Qual è la domanda che si pone. –

1

deve essere d'accordo con Chris: la magia capire-tutto ingresso sta per girarsi e mordere. Se si passa a un iterable di oggetti-con-iterables di iterable, come si specifica a quale livello avviare effettivamente l'elaborazione dei dati?

Molto meglio attenersi a "un elenco o un generatore" come input, quindi pre-elaborare le chiamate alla funzione.