पायथन में बड़ी फ़ाइल पढ़ने के लिए आलसी विधि?


290

मेरे पास एक बहुत बड़ी फाइल 4GB है और जब मैं इसे पढ़ने की कोशिश करता हूं तो मेरा कंप्यूटर हैंग हो जाता है। इसलिए मैं इसे टुकड़े से पढ़ना चाहता हूं और प्रत्येक टुकड़े को संसाधित करने के बाद संसाधित टुकड़े को दूसरी फ़ाइल में संग्रहीत करता हूं और अगले टुकड़े को पढ़ता हूं।

क्या yieldइन टुकड़ों की कोई विधि है ?

मुझे एक आलसी तरीका पसंद आएगा ।

जवाबों:


424

एक आलसी फ़ंक्शन लिखने के लिए, बस उपयोग करें yield:

def read_in_chunks(file_object, chunk_size=1024):
    """Lazy function (generator) to read a file piece by piece.
    Default chunk size: 1k."""
    while True:
        data = file_object.read(chunk_size)
        if not data:
            break
        yield data


with open('really_big_file.dat') as f:
    for piece in read_in_chunks(f):
        process_data(piece)

एक अन्य विकल्प का उपयोग करना iterऔर एक सहायक कार्य करना होगा:

f = open('really_big_file.dat')
def read1k():
    return f.read(1024)

for piece in iter(read1k, ''):
    process_data(piece)

यदि फ़ाइल लाइन-आधारित है, तो फ़ाइल ऑब्जेक्ट पहले से ही लाइनों का एक आलसी जनरेटर है:

for line in open('really_big_file.dat'):
    process_data(line)

तो लाइन f = open('really_big_file.dat')सिर्फ एक संकेतक है बिना किसी मेमोरी खपत के? (मेरा मतलब है कि खपत की गई मेमोरी फ़ाइल आकार की परवाह किए बिना समान है?) अगर मैं f.readline () के बजाय urllib.readline () का उपयोग करूं तो यह प्रदर्शन को कैसे प्रभावित करेगा?
sumid

4
सहकर्मियों का उपयोग करके हमारे Posix- चुनौती वाले विंडोज के साथ संगतता के लिए खुले ('सचमुच_बिज_फाइलटैट', 'आरबी') का उपयोग करने के लिए अच्छा अभ्यास।
ताल वीस

6
rb@Tal Weiss के रूप में गुम ; और एक file.close()कथन याद आ रहा है ( with open('really_big_file.dat', 'rb') as f:उसी को पूरा करने के लिए उपयोग किया जा सकता है ; एक और संक्षिप्त कार्यान्वयन के लिए यहां
Cod3monk3y

4
@ cod3monk3y: टेक्स्ट और बाइनरी फाइलें अलग-अलग चीजें हैं। दोनों प्रकार उपयोगी हैं लेकिन विभिन्न मामलों में। डिफ़ॉल्ट (पाठ) मोड यहाँ उपयोगी हो सकता है यानी, 'rb'है नहीं याद आ रही।
jfs

2
@ jf-sebastian: सच है, ओपी ने निर्दिष्ट नहीं किया था कि वह पाठ्य या द्विआधारी डेटा पढ़ रहा था या नहीं। लेकिन यदि वह पर अजगर 2.7 का उपयोग कर रहा है विंडोज और है बाइनरी डेटा पढ़ने, यह निश्चित रूप से ध्यान देने योग्य बात है कि अगर वह भूल जाता है लायक है 'b'अपने डेटा जाएगा बहुत संभावना दूषित होडॉक्स से -Python on Windows makes a distinction between text and binary files; [...] it’ll corrupt binary data like that in JPEG or EXE files. Be very careful to use binary mode when reading and writing such files.
cd3monk3y

41

यदि आपका कंप्यूटर, OS और अजगर 64-बिट हैं , तो आप फ़ाइल की सामग्री को मेमोरी में मैप करने और इसे सूचक और स्लाइस के साथ एक्सेस करने के लिए mmap मॉड्यूल का उपयोग कर सकते हैं । यहाँ प्रलेखन से एक उदाहरण है:

import mmap
with open("hello.txt", "r+") as f:
    # memory-map the file, size 0 means whole file
    map = mmap.mmap(f.fileno(), 0)
    # read content via standard file methods
    print map.readline()  # prints "Hello Python!"
    # read content via slice notation
    print map[:5]  # prints "Hello"
    # update content using slice notation;
    # note that new content must have same size
    map[6:] = " world!\n"
    # ... and read again using standard file methods
    map.seek(0)
    print map.readline()  # prints "Hello  world!"
    # close the map
    map.close()

यदि या तो आपका कंप्यूटर, OS या अजगर 32-बिट है , तो mmap-ing बड़ी फाइलें आपके एड्रेस स्पेस के बड़े हिस्से को आरक्षित कर सकती हैं और आपके मेमोरी के प्रोग्राम को भूखा कर सकती हैं


7
यह कैसे काम करना है? अगर मेरे पास 32GB फ़ाइल है तो क्या होगा? अगर मैं 256MB RAM वाला VM पर हूं तो क्या होगा? इतनी बड़ी फ़ाइल को मैप करना वास्तव में अच्छी बात नहीं है।
सेविनो सुगेरा

4
इस उत्तर के लिए -12 वोट चाहिए। यह बड़ी फ़ाइलों के लिए उपयोग करके किसी को भी मार देगा।
फ़िओ अर्कार Lwin

23
यह बड़ी फ़ाइलों के लिए भी 64-बिट पायथन पर काम कर सकता है। भले ही फ़ाइल मेमोरी-मैप की गई हो, यह मेमोरी में नहीं पढ़ी जाती है, इसलिए भौतिक मेमोरी की मात्रा फ़ाइल के आकार से बहुत छोटी हो सकती है।
पीटी

1
@SavinoSguera किसी फ़ाइल को mmaping के साथ भौतिक मेमोरी के आकार का करता है?
निक टी

17
@ V3ss0n: मैंने 64-बिट पायथन पर 32GB फ़ाइल को चूमने की कोशिश की है। यह काम करता है (मेरे पास 32 जीबी से कम रैम है): मैं सीक्वेंस और फ़ाइल इंटरफेस दोनों का उपयोग करके फ़ाइल के प्रारंभ, मध्य और अंत तक पहुंच सकता हूं।
JFS

37

file.readlines() एक वैकल्पिक आकार के तर्क में लेता है जो लाइनों की संख्या को पढ़ता है जो वापस लौटी लाइनों में पढ़ता है।

bigfile = open('bigfilename','r')
tmp_lines = bigfile.readlines(BUF_SIZE)
while tmp_lines:
    process([line for line in tmp_lines])
    tmp_lines = bigfile.readlines(BUF_SIZE)

1
यह वास्तव में एक महान विचार है, खासकर जब इसे बड़े डेटा को छोटे लोगों में विभाजित करने के लिए डिफ़ॉल्ट के साथ जोड़ा जाता है।
फ्रैंक वांग

4
मैं उपयोग .read()नहीं करने की सलाह दूंगा .readlines()। यदि फ़ाइल बाइनरी है तो इसमें लाइन ब्रेक नहीं है।
मायर्स कारपेंटर

1
यदि फ़ाइल एक बहुत बड़ी स्ट्रिंग है तो क्या होगा?
मैटसोम

28

पहले से ही कई अच्छे उत्तर हैं, लेकिन अगर आपकी पूरी फ़ाइल एक ही पंक्ति में है और आप अभी भी "पंक्तियों" को संसाधित करना चाहते हैं (जैसा कि निश्चित आकार के ब्लॉक के विपरीत है), ये उत्तर आपकी मदद नहीं करेंगे।

99% समय, फाइलों की लाइन को लाइन से प्रोसेस करना संभव है। फिर, जैसा कि इस उत्तर में सुझाया गया है , आप फ़ाइल ऑब्जेक्ट को आलसी जनरेटर के रूप में उपयोग कर सकते हैं:

with open('big.csv') as f:
    for line in f:
        process(line)

हालांकि, मैं एक बार एक बहुत ही बहुत बड़ा (लगभग) एक पंक्ति फ़ाइल में भाग है, जहां पंक्ति विभाजक वास्तव में था नहीं '\n'लेकिन '|'

  • लाइन द्वारा लाइन पढ़ना एक विकल्प नहीं था, लेकिन मुझे अभी भी इसे पंक्ति द्वारा पंक्ति बनाने की आवश्यकता थी।
  • प्रसंस्करण '|'से '\n'पहले परिवर्तित करना भी सवाल से बाहर था, क्योंकि इस csv के कुछ क्षेत्र निहित थे'\n' (मुक्त पाठ उपयोगकर्ता इनपुट) ।
  • सीएसवी लाइब्रेरी का उपयोग करने से भी इंकार किया गया क्योंकि तथ्य यह है कि, कम से कम लिब के शुरुआती संस्करणों में, इनपुट लाइन को लाइन से पढ़ने के लिए हार्डकोड किया गया है

इस तरह की स्थितियों के लिए, मैंने निम्नलिखित स्निपेट बनाया:

def rows(f, chunksize=1024, sep='|'):
    """
    Read a file where the row separator is '|' lazily.

    Usage:

    >>> with open('big.csv') as f:
    >>>     for r in rows(f):
    >>>         process(row)
    """
    curr_row = ''
    while True:
        chunk = f.read(chunksize)
        if chunk == '': # End of file
            yield curr_row
            break
        while True:
            i = chunk.find(sep)
            if i == -1:
                break
            yield curr_row + chunk[:i]
            curr_row = ''
            chunk = chunk[i+1:]
        curr_row += chunk

मैं अपनी समस्या को हल करने के लिए इसका सफलतापूर्वक उपयोग करने में सक्षम था। यह बड़े पैमाने पर विभिन्न चंक आकारों के साथ परीक्षण किया गया है।


टेस्ट सूट, उन लोगों के लिए जो खुद को मनाना चाहते हैं।

test_file = 'test_file'

def cleanup(func):
    def wrapper(*args, **kwargs):
        func(*args, **kwargs)
        os.unlink(test_file)
    return wrapper

@cleanup
def test_empty(chunksize=1024):
    with open(test_file, 'w') as f:
        f.write('')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 1

@cleanup
def test_1_char_2_rows(chunksize=1024):
    with open(test_file, 'w') as f:
        f.write('|')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 2

@cleanup
def test_1_char(chunksize=1024):
    with open(test_file, 'w') as f:
        f.write('a')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 1

@cleanup
def test_1025_chars_1_row(chunksize=1024):
    with open(test_file, 'w') as f:
        for i in range(1025):
            f.write('a')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 1

@cleanup
def test_1024_chars_2_rows(chunksize=1024):
    with open(test_file, 'w') as f:
        for i in range(1023):
            f.write('a')
        f.write('|')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 2

@cleanup
def test_1025_chars_1026_rows(chunksize=1024):
    with open(test_file, 'w') as f:
        for i in range(1025):
            f.write('|')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 1026

@cleanup
def test_2048_chars_2_rows(chunksize=1024):
    with open(test_file, 'w') as f:
        for i in range(1022):
            f.write('a')
        f.write('|')
        f.write('a')
        # -- end of 1st chunk --
        for i in range(1024):
            f.write('a')
        # -- end of 2nd chunk
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 2

@cleanup
def test_2049_chars_2_rows(chunksize=1024):
    with open(test_file, 'w') as f:
        for i in range(1022):
            f.write('a')
        f.write('|')
        f.write('a')
        # -- end of 1st chunk --
        for i in range(1024):
            f.write('a')
        # -- end of 2nd chunk
        f.write('a')
    with open(test_file) as f:
        assert len(list(rows(f, chunksize=chunksize))) == 2

if __name__ == '__main__':
    for chunksize in [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]:
        test_empty(chunksize)
        test_1_char_2_rows(chunksize)
        test_1_char(chunksize)
        test_1025_chars_1_row(chunksize)
        test_1024_chars_2_rows(chunksize)
        test_1025_chars_1026_rows(chunksize)
        test_2048_chars_2_rows(chunksize)
        test_2049_chars_2_rows(chunksize)

11
f = ... # file-like object, i.e. supporting read(size) function and 
        # returning empty string '' when there is nothing to read

def chunked(file, chunk_size):
    return iter(lambda: file.read(chunk_size), '')

for data in chunked(f, 65536):
    # process the data

अद्यतन: दृष्टिकोण को https://stackoverflow.com/a/4566523/38592 में सबसे अच्छा समझाया गया है


यह ब्लब्स के लिए अच्छा काम करता है, लेकिन लाइन से अलग कंटेंट (जैसे CSV, HTML आदि) के लिए अच्छा नहीं हो सकता है, जहां प्रोसेसिंग के लिए लाइन द्वारा लाइन को संभालने की जरूरत होती है)
cgseller

7

अजगर के आधिकारिक दस्तावेज देखें https://docs.python.org/zh-cn/3/library/functions.html?#iter

हो सकता है कि यह विधि अधिक पाइथोनिक हो:

from functools import partial

"""A file object returned by open() is a iterator with
read method which could specify current read's block size"""
with open('mydata.db', 'r') as f_in:

    part_read = partial(f_in.read, 1024*1024)
    iterator = iter(part_read, b'')

    for index, block in enumerate(iterator, start=1):
        block = process_block(block)    # process block data
        with open(f'{index}.txt', 'w') as f_out:
            f_out.write(block)

3

मुझे लगता है कि हम इस तरह लिख सकते हैं:

def read_file(path, block_size=1024): 
    with open(path, 'rb') as f: 
        while True: 
            piece = f.read(block_size) 
            if piece: 
                yield piece 
            else: 
                return

for piece in read_file(path):
    process_piece(piece)

2

मुझे अपनी कम प्रतिष्ठा के कारण टिप्पणी करने की अनुमति नहीं है, लेकिन फाइल-ट्रेडलाइन ([sizehint]) के साथ साइलेंटगॉस्ट्स समाधान बहुत आसान होना चाहिए

अजगर फ़ाइल विधियों

संपादित करें: साइलेंटगॉस्ट सही है, लेकिन इससे बेहतर होना चाहिए:

s = "" 
for i in xrange(100): 
   s += file.next()

ठीक है, क्षमा करें, आप बिल्कुल सही हैं। लेकिन शायद यह समाधान आपको खुश कर देगा;): s = "" i for i in xrange (100): s + = file.next ()
sinzi

1
-1: भयानक समाधान, इसका मतलब होगा कि प्रत्येक लाइन मेमोरी में एक नया स्ट्रिंग बनाता है, और नए स्ट्रिंग में पढ़े गए संपूर्ण फ़ाइल डेटा की प्रतिलिपि बनाता है। सबसे खराब प्रदर्शन और मेमोरी।
nosklo

यह संपूर्ण फ़ाइल डेटा को एक नए स्ट्रिंग में क्यों कॉपी करेगा? अजगर प्रलेखन से: लूप के लिए एक फ़ाइल (एक बहुत ही सामान्य ऑपरेशन) की तर्ज पर लूपिंग का सबसे कुशल तरीका बनाने के लिए, अगला () विधि एक छिपे हुए रीड-फॉरवर्ड बफर का उपयोग करता है।
पापजी

3
@sinzi: "s + =" या स्ट्रैटेनेटिंग स्ट्रिंग्स प्रत्येक बार स्ट्रिंग की एक नई प्रतिलिपि बनाता है, चूंकि स्ट्रिंग अपरिवर्तनीय है, इसलिए आप एक नया स्ट्रिंग बना रहे हैं।
nosklo

1
@ नोस्कोलो: ये कार्यान्वयन का विवरण हैं, सूची की समझ का उपयोग इसमें किया जा सकता है
साइलेंटगॉस्ट

1

मैं कुछ इसी तरह की स्थिति में हूं। यह स्पष्ट नहीं है कि आप बाइट्स में चंक आकार जानते हैं; मैं आमतौर पर नहीं, लेकिन रिकॉर्ड की संख्या (लाइनों) की आवश्यकता है कि जाना जाता है:

def get_line():
     with open('4gb_file') as file:
         for i in file:
             yield i

lines_required = 100
gen = get_line()
chunk = [i for i, j in zip(gen, range(lines_required))]

अद्यतन : धन्यवाद nosklo। यहाँ मेरा मतलब है। यह लगभग काम करता है, सिवाय इसके कि यह 'चूजों' के बीच एक लाइन खो देता है।

chunk = [next(gen) for i in range(lines_required)]

क्या ट्रिक w / o किसी भी लाइन को खोती है, लेकिन यह बहुत अच्छी नहीं लगती है।


1
क्या यह छद्म कोड है? यह काम नहीं करेगा। यह भी अनावश्यक भ्रमित है, आपको get_line फ़ंक्शन के लिए लाइनों की संख्या को एक वैकल्पिक पैरामीटर बनाना चाहिए।
nosklo

0

लाइन से लाइन को संसाधित करने के लिए, यह एक सुरुचिपूर्ण समाधान है:

  def stream_lines(file_name):
    file = open(file_name)
    while True:
      line = file.readline()
      if not line:
        file.close()
        break
      yield line

जब तक कोई खाली लाइनें नहीं हैं।


6
यह सिर्फ एक अति जटिल है, कम मजबूत है, और जो openपहले से ही आपको देता है उसके बराबर धीमा है । एक फ़ाइल पहले से ही इसकी तर्ज पर एक पुनरावृत्ति है।
22

-2

आप निम्नलिखित कोड का उपयोग कर सकते हैं।

file_obj = open('big_file') 

खुला () फ़ाइल ऑब्जेक्ट लौटाता है

फिर आकार पाने के लिए os.stat का उपयोग करें

file_size = os.stat('big_file').st_size

for i in range( file_size/1024):
    print file_obj.read(1024)

अगर पूरी तरह से 1024
kmaork
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.