В Python 3 , как я могу проверить, является ли объект контейнером (а не итератором, который может допускать только один проход)?
Вот пример:
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!
Очевидно, когда функция renormalize
получает выражение генератора , он не работает должным образом. Предполагается, что он может выполнять итерацию по контейнеру несколько раз, в то время как генератор допускает только один проход через него.
В идеале, я бы хотел сделать это:
def renormalize(cont):
if not is_container(cont):
raise ContainerExpectedException
# ...
Как n Я реализую is_container
?
Полагаю, я мог бы проверить, пуст ли аргумент прямо сейчас, когда мы начинаем выполнять второй проход через него. Но этот подход не работает для более сложных функций, где не очевидно, когда именно начинается второй проход.Кроме того, я бы предпочел поместить проверку на входе в функцию, а не глубоко внутри функции (и сдвигать ее при каждом изменении функции).
Я, конечно, могу переписать функцию renormalize
для правильной работы с однопроходным итератором. Но для этого необходимо скопировать входные данные в контейнер. Влияние на производительность копирования миллионов больших списков «на случай, если они не списки» просто смешно.
РЕДАКТИРОВАТЬ: В моем исходном примере использовалась функция weighted_average
:
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
Но это был не лучший пример, поскольку версия weighted_average
, переписанная для использования за один проход, в любом случае, возможно, лучше :
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