W Pythonie 3, jak mogę sprawdzić, czy obiekt jest kontenerem (zamiast iteratora, który może zezwalać tylko na jedno przejście)?jak sprawdzić, czy iteracja dopuszcza więcej niż jeden przebieg?
Oto przykład:
def renormalize(cont):
'''
each value from the original container is scaled by the same factor
such that their total becomes 1.0
'''
total = sum(cont)
for v in cont:
yield v/total
list(renormalize(range(5))) # [0.0, 0.1, 0.2, 0.3, 0.4]
list(renormalize(k for k in range(5))) # [] - a bug!
Oczywiście, gdy funkcja renormalize
odbiera wyrażenie generator, to nie działa zgodnie z przeznaczeniem. Zakłada on, że może on iterować przez kontener wiele razy, podczas gdy generator umożliwia tylko jedno przejście przez niego.
Idealnie, chciałbym to zrobić:
def renormalize(cont):
if not is_container(cont):
raise ContainerExpectedException
# ...
Jak mogę wdrożyć is_container
?
Przypuszczam, że mógłbym sprawdzić, czy argument jest pusty, gdy zaczynamy robić drugie przejście przez to. Ale to podejście nie działa dla bardziej skomplikowanych funkcji, w których nie jest oczywiste, kiedy dokładnie rozpoczyna się drugie przejście. Co więcej, wolę raczej sprawdzanie poprawności przy wejściu do funkcji niż w głąb funkcji (i przesuwanie jej, gdy tylko funkcja jest modyfikowana).
Mogę oczywiście przepisać funkcję renormalize
, aby działała poprawnie z iteratorem jednoprzebiegowym. Ale to wymaga skopiowania danych wejściowych do kontenera. Wpływ na wydajność kopiowania milionów dużych list "na wypadek, gdyby nie były listami" jest śmieszny.
EDIT: Mój oryginalny przykład użył weighted_average
funkcję:
def weighted_average(c):
'''
returns weighted average of a container c
c contains values and weights in tuples
weights don't need to sum up 1 (automatically renormalized)
'''
return sum((v * w for v, w in c))/sum((w for v, w in c))
weighted_average([(0,1), (1,1)]) #0.5
weighted_average([(k, 1) for k in range(2)]) #0.5
weighted_average((k, 1) for k in range(2)) #mistake
Ale to nie był najlepszy przykład, ponieważ wersja weighted_average
przepisany do korzystania z pojedynczej podanie jest zapewne lepiej i tak:
def weighted_average(it):
'''
returns weighted average of an iterator it
it yields values and weights in tuples
weights don't need to sum up 1 (automatically renormalized)
'''
total_value = 0
total_weight = 0
for v, w in it:
total_value += v
total_weight += w
return total_value/total_weight
Nie widzę problemu z ogólną wersją, czy profilowałeś ją? A co rozumiesz przez złożoność wizualną? – LBarret
"gdzie nie jest oczywiste, kiedy dokładnie rozpoczyna się drugi przebieg"? Co to może oznaczać? Możesz użyć 'itertools.tee() 'bezwarunkowo gwarantuje, że możesz iterować tyle razy, ile to konieczne. Jak to może nie być oczywiste, kiedy projektujesz algorytmy? –
@LionelBarret: Zgadzam się, nie ma powodu, aby nie używać ogólnego "weighted_average". Zaktualizowałem pytanie, aby podać inny przykład. – max