text-files файле - Как дешево получить количество строк в Python?




выбрать строки (25)

Вот что я использую, кажется довольно чистым:

import subprocess

def count_file_lines(file_path):
    """
    Counts the number of lines in a file using wc utility.
    :param file_path: path to file
    :return: int, no of lines
    """
    num = subprocess.check_output(['wc', '-l', file_path])
    num = num.split(' ')
    return int(num[0])

UPDATE: это немного быстрее, чем использование чистого python, но за счет использования памяти. Подпроцесс разветвит новый процесс с тем же объемом памяти, что и родительский процесс, когда он выполнит вашу команду.

Мне нужно получить количество строк большого файла (сотни тысяч строк) в python. Каков наиболее эффективный способ как с точки зрения памяти, так и по времени?

На данный момент я делаю:

def file_len(fname):
    with open(fname) as f:
        for i, l in enumerate(f):
            pass
    return i + 1

можно ли сделать лучше?


Одна строка, возможно довольно быстро:

num_lines = sum(1 for line in open('myfile.txt'))

Я получил небольшое (4-8%) улучшение с этой версией, которая повторно использует постоянный буфер, поэтому он должен избегать любой памяти или накладных расходов GC:

lines = 0
buffer = bytearray(2048)
with open(filename) as f:
  while f.readinto(buffer) > 0:
      lines += buffer.count('\n')

Вы можете играть с размером буфера и, возможно, немного улучшиться.


print open('file.txt', 'r').read().count("\n") + 1

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

with open(filename) as f:
   return len(list(f))

это более кратким, чем ваш явный цикл, и избегает enumerate .


Это самая быстрая вещь, которую я нашел, используя чистый python. Вы можете использовать любой объем памяти, который вы хотите, установив буфер, хотя 2 ** 16 кажется приятным пятном на моем компьютере.

from functools import partial

buffer=2**16
with open(myfile) as f:
        print sum(x.count('\n') for x in iter(partial(f.read,buffer), ''))

Я нашел ответ здесь. Почему чтение строк из stdin происходит намного медленнее на C ++, чем на Python? и немного изменил его. Его очень хорошо читать, чтобы понять, как быстро считать строки, хотя wc -l все еще примерно на 75% быстрее, чем что-либо еще.


Вот программа python для использования библиотеки многопроцессорности для распределения подсчета строк по машинам / ядрам. Мой тест улучшает подсчет 20-миллионного файла линии с 26 секунд до 7 секунд, используя 8-ядерный сервер Windows 64. Примечание: не использование карт памяти делает вещи намного медленнее.

import multiprocessing, sys, time, os, mmap
import logging, logging.handlers

def init_logger(pid):
    console_format = 'P{0} %(levelname)s %(message)s'.format(pid)
    logger = logging.getLogger()  # New logger at root level
    logger.setLevel( logging.INFO )
    logger.handlers.append( logging.StreamHandler() )
    logger.handlers[0].setFormatter( logging.Formatter( console_format, '%d/%m/%y %H:%M:%S' ) )

def getFileLineCount( queues, pid, processes, file1 ):
    init_logger(pid)
    logging.info( 'start' )

    physical_file = open(file1, "r")
    #  mmap.mmap(fileno, length[, tagname[, access[, offset]]]

    m1 = mmap.mmap( physical_file.fileno(), 0, access=mmap.ACCESS_READ )

    #work out file size to divide up line counting

    fSize = os.stat(file1).st_size
    chunk = (fSize / processes) + 1

    lines = 0

    #get where I start and stop
    _seedStart = chunk * (pid)
    _seekEnd = chunk * (pid+1)
    seekStart = int(_seedStart)
    seekEnd = int(_seekEnd)

    if seekEnd < int(_seekEnd + 1):
        seekEnd += 1

    if _seedStart < int(seekStart + 1):
        seekStart += 1

    if seekEnd > fSize:
        seekEnd = fSize

    #find where to start
    if pid > 0:
        m1.seek( seekStart )
        #read next line
        l1 = m1.readline()  # need to use readline with memory mapped files
        seekStart = m1.tell()

    #tell previous rank my seek start to make their seek end

    if pid > 0:
        queues[pid-1].put( seekStart )
    if pid < processes-1:
        seekEnd = queues[pid].get()

    m1.seek( seekStart )
    l1 = m1.readline()

    while len(l1) > 0:
        lines += 1
        l1 = m1.readline()
        if m1.tell() > seekEnd or len(l1) == 0:
            break

    logging.info( 'done' )
    # add up the results
    if pid == 0:
        for p in range(1,processes):
            lines += queues[0].get()
        queues[0].put(lines) # the total lines counted
    else:
        queues[0].put(lines)

    m1.close()
    physical_file.close()

if __name__ == '__main__':
    init_logger( 'main' )
    if len(sys.argv) > 1:
        file_name = sys.argv[1]
    else:
        logging.fatal( 'parameters required: file-name [processes]' )
        exit()

    t = time.time()
    processes = multiprocessing.cpu_count()
    if len(sys.argv) > 2:
        processes = int(sys.argv[2])
    queues=[] # a queue for each process
    for pid in range(processes):
        queues.append( multiprocessing.Queue() )
    jobs=[]
    prev_pipe = 0
    for pid in range(processes):
        p = multiprocessing.Process( target = getFileLineCount, args=(queues, pid, processes, file_name,) )
        p.start()
        jobs.append(p)

    jobs[0].join() #wait for counting to finish
    lines = queues[0].get()

    logging.info( 'finished {} Lines:{}'.format( time.time() - t, lines ) )

Так же:

lines = 0
with open(path) as f:
    for line in f:
        lines += 1

однострочное решение

import os
os.system("wc -l  filename")  

мой фрагмент

os.system ('wc -l * .txt')

0 bar.txt
1000 command.txt
3 test_file.txt
1003 total

Почему бы не прочитать первые 100 и последние 100 строк и оценить среднюю длину линии, а затем разделить общий размер файла на эти числа? Если вам не нужно точное значение, это может сработать.


Я считаю, что файл с отображением памяти будет самым быстрым решением. Я попробовал четыре функции: функцию, отправленную OP ( opcount ); простая итерация по строкам в файле ( simplecount ); readline с отображаемой памятью (mmap) ( mapcount ); и решение для считывания буфера, предложенное Николаем Харечко ( bufcount ).

Я запускал каждую функцию пять раз и вычислял среднее время выполнения для текстового файла в 1,2 млн. Строк.

Windows XP, Python 2.5, RAM 2 ГБ, процессор AMD 2 ГГц

Вот мои результаты:

mapcount : 0.465599966049
simplecount : 0.756399965286
bufcount : 0.546800041199
opcount : 0.718600034714

Изменить : номера для Python 2.6:

mapcount : 0.471799945831
simplecount : 0.634400033951
bufcount : 0.468800067902
opcount : 0.602999973297

Таким образом, стратегия чтения буфера, по-видимому, является самой быстрой для Windows / Python 2.6

Вот код:

from __future__ import with_statement
import time
import mmap
import random
from collections import defaultdict

def mapcount(filename):
    f = open(filename, "r+")
    buf = mmap.mmap(f.fileno(), 0)
    lines = 0
    readline = buf.readline
    while readline():
        lines += 1
    return lines

def simplecount(filename):
    lines = 0
    for line in open(filename):
        lines += 1
    return lines

def bufcount(filename):
    f = open(filename)                  
    lines = 0
    buf_size = 1024 * 1024
    read_f = f.read # loop optimization

    buf = read_f(buf_size)
    while buf:
        lines += buf.count('\n')
        buf = read_f(buf_size)

    return lines

def opcount(fname):
    with open(fname) as f:
        for i, l in enumerate(f):
            pass
    return i + 1


counts = defaultdict(list)

for i in range(5):
    for func in [mapcount, simplecount, bufcount, opcount]:
        start_time = time.time()
        assert func("big_file.txt") == 1209138
        counts[func].append(time.time() - start_time)

for key, vals in counts.items():
    print key.__name__, ":", sum(vals) / float(len(vals))

Однострочное решение bash похоже на этот ответ , используя современную функцию subprocess.check_output :

def line_count(file):
    return int(subprocess.check_output('wc -l {}'.format(file), shell=True).split()[0])

Что касается меня, этот вариант будет самым быстрым:

#!/usr/bin/env python

def main():
    f = open('filename')                  
    lines = 0
    buf_size = 1024 * 1024
    read_f = f.read # loop optimization

    buf = read_f(buf_size)
    while buf:
        lines += buf.count('\n')
        buf = read_f(buf_size)

    print lines

if __name__ == '__main__':
    main()

причины: буферизация быстрее, чем чтение строки за строкой, и string.count также очень быстро


Вы можете выполнить подпроцесс и запустить wc -l filename

import subprocess

def file_len(fname):
    p = subprocess.Popen(['wc', '-l', fname], stdout=subprocess.PIPE, 
                                              stderr=subprocess.PIPE)
    result, err = p.communicate()
    if p.returncode != 0:
        raise IOError(err)
    return int(result.strip().split()[0])

Ответ Кайла

num_lines = sum(1 for line in open('my_file.txt'))

вероятно, лучше, альтернатива для этого

num_lines =  len(open('my_file.txt').read().splitlines())

Вот сравнение эффективности обоих

In [20]: timeit sum(1 for line in open('Charts.ipynb'))
100000 loops, best of 3: 9.79 µs per loop

In [21]: timeit len(open('Charts.ipynb').read().splitlines())
100000 loops, best of 3: 12 µs per loop

Как насчет этого?

import fileinput
import sys

counter=0
for line in fileinput.input([sys.argv[1]]):
    counter+=1

fileinput.close()
print counter

Я бы использовал методы readlines объектов Python, как readlines ниже:

with open(input_file) as foo:
    lines = len(foo.readlines())

Это открывает файл, создает список строк в файле, подсчитывает длину списка, сохраняет это переменной и снова закрывает файл.


Этот код короче и яснее. Это, наверное, лучший способ:

num_lines = open('yourfile.ext').read().count('\n')

def count_text_file_lines(path):
    with open(path, 'rt') as file:
        line_count = sum(1 for _line in file)
    return line_count

Я изменил буферный случай следующим образом:

def CountLines(filename):
    f = open(filename)
    try:
        lines = 1
        buf_size = 1024 * 1024
        read_f = f.read # loop optimization
        buf = read_f(buf_size)

        # Empty file
        if not buf:
            return 0

        while buf:
            lines += buf.count('\n')
            buf = read_f(buf_size)

        return lines
    finally:
        f.close()

Теперь также пустые файлы и последняя строка (без \ n) подсчитываются.


Если вы хотите получить дешевое количество строк в Python в Linux, я рекомендую этот метод:

import os
print os.popen("wc -l file_path").readline().split()[0]

file_path может быть как абстрактным, так и относительным путем. Надеюсь, это поможет.


def file_len(full_path):
  """ Count number of lines in a file."""
  f = open(full_path)
  nr_of_lines = sum(1 for line in f)
  f.close()
  return nr_of_lines

Чтобы завершить описанные выше методы, я попробовал вариант с модулем fileinput:

import fileinput as fi   
def filecount(fname):
        for line in fi.input(fname):
            pass
        return fi.lineno()

И передал файл со строкой длиной 60 мил ко всем вышеперечисленным методам:

mapcount : 6.1331050396
simplecount : 4.588793993
opcount : 4.42918205261
filecount : 43.2780818939
bufcount : 0.170812129974

Для меня немного неожиданно, что fileinput - это то, что плохо и масштабируется намного хуже, чем все другие методы ...


Как насчет этого

def file_len(fname):
  counts = itertools.count()
  with open(fname) as f: 
    for _ in f: counts.next()
  return counts.next()

Что такое метаклассы? Для чего вы их используете?

TLDR: метаклас создает экземпляр и определяет поведение для класса, как экземпляр класса, и определяет поведение для экземпляра.

псевдокод:

>>> Class(...)
instance

Вышеприведенное должно выглядеть знакомым. Ну, откуда появился Class ? Это пример метакласса (также псевдокод):

>>> Metaclass(...)
Class

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

>>> type('Foo', (object,), {}) # requires a name, bases, and a namespace
<class '__main__.Foo'>

Положив это по-другому

  • Класс относится к экземпляру, поскольку метакласс относится к классу.

    Когда мы создаем экземпляр объекта, мы получаем экземпляр:

    >>> object()                          # instantiation of class
    <object object at 0x7f9069b4e0b0>     # instance
    

    Аналогично, когда мы определяем класс явно с метаклассом по умолчанию, type его:

    >>> type('Object', (object,), {})     # instantiation of metaclass
    <class '__main__.Object'>             # instance
    
  • Другими словами, класс является экземпляром метакласса:

    >>> isinstance(object, type)
    True
    
  • Положите третий способ, метакласс - класс класса.

    >>> type(object) == type
    True
    >>> object.__class__
    <class 'type'>
    

Когда вы пишете определение класса, и Python выполняет его, он использует метакласс для создания экземпляра объекта класса (который, в свою очередь, будет использоваться для создания экземпляров этого класса).

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

Для чего их можно использовать? Из docs :

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

Тем не менее, как правило, пользователям рекомендуется избегать использования метаклассов, если это абсолютно необходимо.

Вы используете метакласс каждый раз, когда вы создаете класс:

Например, когда вы пишете определение класса, например,

class Foo(object): 
    'demo'

Вы создаете экземпляр объекта класса.

>>> Foo
<class '__main__.Foo'>
>>> isinstance(Foo, type), isinstance(Foo, object)
(True, True)

Это то же самое, что и функционально вызывающий type с соответствующими аргументами и присваивание результата переменной этого имени:

name = 'Foo'
bases = (object,)
namespace = {'__doc__': 'demo'}
Foo = type(name, bases, namespace)

Обратите внимание: некоторые вещи автоматически добавляются в __dict__ , т. __dict__ имен:

>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>, 
'__module__': '__main__', '__weakref__': <attribute '__weakref__' 
of 'Foo' objects>, '__doc__': 'demo'})

Метаклассом объекта, который мы создали, в обоих случаях является type .

(Замечание по содержимому класса __dict__ : __module__ существует, потому что классы должны знать, где они определены, и __dict__ и __weakref__ есть, потому что мы не определяем __slots__ - если мы определим __slots__ мы сохраним немного в случаях, поскольку мы можем запретить __dict__ и __weakref__ , исключив их. Например:

>>> Baz = type('Bar', (object,), {'__doc__': 'demo', '__slots__': ()})
>>> Baz.__dict__
mappingproxy({'__doc__': 'demo', '__slots__': (), '__module__': '__main__'})

... но я отвлекся.)

Мы можем расширить type как и любое другое определение класса:

Вот __repr__ классов по умолчанию __repr__ :

>>> Foo
<class '__main__.Foo'>

Одной из самых ценных вещей, которые мы можем сделать по умолчанию при написании объекта Python, является предоставление ему хорошего __repr__ . Когда мы вызываем help(repr) мы узнаем, что есть хороший тест для __repr__ который также требует проверки на равенство - obj == eval(repr(obj)) . Следующая простая реализация __repr__ и __eq__ для экземпляров классов нашего класса классов предоставляет нам демонстрацию, которая может улучшить класс по умолчанию __repr__ :

class Type(type):
    def __repr__(cls):
        """
        >>> Baz
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        >>> eval(repr(Baz))
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        """
        metaname = type(cls).__name__
        name = cls.__name__
        parents = ', '.join(b.__name__ for b in cls.__bases__)
        if parents:
            parents += ','
        namespace = ', '.join(': '.join(
          (repr(k), repr(v) if not isinstance(v, type) else v.__name__))
               for k, v in cls.__dict__.items())
        return '{0}(\'{1}\', ({2}), {{{3}}})'.format(metaname, name, parents, namespace)
    def __eq__(cls, other):
        """
        >>> Baz == eval(repr(Baz))
        True            
        """
        return (cls.__name__, cls.__bases__, cls.__dict__) == (
                other.__name__, other.__bases__, other.__dict__)

Итак, теперь, когда мы создаем объект с этим метаклассом, __repr__эхо в командной строке обеспечивает гораздо менее уродливое зрелище, чем значение по умолчанию:

>>> class Bar(object): pass
>>> Baz = Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})

С хорошим, __repr__определенным для экземпляра класса, у нас есть более сильная способность отлаживать наш код. Тем не менее, дальнейшая проверка с eval(repr(Class))вероятностью маловероятна (поскольку функциям было бы совершенно невозможно оценить их дефолт __repr__).

Ожидаемое использование: __prepare__пространство имен

Если, например, мы хотим знать, в каком порядке создаются методы класса, мы могли бы предоставить упорядоченный dict как пространство имен класса. Мы сделали бы это, с помощью __prepare__чего пространство имен dict для класса, если оно реализовано в Python 3 :

from collections import OrderedDict

class OrderedType(Type):
    @classmethod
    def __prepare__(metacls, name, bases, **kwargs):
        return OrderedDict()
    def __new__(cls, name, bases, namespace, **kwargs):
        result = Type.__new__(cls, name, bases, dict(namespace))
        result.members = tuple(namespace)
        return result

И использование:

class OrderedMethodsObject(object, metaclass=OrderedType):
    def method1(self): pass
    def method2(self): pass
    def method3(self): pass
    def method4(self): pass

И теперь у нас есть запись порядка, в котором были созданы эти методы (и другие атрибуты класса):

>>> OrderedMethodsObject.members
('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4')

Обратите внимание, что этот пример был адаптирован из docs - это новое перечисление в стандартной библиотеке .

Так что мы сделали, это создать метакласс, создав класс. Мы также можем рассматривать метакласс как и любой другой класс. Он имеет порядок разрешения метода:

>>> inspect.getmro(OrderedType)
(<class '__main__.OrderedType'>, <class '__main__.Type'>, <class 'type'>, <class 'object'>)

И он имеет приблизительно правильные значения repr(которые мы больше не можем использовать, если мы не сможем найти способ представлять наши функции.):

>>> OrderedMethodsObject
OrderedType('OrderedMethodsObject', (object,), {'method1': <function OrderedMethodsObject.method1 at 0x0000000002DB01E0>, 'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': <function OrderedMet
hodsObject.method3 at 0x0000000002DB02F0>, 'method2': <function OrderedMethodsObject.method2 at 0x0000000002DB0268>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'OrderedMethodsObject' objects>, '__doc__': None, '__d
ict__': <attribute '__dict__' of 'OrderedMethodsObject' objects>, 'method4': <function OrderedMethodsObject.method4 at 0x0000000002DB0378>})




python text-files line-count