Python: วิธีที่รวดเร็วในการอ่านและแยกไฟล์คืออะไร?

ฉันต้องอ่านไฟล์และแยกออกเป็นบรรทัด และแบ่งบรรทัดเหล่านั้นออกครึ่งหนึ่งด้วยอักขระแท็บ รวมถึงกำจัดเครื่องหมายคำพูดทั้งหมด ในขณะนี้ฉันมีฟังก์ชั่นการทำงาน อย่างไรก็ตาม มันค่อนข้างช้า:

temp = []
fp = open(fName, "r")
for line in fp:
    temp.append(line.replace("\"","").rstrip("\n").split("\t"))
print temp

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

จะต้องมีวิธีที่เร็วกว่าในการทำเช่นนี้ ใครช่วยพาฉันไปถูกทางได้ไหม?

ขอบคุณ!

[แก้ไข] ไฟล์ที่ฉันทำงานด้วยมีขนาดใหญ่มาก แต่ฉันจะเพิ่มบางอย่างที่คล้ายกัน (มีวิธีอัพโหลดไฟล์บน stack overflow หรือไม่)

"CARMILLA"  "35"
"JONATHAN R"    "AA2"
"M" "3"
"EMMA"  "350"
"OLD"   "AA"

ควรกลับมา:

["CARMILLA", "35", "JONATHON R", "AA2", "M", "3", "EMMA", "350", "OLD", "AA"]

แม้ว่ารหัสของฉันจะส่งคืนเป็นรายการของ 2 สตริงซึ่งก็ใช้ได้เช่นกัน

ขออภัย ฉันน่าจะสังเกตว่าคำสั่ง print อยู่ในสถานะ return คำสั่ง - เนื่องจากฉันเอาสิ่งนี้ออกจากฟังก์ชัน ฉันจึงเปลี่ยนมันให้พิมพ์ ดังนั้นมันจึงสมเหตุสมผลมากขึ้นที่นี่


person false_azure    schedule 21.05.2013    source แหล่งที่มา
comment
ไฟล์ตัวอย่างและผลลัพธ์จะช่วยเราสร้างคำตอบ (สำหรับการทดสอบ)   -  person HennyH    schedule 21.05.2013
comment
แน่นอนฉันจะเพิ่มหนึ่ง   -  person false_azure    schedule 21.05.2013
comment
หากสิ่งที่คุณต้องการคือผลลัพธ์ที่พิมพ์ออกมา คุณสามารถพิมพ์ใน for ของคุณแทนการต่อท้ายรายการ   -  person Gurgeh    schedule 21.05.2013
comment
คุณกำลังมองหา csv หรือไม่? แต่ฉันไม่แน่ใจเกี่ยวกับประสิทธิภาพ   -  person neuront    schedule 21.05.2013
comment
ฉันแน่ใจว่าคุณสามารถทำได้เร็วกว่านี้ แต่ประเด็นคืออะไร? หากช้าเกินไปสำหรับคุณ แสดงว่าคุณรันโค้ดนี้บ่อยเกินไป - ลองแคชผลลัพธ์   -  person maxy    schedule 21.05.2013
comment
คุณกำลังสันนิษฐานว่าการอ่านและการแยกค่อนข้างช้าจากอะไร คุณวัดมันได้อย่างไร?   -  person interjay    schedule 21.05.2013
comment
ฉันวัดโดยใช้ time.time() โดยลบเวลาเริ่มต้น (ก่อนการเรียกใช้ฟังก์ชัน) ออกจากเวลาสิ้นสุด (หลังการโทร)   -  person false_azure    schedule 21.05.2013
comment
หากมีขนาดใหญ่ ให้ทำใน C++ มาตรฐาน C++11 จะทำให้สิ่งนี้เป็นเรื่องง่ายและสามารถทำได้ภายใน 30 นาที แน่นอนว่าถ้าความเร็วนั้นสำคัญมาก ถ้าไม่เช่นนั้น ให้ใช้ Python และใช้ List Comprehension ดังที่ HennyH พูด ด้านล่างและใช้สิ่งที่ Janne Karila พูด คุณจะได้รับประสิทธิภาพเพิ่มขึ้นอย่างไม่ต้องสงสัย   -  person Paul    schedule 21.05.2013


คำตอบ (7)


ฉันคิดว่ารายการความเข้าใจจะเร็วกว่าการโทร .append สำหรับแต่ละบรรทัด

from itertools import chain
with open('file.txt') as f:
    lines = chain.from_iterable([l.replace(r'"','').rstrip('\n').split('\t',1) for l in f])

แก้ไข: ดังนั้นจึงสร้างรายการที่ราบเรียบ

>>> 
['CARMILLA', '35', 'JONATHAN R', 'AA2', 'M', '3', 'EMMA', '350', 'OLD', 'AA']

เวอร์ชันที่ไม่ราบเรียบ:

with open('file.txt') as f:
    lines = [l.replace(r'"','').rstrip('\n').split('\t',1) for l in f]

และบางจังหวะ ปรากฎว่า OP นั้นเร็วที่สุดใช่ไหม

import timeit
print("chain, list",timeit.timeit(r"""
with open('file.txt') as f:
    lines = chain.from_iterable([l.replace(r'"','').rstrip('\n').split('\t',1) for l in f])""",setup="from itertools import chain",number=1000))
print("flat       ",timeit.timeit(r"""
with open('file.txt') as f:
    lines = [l.replace(r'"','').rstrip('\n').split('\t',1) for l in f]""",setup="from itertools import chain",number=1000))
print("op's       ",timeit.timeit(r"""temp = []
fp = open('file.txt', "r")
for line in fp:
    temp.append(line.replace("\"","").rstrip("\n").split("\t"))
""",number=1000))
print("jamlyks    ",timeit.timeit(r"""
with open('file.txt', 'rb') as f:
    r = csv.reader(f, delimiter=' ', skipinitialspace=True)
    list(chain.from_iterable(r))""",setup="from itertools import chain; import csv",number=1000))
print("lennart    ",timeit.timeit(r"""
    list(csv.reader(open('file.txt'), delimiter='\t', quotechar='"'))""",setup="from itertools import chain; import csv",number=1000))

อัตราผลตอบแทน

C:\Users\Henry\Desktop>k.py
('chain, list', 0.04725674146159321)
('my flat    ', 0.04629905135295972)
("op's       ", 0.04391255644624917)
('jamlyks    ', 0.048360870934994915)
('lennart    ', 0.04569112379085424)
person HennyH    schedule 21.05.2013
comment
chain.from_iterable และนิพจน์ตัวสร้างช่วยให้คุณประหยัดเครื่องหมายวรรคตอน - lines = chain.from_iterable(l.replace('"', '')... for l in f) นอกจากนี้ ไม่จำเป็นต้องใช้สตริงดิบ มันไม่ได้สร้างความแตกต่างใดๆ กับสตริงที่ไม่มี `` - person lvc; 21.05.2013
comment
chain ส่งคืนตัววนซ้ำ list() รอบ ๆ มันจะสร้างรายการ - person Janne Karila; 21.05.2013

เมื่อแทนที่ temp.append ด้วย temp.extend คุณจะได้รับรายการเลเยอร์เดียวแทนที่จะเป็นรายการ

person chenaren    schedule 21.05.2013
comment
ฉันต้องการโพสต์สิ่งนี้เพียงแสดงความคิดเห็น แต่ไม่มีสิทธิ์ที่จำเป็น - person chenaren; 21.05.2013
comment
ฉันจะให้สิทธิพิเศษนี้แก่คุณ - person Dmitry Zagorulkin; 21.05.2013
comment
ขอบคุณ ฉันจะลองดู - person false_azure; 21.05.2013

หากคุณทราบว่าแต่ละบรรทัดมี \t เพียงอันเดียว คุณสามารถใช้ split("\t",1) หรือ rsplit("\t",1) เพื่อหลีกเลี่ยงการสแกนทั้งบรรทัดเพื่อหาแท็บ

strip('"') หลัง split เป็นทางเลือกที่เป็นไปได้แทน replace("\"","") ก่อน split ลองดูถ้ามันเร็วกว่านี้

แต่คุณจับเวลาแล้วหรือยังว่าต้องใช้เวลานานเท่าใดในการอ่านไฟล์โดยใช้ file.read()? เวลาที่ใช้ในการแยกทางมีความสำคัญจริง ๆ เมื่อเทียบกับสิ่งนั้นหรือไม่?

person Janne Karila    schedule 21.05.2013
comment
ขอบคุณ! ควรอ่านเอกสารประกอบ ฉันไม่รู้ด้วยซ้ำว่าคุณสามารถทำเช่นนี้ได้ - person false_azure; 21.05.2013
comment
นั่นเป็นจุดที่ดีขอบคุณ บางทีการแยกทางกันอาจไม่ใช่ปัญหาของฉัน - person false_azure; 21.05.2013

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

อัปเดต:

จากข้อมูลตัวอย่างของคุณ คุณอาจลองสิ่งนี้:

import itertools
print list(itertools.chain(
    *( line.strip().split('\t') for line in file('sample.txt') )
))

กำลังสร้างเครื่องกำเนิดไฟฟ้าสำหรับข้อมูลของคุณ print list(...) มีไว้เพื่อการพิมพ์และเพื่อให้สอดคล้องกับตัวอย่างของคุณเท่านั้น ในแอปโลกแห่งความเป็นจริง คุณอาจจะไม่ได้สร้างรายการขึ้นมา แทนที่จะเขียนข้อมูลไปยังตำแหน่งที่ควรไปหรือประมวลผลต่อไป

อัปเดต 2:

หากคุณต้องการกำจัดเครื่องหมายคำพูดและคุณแน่ใจว่าแต่ละส่วนมีเครื่องหมายคำพูด คุณสามารถใช้ x[1:-1] ได้ หรือคุณสามารถใช้ x.strip('"') หากคุณต้องการความมั่นใจ แต่ไม่จำเป็นต้องใช้ regex

person Achim    schedule 21.05.2013

เช่นนี้:

>>> import csv
>>> reader = csv.reader(open('testfile'), delimiter='\t', quotechar='"')
>>> list(reader)
[['CARMILLA', '35'], ['JONATHAN R', 'AA2'], ['M', '3'], ['EMMA', '350'], ['OLD', 'AA']]
person Lennart Regebro    schedule 21.05.2013
comment
@HennyH: การอ้างถึง OP: แม้ว่าโค้ดของฉันจะส่งคืนเป็นรายการของ 2 สตริงซึ่งก็ดีเช่นกัน ไม่เลย ไม่จำเป็นต้องแบน - person Lennart Regebro; 21.05.2013

การใช้ regex และรายการความเข้าใจ:

import re
with open("abc") as f:
    lis = [x.group(1) for line in f for x in \
                             re.finditer(r'"([a-zA-Z0-9\s]+)"', line) ]
    print lis

เอาท์พุท:

['CARMILLA', '35', 'JONATHAN R', 'AA2', 'M', '3', 'EMMA', '350', 'OLD', 'AA']

หากจำนวนค่าที่คั่นแท็บไม่มาก ให้ใช้ re.findall():

lis =  [y for line in f for y in re.findall(r'"([a-zA-Z0-9\s]+)"', line)]

หรือใช้ itertools.chain:

lis =  list(chain(*(re.findall(r'"([a-zA-Z0-9\s]+)"', line) for line in f)))
person Ashwini Chaudhary    schedule 21.05.2013
comment
หากคุณกำลังจะใช้ตัววนซ้ำทั้งหมด รายการเวอร์ชัน re.findall จะเร็วขึ้น - person jamylak; 21.05.2013
comment
@jamylak ใช่แล้ว แต่จะสร้างรายการทั้งหมดในหน่วยความจำก่อน - person Ashwini Chaudhary; 21.05.2013
comment
ใช่ แต่เส้นนั้นสั้น นี่จะทำให้มีค่าใช้จ่ายมากมาย - person jamylak; 21.05.2013

person    schedule
comment
splitlines() จะสร้างรายการทั้งหมดในหน่วยความจำก่อน ไม่ใช่หน่วยความจำที่มีประสิทธิภาพ - person Ashwini Chaudhary; 21.05.2013
comment
คุณจะต้องสร้างรายการทั้งหมดในหน่วยความจำซึ่งต้องใช้เวลาและ ... ใช้หน่วยความจำมาก เหตุใดจึงต้องเร็วกว่านี้? - person Achim; 21.05.2013
comment
ขณะนี้เขากำลังต่อท้ายรายการและพิมพ์รายการออกมา - person robert king; 21.05.2013
comment
ฉันจะทำคะแนนเปรียบเทียบ ครั้งสุดท้ายที่ฉันตรวจสอบมันเร็วที่สุด - person robert king; 21.05.2013
comment
@robertking จุดไม่ใช่รายการที่สร้างโดยความเข้าใจในรายการ แต่เป็นรายการชั่วคราว (และแน่นอนว่าเป็นสตริงชั่วคราว) ที่สร้างโดย file_pointer.read().split_lines() - person lvc; 21.05.2013
comment
ฉันพยายามใช้ read() ซึ่งจะอ่านทุกอย่างในครั้งเดียวซึ่งเร็วกว่าเพราะใช้บัฟเฟอร์ที่ใหญ่กว่า ฉันพบว่า read().splitlines() เร็วกว่ามากในอดีต - person robert king; 21.05.2013