Получение количества строк в текстовом файле без readlines

Допустим, у меня есть программа, которая использует файл.txt для хранения данных, необходимых для работы. Поскольку в текстовом файле содержится очень большой объем данных (просто с ним), я должен был использовать генератор, а не итератор, чтобы просмотреть данные в нем, чтобы моя программа оставляла как можно больше места. Давайте просто скажем (я знаю, что это не безопасно), что это список имен пользователей. Так что мой код будет выглядеть так (с использованием Python 3.3).

for x in range LenOfFile:
    id = file.readlines(x)
    if username == id:
       validusername = True
       #ask for a password
if validusername == True and validpassword == True:
    pass
else:
    print("Invalid Username")

Предположим, что действительный пароль установлен в True или False, когда я спрашиваю пароль. У меня такой вопрос: поскольку я не хочу занимать всю оперативную память, я не хочу использовать readlines(), чтобы получить все это, и с помощью кода здесь я беру очень небольшое количество оперативной памяти в любой данный момент время. Тем не менее, я не уверен, как я могу получить количество строк в файле (предположим, что я не могу найти количество строк и добавить его по мере поступления новых пользователей). Есть ли способ, которым Python может сделать это, не читая весь файл и не сохраняя его сразу? Я уже пробовала len(), который, очевидно, не работает с текстовыми файлами, но стоило попробовать. Один из способов, который я решил сделать, не слишком хорош, он заключается в том, чтобы просто использовать readlines по одной строке за раз в таком большом текстовом файле, который должен быть меньше, а затем продолжать, когда я получаю ошибку. Я бы предпочел не использовать этот способ, поэтому любые предложения будут оценены.

3 ответа

Решение

Вы можете просто перебирать дескриптор файла напрямую, который затем перебирает его построчно:

for line in file:
    if username == line.strip():
       validusername = True
       break

Кроме того, вы не можете точно сказать, сколько строк в файле, не глядя на него полностью. Вы знаете, насколько большой файл, и вы могли бы сделать некоторые предположения относительно количества символов, например (UTF-8 разрушает это, хотя:P); но вы не знаете, какова длина каждой строки, не видя ее, поэтому вы не знаете, где находятся разрывы строк, и поэтому не можете сказать, сколько строк в общей сложности. Вам все равно придется смотреть на каждый символ один за другим, чтобы увидеть, начинается новая строка или нет.

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

Да, хорошая новость заключается в том, что вы можете найти количество строк в текстовом файле без строк чтения, для строки в файле и т. Д. Более конкретно, в python вы можете использовать байтовые функции, произвольный доступ, параллельные операции и регулярные выражения вместо медленных последовательных обработка текстовой строки. Параллельный текстовый файл, такой как счетчик строк CSV-файла, особенно подходит для устройств SSD, которые имеют быстрый произвольный доступ в сочетании со многими ядрами процессора. Я использовал 16-ядерную систему с SSD для хранения набора данных Higgs Boson в качестве стандартного файла, который вы можете загрузить для тестирования. Более конкретно, здесь приведены фрагменты из рабочего кода, с которых можно начать. Вы можете свободно копировать и использовать, но если вы это сделаете, пожалуйста, процитируйте мою работу, спасибо:

import re
from argparse import ArgumentParser
from multiprocessing import Pool
from itertools import repeat
from os import stat

unitTest = 0
fileName = None
balanceFactor = 2
numProcesses = 1

if __name__ == '__main__':
    argparser = ArgumentParser(description='Parallel text file like CSV file line counter is particularly suitable for SSD which have fast random access')
    argparser.add_argument('--unitTest', default=unitTest, type=int, required=False, help='0:False  1:True.')
    argparser.add_argument('--fileName', default=fileName, required=False, help='')
    argparser.add_argument('--balanceFactor', default=balanceFactor, type=int, required=False, help='integer: 1 or 2 or 3 are typical')
    argparser.add_argument('--numProcesses', default=numProcesses, type=int, required=False, help='integer: 1 or more. Best when matched to number of physical CPU cores.')
    cmd = vars(argparser.parse_args())
    unitTest=cmd['unitTest']
    fileName=cmd['fileName']
    balanceFactor=cmd['balanceFactor']
    numProcesses=cmd['numProcesses']

#Do arithmetic to divide partitions into startbyte, endbyte strips among workers (2 lists of int)
#Best number of strips to use is 2x to 3x number of workers, for workload balancing
#import numpy as np  # long heavy import but i love numpy syntax

    def PartitionDataToWorkers(workers, items, balanceFactor=2):
        strips = balanceFactor * workers
        step = int(round(float(items)/strips))
        startPos = list(range(1, items+1, step))
        if len(startPos) > strips:
            startPos = startPos[:-1]
        endPos = [x + step - 1 for x in startPos]
        endPos[-1] = items
        return startPos, endPos

    def ReadFileSegment(startByte, endByte, fileName, searchChar='\n'):  # counts number of searchChar appearing in the byte range
        with open(fileName, 'r') as f:
            f.seek(startByte-1)  # seek is initially at byte 0 and then moves forward the specified amount, so seek(5) points at the 6th byte.
            bytes = f.read(endByte - startByte + 1)
            cnt = len(re.findall(searchChar, bytes)) # findall with implicit compiling runs just as fast here as re.compile once + re.finditer many times.
        return cnt

    if 0 == unitTest:
        # Run app, not unit tests.
        fileBytes = stat(fileName).st_size  # Read quickly from OS how many bytes are in a text file
        startByte, endByte = PartitionDataToWorkers(workers=numProcesses, items=fileBytes, balanceFactor=balanceFactor)
        p = Pool(numProcesses)
        partialSum = p.starmap(ReadFileSegment, zip(startByte, endByte, repeat(fileName))) # startByte is already a list. fileName is made into a same-length list of duplicates values.
        globalSum = sum(partialSum)
        print(globalSum)
    else: 
        print("Running unit tests") # Bash commands like: head --bytes 96 beer.csv  are how I found the correct values.
        fileName='beer.csv' # byte 98 is a newline
        assert(8==ReadFileSegment(1, 288, fileName))
        assert(1==ReadFileSegment(1, 100, fileName))
        assert(0==ReadFileSegment(1,  97, fileName))
        assert(1==ReadFileSegment(97, 98, fileName))
        assert(1==ReadFileSegment(98, 99, fileName))
        assert(0==ReadFileSegment(99, 99, fileName))
        assert(1==ReadFileSegment(98, 98, fileName))
        assert(0==ReadFileSegment(97, 97, fileName))
        print("OK")

Программа bash wc немного быстрее, но вы хотели чистый Python, и я тоже. Ниже приведены некоторые результаты тестирования производительности. Тем не менее, если вы измените часть этого кода, чтобы использовать Cython или что-то еще, вы могли бы получить еще большую скорость.

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=16 --balanceFactor=2
11000000

real    0m2.257s
user    0m12.088s
sys 0m20.512s

HP-Z820:/mnt/fastssd/fast_file_reader$ time wc -l HIGGS.csv
11000000 HIGGS.csv

real    0m1.820s
user    0m0.364s
sys 0m1.456s


HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=16 --balanceFactor=2
11000000

real    0m2.256s
user    0m10.696s
sys 0m19.952s

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=1 --balanceFactor=1
11000000

real    0m17.380s
user    0m11.124s
sys 0m6.272s

Вывод: скорость хороша для чистой программы на Python по сравнению с программой на Си. Тем не менее, недостаточно использовать чистую программу на Python над программой на языке Си.

Я задавался вопросом, если компиляция регулярного выражения только один раз и передача его всем работникам улучшит скорость. Ответ: Предварительная компиляция Regex НЕ помогает в этом приложении. Я предполагаю, что причина в том, что накладные расходы на сериализацию и создание процессов для всех работников являются доминирующими.

Еще кое-что. Интересно, помогает ли параллельное чтение файлов CSV? Является ли диск узким местом или процессором? Ах да, да, это так. Параллельное чтение файлов работает довольно хорошо. Ну вот и все!

Наука о данных - типичный пример использования чистого питона. Мне нравится использовать записные книжки на python (jupyter), и я люблю хранить весь код в записной книжке, а не использовать bash-скрипты, когда это возможно. Определение количества примеров в наборе данных является обычной потребностью в машинном обучении, когда вам обычно нужно разделить набор данных на примеры обучения, разработки и тестирования.

Набор данных бозона Хиггса: https://archive.ics.uci.edu/ml/datasets/HIGGS

Если вы так сильно хотите количество строк в файле, почему бы вам не использовать len

with open("filename") as f:
    num = len(f.readlines())
Другие вопросы по тегам