2010-12-14 15 views
7

Mam funkcję, która w najprostszych przypadkach działa na elementach, które można iterować.Elegancko obsługiwane różne typy parametrów

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

Czasami nie chcę przekazać mu iterowalny elementów bezpośrednio, ale raczej obiekt, który zapewnia sposób, aby uzyskać iterable:

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

mogę połączyć te dwa przypadki, jak ten :

def foo(obj): 
    try: 
     items = obj.iteritems() 
    except ValueError: 
     items = obj 
    for item in items: 
     # do stuff 

Działa to dobrze. Teraz dostaję przypadek użycia trzeciego, który wygląda tak:

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

mogę nadal stosować metodę try-except, ponieważ interfejsy są niekompatybilne. Jednak zagnieżdżona próba catch zaczęłaby być bardzo brzydka. Bardziej, gdy chcę dodać czwarty przypadek użycia. Czy istnieje sposób na rozwiązanie tego problemu bez zagnieżdżania bloków try?

+1

Ah, ja * więc * żałuję, że w Pythonie nie było przeciążenia funkcji! –

+3

@ André: prawie zawsze w Pythonie, jeśli chcesz przeciążać funkcje/metody, robisz to w niewłaściwy sposób. –

+0

Ale jaki jest problem z wysyłaniem jawnie wersji iteracyjnej? zbyt szczegółowe? – tokland

Odpowiedz

2

Jak się stoi, powinieneś użyć co najmniej dwóch metod tutaj, trzeci po prostu wywołanie pierwszego z wynikiem itertools.chain.from_iterable. Możesz również potencjalnie użyć dla * argumentów; zależy to od twojej konkretnej sprawy (podanie prawdziwego przykładu jest pomocne). Możesz również użyć prostej funkcji pomocnika, aby zwrócić właściwy typ iteratora.

Być może to wyszło:

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

Podany przeze mnie kod jest bardzo zbliżony do rzeczywistego, zmieniają się tylko nazwy. –

+0

@ Space_C0wb0y: ale nie wyjaśniłeś, jak dokładnie go używasz i po co. Dla niektórych rzeczy może istnieć lepszy sposób, który nie przyszedł ci do głowy (co często się zdarza, np.w jednym pytaniu użyłem funkcji niestandardowej, gdy 'x in y 'zrobiłoby się równie dobrze, ale z jakiegoś powodu mi to nie przyszło) –

+0

Funkcja jest generatorem, który oblicza określoną właściwość dla każdego z elementów. –

0

Aby sprawdzić, czy dany przedmiot jest iterable:

import collections 
isinstance(obj, collections.Iterable) 

To powinno Ci pozbyć się niektórych problemów.

Ponadto, sposobem radzenia sobie z takimi problemami jest za pomocą recursivity (nie wiem, czy to dotyczy jednak):

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' jest po prostu' try' wokół 'getattr'. –

+0

@chris Nie, nie wymusza zagnieżdżania wyjątków, np. Jest bardziej czytelny. O to właśnie pyta. –

1

musi zgadzać się z Chrisem: magia zrozumieć wszystko-wejściowy ma zamiar zawrócić i ugryzie cię. Jeśli przekażesz mu iterowalne obiekty z iterabelami, jak określisz, na jakim poziomie faktycznie rozpocząć przetwarzanie danych?

Znacznie lepiej trzymać się "listy lub generatora" jako wejścia, a następnie wstępnie przetworzyć swoje wywołania funkcji.

Powiązane problemy