วิธีรับจำนวนบรรทัดของไฟล์ขนาดใหญ่ใน Python อย่างถูกต้อง?


1010

ฉันต้องได้รับการนับบรรทัดของไฟล์ขนาดใหญ่ (หลายร้อยหลายพันบรรทัด) ในหลาม อะไรคือวิธีที่มีประสิทธิภาพมากที่สุดทั้งในเรื่องของความจำและเวลา?

ในขณะนี้ฉันทำ:

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

เป็นไปได้ไหมที่จะทำได้ดีกว่านี้?


7
คุณต้องการการนับจำนวนบรรทัดที่แน่นอนหรือการประมาณนั้นเพียงพอหรือไม่
pico

43
ฉันจะเพิ่ม i = -1 ก่อนสำหรับลูปเนื่องจากรหัสนี้ใช้ไม่ได้กับไฟล์ว่าง
Maciek Sawicki

12
@ ข้อสังเกต: ฉันเดิมพัน pico กำลังคิดหาขนาดไฟล์ (ด้วย find (0,2) หรือ equiv) หารด้วยความยาวบรรทัดโดยประมาณ คุณสามารถอ่านสองสามบรรทัดที่จุดเริ่มต้นเพื่อคาดเดาความยาวบรรทัดโดยเฉลี่ย
แอนน์

32
enumerate(f, 1)และคลองi + 1?
Ian Mackinnon

4
@IanMackinnon ใช้งานได้กับไฟล์ว่างเปล่า แต่คุณต้องเริ่มต้นiเป็น0ก่อน for-loop
scai

คำตอบ:


356

คุณไม่สามารถรับได้ดีไปกว่านี้อีกแล้ว

ท้ายที่สุดโซลูชันใด ๆ จะต้องอ่านไฟล์ทั้งหมดหาจำนวนที่\nคุณมีและส่งคืนผลลัพธ์นั้น

คุณมีวิธีที่ดีกว่าในการทำเช่นนั้นโดยไม่ต้องอ่านไฟล์ทั้งหมดหรือไม่? ไม่แน่ใจ ... ทางออกที่ดีที่สุดจะเป็น I / O-bound ที่ดีที่สุดที่คุณสามารถทำได้คือให้แน่ใจว่าคุณไม่ได้ใช้หน่วยความจำที่ไม่จำเป็น แต่ดูเหมือนว่าคุณจะได้รับการคุ้มครอง


7
แน่นอนแม้แต่ WC กำลังอ่านไฟล์อยู่ แต่ใน C และมันอาจจะเหมาะสมที่สุด
aflafur Waage

6
เท่าที่ฉันเข้าใจไฟล์ Python IO จะทำผ่าน C เช่นกัน docs.python.org/library/stdtypes.html#file-objects
Tomalak

9
@ Tomalak นั่นคือปลาเฮอริ่งแดง ในขณะที่ python และ wc อาจจะออก syscalls เดียวกัน python มี opcode ส่งค่าโสหุ้ยที่ wc ไม่มี
bobpoekert

4
คุณสามารถประมาณจำนวนเส้นโดยการสุ่มตัวอย่าง มันสามารถเร็วกว่าเป็นพันเท่า ดู: documentroot.com/2011/02/…
Erik Aronesty

4
คำตอบอื่น ๆ ดูเหมือนจะบ่งบอกว่าคำตอบที่เป็นหมวดหมู่นี้ผิดและควรลบทิ้งแทนที่จะเก็บไว้เป็นที่ยอมรับ
Skippy le Grand Gourou

624

หนึ่งบรรทัดอาจจะค่อนข้างเร็ว:

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

8
มันคล้ายกับผลรวม (ลำดับ 1) ทุกบรรทัดนับเป็น 1 >>> [1 สำหรับบรรทัดในช่วง (10)] [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] >>> ผลรวม (1 สำหรับบรรทัดที่อยู่ในช่วง (10)) 10 >>>
James Sapam

4
num_lines = sum (1 สำหรับบรรทัดที่เปิดอยู่ ('myfile.txt') ถ้า line.rstrip ()) สำหรับตัวกรองบรรทัดว่าง
Honghe.Wu

61
เมื่อเราเปิดไฟล์สิ่งนี้จะถูกปิดโดยอัตโนมัติเมื่อเราวนองค์ประกอบทั้งหมดหรือไม่ จำเป็นต้อง 'ปิด ()' หรือไม่ ฉันคิดว่าเราไม่สามารถใช้ 'with open ()' ในคำสั่งสั้น ๆ นี้ใช่ไหม
Mannaggia

16
@ Managgia คุณถูกต้องมันจะดีกว่าถ้าใช้ 'เปิด (ชื่อไฟล์)' เพื่อให้แน่ใจว่าไฟล์ปิดเมื่อเสร็จแล้วและยิ่งดีกว่าคือทำสิ่งนี้ภายในบล็อกแบบลองยกเว้นซึ่งข้อยกเว้นและ IOError นั้นถูกโยนทิ้งหาก ไม่สามารถเปิดไฟล์ได้
BoltzmannBrain

17
อีกสิ่งที่ควรทราบ: นี่คือ ~ 0.04-0.05 วินาทีช้ากว่าปัญหาเดิมที่ให้ไว้ในไฟล์ข้อความ 300,000 บรรทัด
andrew

202

ฉันเชื่อว่าไฟล์ที่แมปหน่วยความจำจะเป็นวิธีแก้ปัญหาที่เร็วที่สุด ฉันลองสี่ฟังก์ชั่น: ฟังก์ชั่นที่โพสต์โดย OP ( opcount); การทำซ้ำแบบง่ายเหนือบรรทัดในไฟล์ ( simplecount); readline พร้อมหน่วยความจำที่แมปยื่น (mmap) ( mapcount); และโซลูชันการอ่านบัฟเฟอร์ที่นำเสนอโดย Mykola Kharechko ( bufcount)

ฉันวิ่งแต่ละฟังก์ชั่นห้าครั้งและคำนวณเวลาทำงานเฉลี่ยสำหรับไฟล์ข้อความ 1.2 ล้านบรรทัด

Windows XP, Python 2.5, 2GB RAM, โปรเซสเซอร์ 2 GHz AMD

นี่คือผลลัพธ์ของฉัน:

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))

1
ไฟล์ที่แมปหน่วยความจำทั้งหมดไม่ได้โหลดเข้าสู่หน่วยความจำ คุณได้รับพื้นที่หน่วยความจำเสมือนที่ระบบปฏิบัติการสลับเข้าและออกจาก RAM ตามต้องการ นี่คือวิธีจัดการกับ Windows: msdn.microsoft.com/en-us/library/ms810613.aspx
Ryan Ginstrom

1
ขออภัยนี่เป็นข้อมูลอ้างอิงทั่วไปเพิ่มเติมเกี่ยวกับไฟล์ที่แม็พหน่วยความจำ: en.wikipedia.org/wiki/Memory-mapped_fileและขอบคุณสำหรับการโหวต :)
Ryan Ginstrom

1
แม้ว่ามันจะเป็นเพียงหน่วยความจำเสมือน แต่เป็นสิ่งที่ จำกัด แนวทางนี้และดังนั้นจึงไม่สามารถทำงานกับไฟล์ขนาดใหญ่ได้ ฉันได้ลองกับไฟล์ ~ 1.2 Gb ที่มีมากกว่า 10 mln แล้ว บรรทัด (ตามที่ได้รับด้วย wc -l) และเพิ่งได้รับ WindowsError: [ข้อผิดพลาด 8] ที่เก็บข้อมูลไม่เพียงพอที่จะประมวลผลคำสั่งนี้ แน่นอนนี่เป็นกรณีที่มีขอบ
SilentGhost

6
+1 สำหรับข้อมูลเวลาจริง เรารู้หรือไม่ว่าขนาดบัฟเฟอร์ที่ 1024 * 1024 เหมาะสมที่สุดหรือมีขนาดบัฟเฟอร์ที่ดีกว่า
Kiv

28
ดูเหมือนว่าwccount()จะเป็นgist.github.com/0ac760859e614cd03652 ที่
jfs

133

ฉันต้องโพสต์คำถามนี้ด้วยคำถามที่คล้ายกันจนกระทั่งคะแนนชื่อเสียงของฉันเพิ่มขึ้นเล็กน้อย (ขอบคุณผู้ที่กระแทกฉัน!)

โซลูชันทั้งหมดเหล่านี้ไม่สนใจวิธีหนึ่งในการทำให้การดำเนินการนี้เร็วขึ้นอย่างมากนั่นคือโดยใช้อินเทอร์เฟซที่ไม่มีบัฟเฟอร์ (ดิบ) ใช้การทดสอบแบบไบต์และการบัฟเฟอร์ของคุณเอง (สิ่งนี้ใช้ได้เฉพาะใน Python 3 เท่านั้นใน Python 2 อินเทอร์เฟซแบบดิบอาจใช้หรือไม่ใช้เป็นค่าเริ่มต้น แต่ใน Python 3 คุณจะเริ่มต้นเป็น Unicode)

การใช้เครื่องมือกำหนดเวลาเวอร์ชันที่แก้ไขแล้วฉันเชื่อว่ารหัสต่อไปนี้เร็วกว่า (และ pythonic เล็กน้อย) มากกว่าโซลูชันที่เสนอ:

def rawcount(filename):
    f = open(filename, 'rb')
    lines = 0
    buf_size = 1024 * 1024
    read_f = f.raw.read

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

    return lines

การใช้ฟังก์ชั่นเครื่องกำเนิดไฟฟ้าแยกต่างหากจะทำให้ smidge ทำงานได้เร็วขึ้น:

def _make_gen(reader):
    b = reader(1024 * 1024)
    while b:
        yield b
        b = reader(1024*1024)

def rawgencount(filename):
    f = open(filename, 'rb')
    f_gen = _make_gen(f.raw.read)
    return sum( buf.count(b'\n') for buf in f_gen )

สามารถทำได้อย่างสมบูรณ์ด้วยนิพจน์กำเนิดในบรรทัดโดยใช้ itertools แต่มันก็ดูแปลก ๆ :

from itertools import (takewhile,repeat)

def rawincount(filename):
    f = open(filename, 'rb')
    bufgen = takewhile(lambda x: x, (f.raw.read(1024*1024) for _ in repeat(None)))
    return sum( buf.count(b'\n') for buf in bufgen )

นี่คือการกำหนดเวลาของฉัน:

function      average, s  min, s   ratio
rawincount        0.0043  0.0041   1.00
rawgencount       0.0044  0.0042   1.01
rawcount          0.0048  0.0045   1.09
bufcount          0.008   0.0068   1.64
wccount           0.01    0.0097   2.35
itercount         0.014   0.014    3.41
opcount           0.02    0.02     4.83
kylecount         0.021   0.021    5.05
simplecount       0.022   0.022    5.25
mapcount          0.037   0.031    7.46

20
ฉันทำงานกับไฟล์ 100Gb + และ rawgencounts ของคุณเป็นทางออกเดียวที่ฉันได้เห็น ขอบคุณ!
soungalo

1
อยู่wccountในตารางนี้สำหรับwcเครื่องมือเชลล์ subprocess ?
Anentropic

1
พบสิ่งนี้ในความคิดเห็นอื่นฉันเดาว่ามันเป็นgist.github.com/zed/0ac760859e614cd03652
Anentropic

3
ขอบคุณ @ michael-bacon มันเป็นทางออกที่ดีจริงๆ คุณสามารถทำให้rawincountการแก้ปัญหาแปลกมองน้อยโดยใช้bufgen = iter(partial(f.raw.read, 1024*1024), b'')แทนการรวมและtakewhile repeat
ปีเตอร์เอช

1
โอ้ฟังก์ชั่นบางส่วนใช่ว่าเป็นบิดเล็กน้อยดี นอกจากนี้ฉันคิดว่า 1024 * 1024 จะได้รับการรวมกันโดยล่ามและถือว่าเป็นค่าคงที่ แต่ที่อยู่ในลางสังหรณ์ไม่ใช่เอกสาร
Michael Bacon

90

คุณสามารถดำเนินการกระบวนการย่อยและเรียกใช้ 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])

6
Windows รุ่นนี้จะเป็นอะไร?
SilentGhost

1
คุณสามารถอ้างถึงคำถาม SO นี้เกี่ยวกับสิ่งนั้น stackoverflow.com/questions/247234/…
Ólafur Waage

7
ที่จริงแล้วในกรณีของฉัน (Mac OS X) ใช้เวลา 0.13 วินาทีเทียบกับ 0.5s สำหรับการนับจำนวนบรรทัด "สำหรับ x ในไฟล์ (... )" ผลิตเทียบกับ 1.0s นับการเรียกซ้ำไปยัง str.find หรือ mmap.find . (ไฟล์ที่ผมใช้ในการทดสอบนี้มี 1.3 ล้านเส้น.)
Bendin

1
ไม่จำเป็นต้องเกี่ยวข้องกับเชลล์ในเรื่องนั้น คำตอบที่แก้ไขและเพิ่มรหัสตัวอย่าง;
nosklo

2
ไม่ข้ามแพลตฟอร์ม
e-info128

42

นี่คือโปรแกรมไพ ธ อนที่ใช้ไลบรารีมัลติโพรเซสซิงเพื่อกระจายการนับบรรทัดในเครื่อง / แกนหลัก การทดสอบของฉันปรับปรุงการนับไฟล์บรรทัด 20 ล้านจาก 26 วินาทีเป็น 7 วินาทีโดยใช้เซิร์ฟเวอร์ 8 core 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 ) )

สิ่งนี้ทำงานกับไฟล์ที่ใหญ่กว่าหน่วยความจำหลักได้อย่างไร เช่นไฟล์ 20GB บนระบบที่มี 4GB RAM และ 2 คอร์
Brian Minton

ยากที่จะทดสอบตอนนี้ แต่ฉันคิดว่ามันจะหน้าไฟล์เข้าและออก
Martlark

5
นี่เป็นโค้ดที่เรียบร้อย ฉันรู้สึกประหลาดใจที่พบว่าใช้โปรเซสเซอร์หลายตัวเร็วขึ้น ฉันคิดว่า IO จะเป็นคอขวด ในเวอร์ชัน Python ที่เก่ากว่าบรรทัดที่ 21 ต้องการ int () เช่น chunk = int ((fSize / กระบวนการ)) + 1
Karl Henselin

มันจะโหลดไฟล์ทั้งหมดลงในหน่วยความจำหรือไม่? แล้วไฟที่ใหญ่กว่าขนาดไหนใหญ่กว่า RAM ในคอมพิวเตอร์?
pelos

ไฟล์ถูกแมปเข้ากับหน่วยความจำเสมือนดังนั้นขนาดของไฟล์และจำนวนหน่วยความจำจริงจึงมักจะไม่ถูก จำกัด
Martlark

17

โซลูชันทุบตีแบบบรรทัดเดียวคล้ายกับคำตอบนี้โดยใช้subprocess.check_outputฟังก์ชั่นที่ทันสมัย:

def line_count(filename):
    return int(subprocess.check_output(['wc', '-l', filename]).split()[0])

คำตอบนี้ควรได้รับการโหวตสูงสุดในเธรดนี้สำหรับผู้ใช้ Linux / Unix แม้จะมีการตั้งค่าส่วนใหญ่ในโซลูชันข้ามแพลตฟอร์มนี้เป็นวิธีที่ยอดเยี่ยมบน Linux / Unix สำหรับไฟล์ csv 184 ล้านบรรทัดที่ฉันต้องเก็บตัวอย่างจากข้อมูลมันให้รันไทม์ที่ดีที่สุด โซลูชั่นหลามบริสุทธิ์อื่น ๆ ใช้เวลาโดยเฉลี่ยมากกว่า 100 วินาทีในขณะที่การเรียกwc -lใช้กระบวนการย่อยใช้เวลา ~ 5 วินาที
Shan Dou

shell=Trueไม่ดีต่อความปลอดภัยควรหลีกเลี่ยง
Alexey Vazhnov

จุดยุติธรรม, แก้ไข
1 ''

15

ฉันจะใช้วิธีการวัตถุไฟล์ของงูreadlinesใหญ่ดังนี้

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

สิ่งนี้จะเปิดไฟล์สร้างรายการของบรรทัดในไฟล์นับความยาวของรายการบันทึกลงในตัวแปรและปิดไฟล์อีกครั้ง


6
แม้ว่านี่เป็นวิธีแรก ๆ ที่นึกถึง แต่ก็อาจไม่ได้มีประสิทธิภาพหน่วยความจำมากนักโดยเฉพาะอย่างยิ่งหากการนับบรรทัดในไฟล์สูงสุด 10 GB (เช่นเดียวกับฉัน) ซึ่งเป็นข้อเสียที่น่าสังเกต
Steen Schütt

@TimeSheep นี่เป็นปัญหาสำหรับไฟล์ที่มีขนาดเล็กจำนวนมาก (พูดพันล้านบรรทัด) หรือไฟล์ที่มีเส้นยาวมาก (เช่นกิกะไบต์ต่อบรรทัด) หรือไม่?
เบิร์ต

เหตุผลที่ฉันถามก็คือดูเหมือนว่าคอมไพเลอร์ควรจะเพิ่มประสิทธิภาพนี้ออกไปโดยไม่สร้างรายการกลาง
เบิร์ต

@dmityugov ต่อเอกสาร Python xreadlinesถูกเลิกใช้แล้วตั้งแต่ 2.3 เนื่องจากเพิ่งส่งคืนตัววนซ้ำ for line in fileเป็นการทดแทนที่ระบุไว้ ดู: docs.python.org/2/library/stdtypes.html#file.xreadlines
Kumba

12
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

12

นี่คือสิ่งที่ฉันใช้ดูเหมือนสะอาดดี:

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 แท้ แต่มีค่าใช้จ่ายในการใช้หน่วยความจำ กระบวนการย่อยจะแยกกระบวนการใหม่โดยมีหน่วยความจำรอยเดียวกับกระบวนการหลักในขณะที่ประมวลผลคำสั่งของคุณ


1
เช่นเดียวกับบันทึกย่อด้านนี้จะไม่ทำงานบน Windows แน่นอน
Bram Vanroy

utils หลักเห็นได้ชัดให้ "ห้องสุขา" สำหรับ Windows stackoverflow.com/questions/247234/... นอกจากนี้คุณยังสามารถใช้ linux VM ในกล่องหน้าต่างของคุณหากรหัสของคุณจะจบลงด้วยการทำงานใน linux ใน prod
radtek

หรือ WSL ควรได้รับคำแนะนำจาก VM ใด ๆ หากสิ่งนี้เป็นสิ่งเดียวที่คุณทำ :-)
Bram Vanroy

ใช่ว่าใช้งานได้ ฉันไม่ใช่คนที่แต่งตัวประหลาด windows แต่จาก goolging ฉันได้เรียนรู้ WSL = ระบบย่อย Windows สำหรับ Linux =)
34719

3
python3.7: subprocess return bytes ดังนั้นโค้ดจะมีลักษณะดังนี้: int (subprocess.check_output (['wc', '-l', file_path]). decode ("utf-8"). lstrip (). split (" ") [0])
Alexey Alexeenka

11

นี่คือสิ่งที่เร็วที่สุดที่ฉันพบโดยใช้งูหลามบริสุทธิ์ คุณสามารถใช้หน่วยความจำจำนวนเท่าใดก็ได้ที่คุณต้องการโดยการตั้งค่าบัฟเฟอร์แม้ว่า 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%


9

ฉันได้รับการปรับปรุงเล็กน้อย (4-8%) กับรุ่นนี้ซึ่งใช้บัฟเฟอร์คงที่อีกครั้งดังนั้นจึงควรหลีกเลี่ยงหน่วยความจำหรือค่าใช้จ่าย GC:

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

คุณสามารถเล่นกับขนาดบัฟเฟอร์และอาจเห็นการปรับปรุงเล็กน้อย


ดี หากต้องการบัญชีสำหรับไฟล์ที่ไม่ได้ลงท้ายด้วย \ n ให้เพิ่ม 1 นอกลูปถ้าบัฟเฟอร์และบัฟเฟอร์ [-1]! = '\ n'
ryuusenshi

ข้อผิดพลาด: บัฟเฟอร์ในรอบสุดท้ายอาจไม่สะอาด
Jay

เกิดอะไรขึ้นถ้าในระหว่างบัฟเฟอร์ส่วนหนึ่งลงท้ายด้วย \ และส่วนอื่นเริ่มต้นด้วย n ที่จะพลาดหนึ่งบรรทัดใหม่ในนั้นฉันจะ sudgest กับตัวแปรในการจัดเก็บปลายและจุดเริ่มต้นของแต่ละชิ้น แต่อาจเพิ่มเวลามากขึ้นในสคริปต์ = (
pelos

9

คำตอบของไคล์

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

9

โซลูชันเดียว:

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

ตัวอย่างของฉัน:

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

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

เป็นความคิดที่ดีน่าเสียดายที่นี่ไม่สามารถใช้กับ Windows
Kim

3
ถ้าคุณต้องการเป็นนักท่องของไพ ธ อนจงบอกลาไปที่ windows โปรดเชื่อฉันว่าคุณจะขอบคุณฉันสักวัน
TheExorcist

6
ฉันเพิ่งคิดว่ามันน่าสังเกตว่ามันจะใช้งานได้เฉพาะบน windows ฉันชอบทำงานกับ linux / unix stack แต่เมื่อเขียนซอฟต์แวร์ IMHO ควรพิจารณาถึงผลข้างเคียงที่โปรแกรมอาจมีเมื่อทำงานภายใต้ระบบปฏิบัติการที่แตกต่างกัน เนื่องจาก OP ไม่ได้กล่าวถึงแพลตฟอร์มของเขาและในกรณีที่ทุกคนปรากฏขึ้นบนโซลูชันนี้ผ่านทาง Google และคัดลอกมัน (ไม่ทราบถึงข้อ จำกัด ที่ระบบ Windows อาจมี) ฉันต้องการเพิ่มบันทึกย่อ
Kim

คุณไม่สามารถบันทึกผลลัพธ์ของos.system()การเป็นตัวแปรและกระบวนการภายหลังได้ แต่อย่างใด
Se

@AnSe คุณถูกต้อง แต่คำถามไม่ได้ถูกถามว่าจะช่วยได้หรือไม่ฉันเดาว่าคุณเข้าใจบริบท
TheExorcist

6

เพียงเพื่อทำวิธีการด้านบนฉันลองตัวแปรกับโมดูล fileinput:

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

และผ่านไฟล์ 60mil ไปยังวิธีการที่ระบุข้างต้นทั้งหมด:

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

มันเป็นเรื่องแปลกใจเล็กน้อยสำหรับฉันว่าการป้อนข้อมูลไฟล์นั้นแย่มากและแย่กว่าวิธีอื่น ๆ ทั้งหมด ...


5

สำหรับฉันตัวแปรนี้จะเร็วที่สุด:

#!/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เร็วมาก


1
แต่มันคืออะไร อย่างน้อยใน OSX / python2.5 เวอร์ชั่นของ OP ยังคงเร็วขึ้นประมาณ 10% ตาม timeit.py
dF

เกิดอะไรขึ้นถ้าบรรทัดสุดท้ายไม่ได้ลงท้ายด้วย '\ n'
tzot

1
ฉันไม่รู้ว่าคุณทดสอบมันอย่างไร dF แต่บนเครื่องของฉันมันช้ากว่าตัวเลือกอื่นประมาณ 2.5 เท่า
SilentGhost

34
คุณระบุว่ามันจะเร็วที่สุดแล้วระบุว่าคุณไม่ได้ทำการทดสอบ ไม่ใช่วิทยาศาสตร์ใช่มั้ย :)
Ólafur Waage

ดูโซลูชันและสถิติที่จัดทำโดย Ryan Ginstrom คำตอบด้านล่าง ตรวจสอบคำติชมของ JF Sebastian และลิงก์ในคำตอบเดียวกัน
SherylHohman

5

รหัสนี้สั้นกว่าและชัดเจนกว่า มันอาจเป็นวิธีที่ดีที่สุด:

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

6
คุณควรปิดไฟล์
rsm

6
มันจะโหลดไฟล์ทั้งหมดลงในหน่วยความจำ
Ivelin

ไม่ดีที่สุดเมื่อต้องการประสิทธิภาพในไฟล์ขนาดใหญ่
mabraham

4

ฉันได้แก้ไขกรณีบัฟเฟอร์เช่นนี้

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) ถูกนับ


อาจอธิบาย (หรือเพิ่มความคิดเห็นในรหัส) สิ่งที่คุณเปลี่ยนแปลงและสิ่งที่สำหรับ;) อาจทำให้บางคนในโค้ดของคุณง่ายขึ้น (แทนที่จะเป็น "การแยกวิเคราะห์" โค้ดในสมอง)
Styxxy

ฉันคิดว่าการเพิ่มประสิทธิภาพแบบวนรอบช่วยให้ Python ทำการค้นหาตัวแปรภายในเครื่องที่ read_f, python.org/doc/essays/list2str
The Red Pea

3

เกี่ยวกับสิ่งนี้

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




3

หากต้องการได้รับจำนวนบรรทัดอย่างถูกต้องใน Python ใน Linux ฉันแนะนำวิธีนี้:

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

file_path สามารถเป็นได้ทั้งเส้นทางไฟล์นามธรรมหรือเส้นทางสัมพัทธ์ หวังว่ามันจะช่วยได้


2

แล้วเรื่องนี้ล่ะ

import fileinput
import sys

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

fileinput.close()
print counter

2

วิธีการหนึ่งซับนี้:

file_length = len(open('myfile.txt','r').read().split('\n'))

ใช้เวลา 0.003 วินาทีโดยใช้วิธีนี้กับเวลาบนไฟล์บรรทัด 3900

def c():
  import time
  s = time.time()
  file_length = len(open('myfile.txt','r').read().split('\n'))
  print time.time() - s

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

คุณช่วยอธิบายสิ่งที่ผิดกับมันได้ไหมถ้าคุณคิดว่ามันผิด มันใช้งานได้สำหรับฉัน ขอบคุณ!
jciloa

ฉันจะสนใจว่าทำไมคำตอบนี้จึงลดลงเช่นกัน มันวนซ้ำไฟล์ตามบรรทัดและรวมเข้าด้วยกัน ฉันชอบมันสั้นและตรงประเด็นเกิดอะไรขึ้นกับมัน?
ดูแล

2

วิธีง่าย ๆ :

1)

>>> f = len(open("myfile.txt").readlines())
>>> f

430

2)

>>> f = open("myfile.txt").read().count('\n')
>>> f
430
>>>

3)

num_lines = len(list(open('myfile.txt')))

3
ในไฟล์ตัวอย่างนี้จะไม่ปิด
Maciej M

9
OP ต้องการหน่วยความจำที่มีประสิทธิภาพ นี่มันไม่แน่นอน
Andy Carlson

1

ผลลัพธ์ของการเปิดไฟล์คือตัววนซ้ำซึ่งสามารถแปลงเป็นลำดับซึ่งมีความยาว:

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

enumerateนี้คือการที่รัดกุมมากกว่าห่วงอย่างชัดเจนของคุณและหลีกเลี่ยงการ


10
ซึ่งหมายความว่าจะต้องอ่านไฟล์ 100 Mb ลงในหน่วยความจำ
SilentGhost

ใช่จุดดีแม้ว่าฉันสงสัยเกี่ยวกับความแตกต่างความเร็ว (เมื่อเทียบกับหน่วยความจำ) อาจเป็นไปได้ที่จะสร้างตัววนซ้ำที่ทำสิ่งนี้ แต่ฉันคิดว่ามันจะเทียบเท่ากับวิธีแก้ปัญหาของคุณ
Andrew Jaffe

6
-1 มันไม่ใช่แค่หน่วยความจำ แต่ต้องสร้างรายการในหน่วยความจำ
orip

0

คุณสามารถใช้os.pathโมดูลด้วยวิธีต่อไปนี้:

import os
import subprocess
Number_lines = int( (subprocess.Popen( 'wc -l {0}'.format( Filename ), shell=True, stdout=subprocess.PIPE).stdout).readlines()[0].split()[0] )

Filenameคือตำแหน่งที่แน่นอนของไฟล์


1
คำตอบนี้เกี่ยวกับos.pathอะไร?
moi

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.