Есть ли встроенный способ получения длины итерации в python?

Например, файлы в Python являются итерабельными - они перебирают строки в файле. Я хочу подсчитать количество строк.

Один быстрый способ сделать это:

lines = len(list(open(fname)))

Однако он загружает весь файл в память (сразу). Это скорее поражает цель итератора (которому требуется только сохранить текущую строку в памяти).

Это не работает:

lines = len(line for line in open(fname))

поскольку генераторы не имеют длины.

Есть ли способ сделать это, не считая функции count?

def count(i):
    c = 0
    for el in i: c += 1
    return c

EDIT: Чтобы понять, я понимаю, что весь файл должен быть прочитан! Я просто не хочу его в памяти сразу =).

Ответ 1

За исключением повторения итерации и подсчета количества итераций, нет. Это делает его итерируемым, а не списком. На самом деле это не проблема даже для python. Посмотрите на классическую структуру данных связанных списков. Поиск длины - это операция O (n), которая включает в себя повторение всего списка, чтобы найти количество элементов.

Как указано выше, вы можете уменьшить свою функцию до:

def count_iterable(i):
    return sum(1 for e in i)

Конечно, если вы определяете свой собственный итерируемый объект, вы всегда можете реализовать __len__ самостоятельно и хранить где-нибудь элемент.

Ответ 2

Если вам нужно количество строк, вы можете сделать это, я не знаю, как лучше это сделать:

line_count = sum(1 for line in open("yourfile.txt"))

Ответ 3

Абсолютно нет, по той простой причине, что итерабельность не гарантируется конечной.

Рассмотрим эту совершенно законную функцию генератора:

def forever():
    while True:
        yield "I will run forever"

Попытка вычислить длину этой функции с помощью len([x for x in forever()]) явно не сработает.

Как вы заметили, большая часть целей итераторов/генераторов заключается в том, чтобы работать с большим набором данных, не загружая все это в память. Тот факт, что вы не можете получить немедленную длину, следует рассматривать как компромисс.

Ответ 4

Я использовал это переопределение в течение некоторого времени:

def len(thingy):
    try:
        return thingy.__len__()
    except AttributeError:
        return sum(1 for item in iter(thingy))

Ответ 5

Пакет cardinality обеспечивает эффективную функцию count() и некоторые связанные функции для подсчета и проверки размера любого итерабельного: http://cardinality.readthedocs.org/

import cardinality

it = some_iterable(...)
print(cardinality.count(it))

Внутри он использует enumerate() и collections.deque(), чтобы переместить всю реальную логику цикла и подсчета на уровень C, что привело к значительному ускорению над циклами for в Python.

Ответ 6

Оказывается, есть реализованное решение для этой общей проблемы. Рассмотрите возможность использования функции ilen() из more_itertools.

more_itertools.ilen(iterable)

Пример печати нескольких строк в файле (мы используем контекстный менеджер with для безопасного обращения к закрывающим файлам):

# Example
import more_itertools

with open("foo.py", "r+") as f:
    print(more_itertools.ilen(f))

# Output: 433

Этот пример возвращает тот же результат, что и решения, представленные ранее для суммирования строк в файле:

# Equivalent code
with open("foo.py", "r+") as f:
    print(sum(1 for line in f))

# Output: 433

Ответ 7

Если мы подумаем об этом, как вы предложите найти количество строк в файле, не читая весь файл для строк новой строки? Конечно, вы можете найти размер файла, и если вы можете гарантировать, что длина строки равна x, вы можете получить количество строк в файле. Но если у вас есть какое-то ограничение, я не вижу, как это может работать вообще. Кроме того, поскольку итерации могут быть бесконечно длинными...

Ответ 8

Я проверил между двумя общими процедурами в каком-то моем коде, который находит, сколько графиков на n вершинах есть, чтобы увидеть, какой метод подсчета элементов сгенерированного списка идет быстрее. У Sage есть генераторные графики (n), которые генерируют все графики на n вершин. Я создал две функции, которые получают длину списка, полученного итератором, двумя разными способами и приурочен к каждому из них (усредняя более 100 тестовых прогонов), используя функцию time.time(). Функции были следующими:

def test_code_list(n):
    l = graphs(n)
    return len(list(l))

и

def test_code_sum(n):
    S = sum(1 for _ in graphs(n))
    return S

Теперь я использую каждый метод

import time

t0 = time.time()
for i in range(100):
    test_code_list(5)
t1 = time.time()

avg_time = (t1-t0)/10

print 'average list method time = %s' % avg_time


t0 = time.time()
for i in range(100):
    test_code_sum(5)
t1 = time.time()

avg_time = (t1-t0)/100

print "average sum method time = %s" % avg_time

средний метод списка time = 0.0391882109642

средняя сумма времени метода = 0,0418473792076

Таким образом, вычисляя количество графиков на n = 5 вершинах таким образом, метод списка немного быстрее (хотя 100 тестовых прогонов не являются большим размером выборки). Но когда я увеличил длину списка, вычисляемого, используя графики на n = 7 вершинах (т.е. Меняя графики (5) на графики (7)), результатом было следующее:

средний метод списка время = 4.14753051996

средняя сумма метод время = 3.96504004002

В этом случае метод суммы был немного быстрее. В общем, эти два метода имеют примерно одинаковую скорость, но разница MIGHT зависит от длины вашего списка (может быть, просто было то, что я только усреднял более 100 тестовых прогонов, что было не очень высоким - навсегда в противном случае).