পাইথনে বড় ফাইল পড়ার জন্য অলস পদ্ধতি?


290

আমার কাছে খুব বড় ফাইল 4 জিবি রয়েছে এবং আমি যখন এটি পড়ার চেষ্টা করি তখন আমার কম্পিউটার স্তব্ধ হয়ে যায়। সুতরাং আমি এটি টুকরো টুকরো করে পড়তে চাই এবং প্রতিটি টুকরো প্রক্রিয়া করার পরে প্রক্রিয়াজাত টুকরোটি অন্য একটি ফাইলে সংরক্ষণ করুন এবং পরবর্তী অংশটি পড়ুন read

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 () ব্যবহার করি তবে এটি কীভাবে পারফরম্যান্সে প্রভাব ফেলবে?
অগস্ট

4
সহকর্মীদের ব্যবহার করে আমাদের পিক্সিক-চ্যালেঞ্জযুক্ত উইন্ডোজের সামঞ্জস্যের জন্য ওপেন ('সত্য_বিগ_ফিল.ড্যাট', 'আরবি') ব্যবহার করার ভাল অনুশীলন।
তাল ওয়েইস

6
rb@ ট্যাল ওয়েইস উল্লিখিত হিসাবে অনুপস্থিত ; এবং একটি file.close()বিবৃতি অনুপস্থিত ( with open('really_big_file.dat', 'rb') as f:এটি সম্পাদন করতে ব্যবহার করতে পারে ; অন্য সংক্ষিপ্ত বাস্তবায়নের জন্য এখানে
cod3monk3y

4
@ cod3monk3y: পাঠ্য এবং বাইনারি ফাইলগুলি আলাদা জিনিস। উভয় প্রকারই কার্যকর তবে বিভিন্ন ক্ষেত্রে। ডিফল্ট (পাঠ্য) মোড এখানে দরকারী হতে পারে অর্থাত, 'rb'হয় না অনুপস্থিত।
jfs

2
@ জেএফ-সেবাস্তিয়ান: সত্য, ওপি পাঠ্য বা বাইনারি ডেটা পড়ছে কিনা তা নির্দিষ্ট করে নি। কিন্তু যদি তিনি পাইথন 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.
cod3monk3y

41

যদি আপনার কম্পিউটার, ওএস এবং পাইথন 64৪-বিট হয় , তবে আপনি ফাইলের বিষয়বস্তুগুলিকে মেমরিতে ম্যাপ করতে এবং সূচক এবং টুকরো দিয়ে এটি অ্যাক্সেস করতে এমএমএপ মডিউলটি ব্যবহার করতে পারেন । এখানে ডকুমেন্টেশন থেকে একটি উদাহরণ:

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

যদি আপনার কম্পিউটার, ওএস বা পাইথন 32-বিট হয় , তবে এমএমএপ-ইন বড় ফাইলগুলি আপনার ঠিকানার জায়গার বড় অংশ সংরক্ষণ করতে পারে এবং আপনার মেমরির প্রোগ্রামটি অনাহারে রাখতে পারে


7
এটি কীভাবে কাজ করার কথা? আমার কাছে যদি 32 জিবি ফাইল থাকে? আমি যদি 256 এমবি র‌্যাম সহ কোনও ভিএম এ থাকি তবে কী হবে? এত বিশাল ফাইল ম্যাপ করা সত্যিই কখনই ভাল জিনিস নয়।
সাভিনো স্টিগরা

4
এই উত্তরটি একটি -12 ভোটের জন্য প্রাপ্য। এটি বড় ফাইলগুলির জন্য এটি ব্যবহার করে যে কাউকে হত্যা করবে।
ফায়ো আরকার লুইন

23
এটি বড় ফাইলগুলির জন্যও 64৪-বিট পাইথনে কাজ করতে পারে। ফাইলটি মেমরি-ম্যাপযুক্ত হওয়া সত্ত্বেও এটি মেমরির কাছে পড়ে না, সুতরাং শারীরিক মেমরির পরিমাণ ফাইল আকারের চেয়ে অনেক কম হতে পারে।
পিটিএস

1
@ সাভিনোসিগুরা কোনও ফাইলকে এমএমপিংয়ের সাথে দৈহিক মেমরির আকারের কী বোঝায়?
নিক টি

17
@ V3ss0n: আমি GB৪-বিট পাইথনে 32 জিবি ফাইল এমএম্যাপ করার চেষ্টা করেছি। এটি কাজ করে (আমার কাছে 32 গিগাবাইটের চেয়ে কম র‌্যাম রয়েছে): আমি সিকোয়েন্স এবং ফাইল ইন্টারফেস উভয়ই ব্যবহার করে ফাইলের শুরু, মাঝারি এবং শেষের দিকে অ্যাক্সেস করতে পারি।
jfs

37

file.readlines() একটি alচ্ছিক আকারের আর্গুমেন্ট গ্রহণ করে যা প্রত্যাবর্তিত রেখাগুলিতে পাঠানো লাইনের সংখ্যার সমান করে।

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'(বিনামূল্যে টেক্সট ব্যবহারকারীর ইনপুট)।
  • CSV গ্রন্থাগার ব্যবহার এছাড়াও ছিটকে করা হয়েছে কারণ যে, liberal এর সংক্ষিপ্ত রূপ প্রথম সংস্করণে অন্তত এটা হার্ডকোডেড হয় লাইন দ্বারা ইনপুট লাইন পড়তে

এই ধরণের পরিস্থিতিতে আমি নিম্নলিখিত স্নিপেট তৈরি করেছি:

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 এ সর্বোত্তমভাবে ব্যাখ্যা করা হয়েছে


এটি ব্লবগুলির পক্ষে ভাল কাজ করে, তবে লাইন বিচ্ছিন্ন সামগ্রীর জন্য ভাল নাও হতে পারে (যেমন সিএসভি, এইচটিএমএল ইত্যাদির জন্য যেখানে প্রসেসিংটি লাইন দ্বারা হ্যান্ডেল করা দরকার)
সিজিसेलার

7

পাইথনের অফিসিয়াল ডকুমেন্টেশন দেখুন https://docs.python.org/zh-cn/3/library/function.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

আমার কম সুনামের কারণে আমাকে মন্তব্য করার অনুমতি নেই তবে সাইলেন্টহোস্টস সমাধান ফাইল.রেডলাইনগুলির সাথে খুব সহজ হওয়া উচিত ([সাইজহিন্ট])

পাইথন ফাইল পদ্ধতি

সম্পাদনা করুন: সাইলেন্টগোস্ট সঠিক, তবে এটি এর থেকে ভাল হওয়া উচিত:

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

ঠিক আছে, দুঃখিত, আপনি ঠিক বলেছেন। তবে সম্ভবত এই সমাধান আপনাকে আরও সুখী করে তুলবে;): s = "" আমার জন্য এক্সরেঞ্জ (100): s + = file.next ()
sinzi

1
-1: ভয়াবহ সমাধান, এর অর্থ প্রতিটি লাইনে মেমরির একটি নতুন স্ট্রিং তৈরি করা এবং নতুন স্ট্রিংয়ে পড়া পুরো ফাইল ডেটা অনুলিপি করা। সবচেয়ে খারাপ পারফরম্যান্স এবং স্মৃতি।
নসক্লো

এটি কেন পুরো ফাইল ডেটাটিকে একটি নতুন স্ট্রিংয়ে অনুলিপি করবে? পাইথন ডকুমেন্টেশন থেকে: কোনও ফাইলের লাইন (খুব সাধারণ ক্রিয়াকলাপ) এর উপর লুপের সবচেয়ে কার্যকর পদ্ধতিতে লুপ তৈরির জন্য, পরবর্তী () পদ্ধতিতে একটি লুকানো পঠিত-সামনের বাফার ব্যবহার করা হয়।
sinzi

3
@ সিনজি: "এস + =" বা সংলগ্ন স্ট্রিংগুলি প্রতিবার স্ট্রিংয়ের একটি নতুন অনুলিপি তৈরি করে, যেহেতু স্ট্রিং অপরিবর্তনীয়, তাই আপনি একটি নতুন স্ট্রিং তৈরি করছেন।
নসক্লো

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

কৌতুকটি কোনও লাইন হারাতে পারে তবে এটি খুব সুন্দর দেখাচ্ছে না।


1
এই ছদ্ম কোড কি? এটা কাজ করবে না। এটিও অযথা বিভ্রান্তিকর, আপনার লাইনের সংখ্যাটি get_line ফাংশনের একটি toচ্ছিক পরামিতি করা উচিত।
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ইতিমধ্যে আপনাকে যা দেবে তার চেয়ে মাত্রাতিরিক্ত জটিল, কম শক্তিশালী এবং ধীর সমতুল্য । একটি ফাইল ইতিমধ্যে তার রেখাগুলিতে একটি পুনরাবৃত্তি।
abarnert

-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
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.