как проверить, допускает ли итерация более одного прохода?

В 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
8
задан max 24 January 2012 в 20:27
поделиться