Это нормально для tuple () так сильно увеличить время выполнения? - PullRequest
3 голосов
/ 30 апреля 2020

У меня есть относительно длинный (20000 строк) CSV-файл и простая функция, которую я написал, чтобы открыть его:

def read_prices():
    with open('sp500.csv', 'r') as f:
        reader = csv.DictReader(f)
        for row in reader:
            yield float(row['Adj Close'].strip())

, когда я рассчитываю время таким, какое оно есть, 3e-05s:

print(timeit.timeit(lambda: read_prices(), number=100))

когда я запускаю ту же функцию, но с tuple(...) требуется колоссальное 27s:

print(timeit.timeit(lambda: tuple(read_prices()), number=100))

Это нормально для tuple()? Почему это может быть? Я новичок, поэтому объяснения ELI5 приветствуются:)

Ответы [ 2 ]

4 голосов
/ 30 апреля 2020

Это происходит потому, что read_prices не является функцией - это на самом деле generator. Это из-за ключевого слова yield.

Как объясняется в функциональном программировании HOWTO :

Любая функция, содержащая , дает ключевое слово - функция генератора; это обнаруживается компилятором байт-кода Python, который специально компилирует функцию в результате.

Когда вы вызываете функцию генератора, не возвращает ни единого значения; вместо этого он возвращает объект генератора , который поддерживает протокол итератора.

Итак, что происходит, когда вы запускаете первый read_prices(), это просто создание generator объекта, ожидающего, чтобы ему сообщили до yield элементов.

Во второй версии, tuple(read_prices()), вы создаете объект generator, как и раньше, но tuple() на самом деле истощает его и yield s ВСЕХ элементов одновременно.


Простая демонстрация:

>>> def yielder():
...     yield from [1, 2, 3]
...     
>>> y = yielder()
>>> y
<generator object yielder at 0x2b5604090de0>
>>> next(y)
1
>>> list(y)
[2, 3]
>>> tuple(yielder())
(1, 2, 3)
2 голосов
/ 30 апреля 2020

Это потому, что это генератор read_prices('SP500.csv'), который почти ничего не делает, когда его так называют.

Однако, когда вы делаете это tuple(read_prices('SP500.csv')), он воздействует на генератор и предоставляет значения.

Генератор повторяемый действует:

  • для l oop
  • далее
  • распаковка используя tuple (как вы заметили) или list

Среди других операций, связанных с конструкциями коллекций.

Вот более конкретный пример генератора:

def f():
    print("First value:")
    yield "first"
    print("Second value:")
    yield "second"

Вот оно в действии:

### Nothing prints when called (analogous to your first timeit  without tuple)

In [2]: v = f()

In [3]:

### However when I call `next` the first value is provided:

In [3]: next(v)
First value:
Out[3]: 'first'

## etc, until there is no more values and a "StopIteration` exception is raised:

In [4]: next(v)
Second value:
Out[4]: 'second'

In [5]: next(v)
------------------------------------
...

StopIteration:

## by unpacking using "tuple" the "StopIteration" 
## exception is handled and all the values are provided at once
##  (like your timeit using the tuple):

In [6]: tuple(f())
First value:
Second value:
Out[6]: ('first', 'second')
...