2012-01-30 3 views
19

Каждое использование, которое я могу придумать для класса itertools.repeat() Python, я могу придумать еще одно равно (возможно, более) приемлемое решение для достижения такого же эффекта. Например:Какова цель в itertools.repeat Python?

>>> (i for i in itertools.repeat('example', 5)) 
('example', 'example', 'example', 'example', 'example') 
>>> ('example') * 5 
('example', 'example', 'example', 'example', 'example') 

>>> map(str.upper, itertools.repeat('example', 5)) 
['EXAMPLE', 'EXAMPLE', 'EXAMPLE', 'EXAMPLE', 'EXAMPLE'] 
>>> ['example'.upper()] * 5 
['EXAMPLE', 'EXAMPLE', 'EXAMPLE', 'EXAMPLE', 'EXAMPLE'] 

Есть ли случаи, когда это было бы наиболее подходящее решение? Если да, то при каких обстоятельствах?

+3

Я добавил новый ответ, который показывает оригинальный мотивирующий пример использования для повторения itertools. Кроме того, я только что обновил документы Python, чтобы отразить это примечание об использовании. –

ответ

19

itertools.repeat функция ленив; он использует только память, требуемую для одного элемента. С другой стороны, идиомы (a) * n и [a] * n создают n копий объекта в памяти. Для пяти элементов, вероятно, лучше идиома умножения, но вы можете заметить проблему с ресурсами, если вам нужно повторить что-то, скажем, миллион раз.

По-прежнему трудно представить много статические использует для itertools.repeat. Однако тот факт, что itertools.repeat является функцией , позволяет использовать его во многих функциональных приложениях. Например, у вас может быть некоторая функция библиотеки func, которая работает с итерабельным вводом. Иногда у вас могут быть предварительно построенные списки различных предметов. В других случаях вы можете просто использовать единый список. Если список большой, itertools.repeat сохранит вам память.

Наконец, repeat делает возможной так называемую «итерационную алгебру», описанную в документации itertools. Даже сам модуль itertools использует функцию repeat.Например, следующий код указан как эквивалентная реализация itertools.izip_longest (хотя реальный код, вероятно, написан на языке C). Обратите внимание на использовании repeat семь линий от дна:

class ZipExhausted(Exception): 
    pass 

def izip_longest(*args, **kwds): 
    # izip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D- 
    fillvalue = kwds.get('fillvalue') 
    counter = [len(args) - 1] 
    def sentinel(): 
     if not counter[0]: 
      raise ZipExhausted 
     counter[0] -= 1 
     yield fillvalue 
    fillers = repeat(fillvalue) 
    iterators = [chain(it, sentinel(), fillers) for it in args] 
    try: 
     while iterators: 
      yield tuple(map(next, iterators)) 
    except ZipExhausted: 
     pass 
+9

Незначительный приговор: '[a] * n' не создает n копий a в памяти.Он создает n ссылок на одну копию a. В некоторых случаях разница может быть весьма значительной; попробуйте 'a = [[]] * 5; а [0] .append (1) '. –

+5

Хорошая точка. Я все время забываю, что почти все в Python является ссылкой. Я думаю, что это также немного сдерживает проблему использования памяти, но я бы предположил, что миллион ссылок по-прежнему имеет нетривиальное требование к ресурсам. – HardlyKnowEm

+2

Да, все равно придется выделять массив из n указателей. –

16

Ваш пример foo * 5 выглядит внешне аналогичным itertools.repeat(foo, 5), но на самом деле это совсем другое.

Если вы пишете foo * 100000, интерпретатор должен создать 100 000 копий foo, прежде чем он сможет дать вам ответ. Это, таким образом, очень дорогая и недружественная операция.

Но если вы пишете itertools.repeat(foo, 100000), интерпретатор может вернуть итератор, который выполняет ту же функцию, и не нужно, чтобы вычислить результат, пока не нужно - скажем, с помощью его в функцию, которая хочет знать каждый результат в последовательности.

Это главное преимущество итераторов: они могут отложить вычисление части (или всего) списка, пока вам не понадобится ответ.

+0

Почему бы просто не использовать 'for i в диапазоне (100000):', а затем получить доступ к 'foo' внутри цикла вместо того, чтобы просить эту функцию, какое значение вы дали? –

+0

@TylerCrompton: Итератор может быть передан другим вещам, которые ожидают любой итератор, независимо от его внутреннего содержимого. Вы не можете сделать то же самое с диапазоном (он итерабелен, но не является итератором). –

+0

Я вижу вашу точку зрения, но что касается конца вашего комментария, в Python 3? –

2

Это итератор. Большая подсказка здесь: он находится в модуле itertools. Из документации вы связаны с:

itertools.repeat (объект [,] раз) Сделать итератор, который возвращает объект снова и снова. Выполняется неопределенно, если не указан аргумент times.

Значит, у вас никогда не будет всего этого в памяти. Пример, в котором вы хотите использовать его может быть

n = 25 
t = 0 
for x in itertools.repeat(4): 
    if t > n: 
     print t 
    else: 
     t += x 

, поскольку это позволит вам произвольное число 4 с, или все, что вам может понадобиться бесконечный список.

+3

Вы можете изменить строку 3 на 'while True:' и 'x' на строке 7 до' 4', и она будет делать то же самое, будет более читаемой и будет немного быстрее. Вот почему мне было интересно, имеет ли она какие-то цели. –

14

Основной целью itertools.repeat является обеспечением потока постоянных значений для использования с карты или почтового:

>>> list(map(pow, range(10), repeat(2)))  # list of squares 
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 

вторичная цель состоит в том, что она дает очень быстрый способ цикла фиксированное количество раз, как это:

for _ in itertools.repeat(None, 10000): 
    do_something() 

Это быстрее, чем:

for i in range(10000): 
    do_something(). 

Бывшие победы, потому что все, что нужно сделать, это обновить счетчик ссылок для существующего None объекта. Последний теряет, потому что диапазон () или xrange() должен изготовить 10 000 различных целых объектов.

Примечание. Сам Гвидо использует эту технику быстрого цикла в модуле timeit(). См.источник на https://hg.python.org/cpython/file/2.7/Lib/timeit.py#l195:

if itertools: 
     it = itertools.repeat(None, number) 
    else: 
     it = [None] * number 
    gcold = gc.isenabled() 
    gc.disable() 
    try: 
     timing = self.inner(it, self.timer) 
2

Как уже упоминалось выше, он хорошо работает с zip:

Другой пример:

from itertools import repeat 

fruits = ['apples', 'oranges', 'bananas'] 

# Initialize inventory to zero for each fruit type. 
inventory = dict(zip(fruits, repeat(0))) 

Результат:

{'apples': 0, 'oranges': 0, 'bananas': 0} 

Чтобы сделать это без повторите, мне нужно будет включить len(fruits).

+2

'inventory = {fruit: 0 for fruit in fruits}' более читабельна и немного быстрее. –

+0

@TylerCrompton Действительно. Я не уверен, что раньше использовал этот синтаксис для инициализации словаря. Или я просто использовал слишком много LINQ :-) Спасибо за информативный комментарий. –

0

Обычно я использую повтор в сочетании с цепью и циклом. Ниже приведен пример:

from itertools import chain,repeat,cycle 

fruits = ['apples', 'oranges', 'bananas', 'pineapples','grapes',"berries"] 

inventory = list(zip(fruits, chain(repeat(10,2),cycle(range(1,3))))) 

print inventory 

ставит первые 2 плоды как значение 10, то циклы значений 1 и 2 для остальных фруктов.