Python多重处理返回的结果按块大小设置

时间:2020-10-22 12:05:05

标签: python python-multiprocessing concurrent.futures

我想使用称为file_list的函数处理存储在get_scores_dataframe中的大量csv文件。此函数采用存储在另一个列表中的第二个自变量phenotypes。然后,该函数将结果写回到csv文件。我设法使用ProcessPoolExecutor()来并行化此任务,因此它可以正常工作。

   with concurrent.futures.ProcessPoolExecutor() as executor:
        phenotypes = [phenotype for i in range(len(file_list))]
        futures = executor.map(get_scores_dataframe, file_list, phenotypes,
                                    chunksize=25)
        filenames = executor.map(os.path.basename, file_list)
        for future, filename  in zip(futures, filenames):
                futures.to_csv(os.path.join(f'{output_path}',f'{filename}.csv'),
                              index = False)

如您所见,我为此使用了上下文管理器,并且在上下文管理器中使用了方法map(),可以在其中设置选项chunksize。但是,我希望程序在完成处理每个数据帧时写入csv文件。似乎上下文管理器会等到所有作业完成后再将结果写入csv文件。

您知道我该如何实现吗?

1 个答案:

答案 0 :(得分:2)

首先,executor.map不返回Future实例,因此变量futures的命名不正确。它确实返回一个迭代器,该迭代器产生将get_scores_dataframe依次应用于file_list的每个元素的返回值。其次,看看接下来如何使用它们,这些返回值似乎是输入文件(与输入参数可能相同或不同)–由于缺少所示代码,因此无法确定。同样,使用进程池map函数而不是内置的map函数来获取文件名参数的基本名称似乎有些大材小用。最后,在您的代码中,它不是futures.to_csv,而是future.to_csv。因此,我对您的代码本该如何工作感到困惑。

如果您修改函数get_scores_dataframe以返回由数据框和原始传递的filename参数组成的元组,那么我们可以使用as_competed按完成顺序处理结果:

from concurrent.futures import as_completed
import multiprocessing

with concurrent.futures.ProcessPoolExecutor(multiprocessing.cpu_count() - 1) as executor:
    futures = [executor.submit(get_scores_dataframe, file, phenotype) for file in file_list]
    for future in as_completed(futures):
        # it is assumed return value is tuple: (data frame, original filename argument):
        df, file = future.result()
        csv_filename = os.path.basename(file)
        df.to_csv(os.path.join(f'{output_path}', f'{csv_filename}.csv'), index = False)

现在,通过使用submit,您将失去“分批”提交工作的能力。我们可以将multiprocessing.Poolimap_unordered一起使用。但是imap_unordered只能将单个参数传递给worker函数。因此,如果您能够修改工作程序以更改参数的顺序,我们可以将phenotype设为第一个,并使用partial (see manual)

import multiprocessing
from functools import partial


POOL_SIZE = multiprocessing.cpu_count() - 1 # leave 1 for main process


def compute_chunksize(iterable_size):
    if iterable_size == 0:
        return 0
    chunksize, extra = divmod(iterable_size, POOL_SIZE * 4)
    if extra:
        chunksize += 1
    return chunksize


with multiprocessing.Pool(POOL_SIZE) as pool:
    chunksize = compute_chunksize(len(file_list))
    worker = partial(get_scores_dataframe, phenotype)
    # it is assumed that start_processing returns a tuple: (data frame, original filename argument)
    for df, file in pool.imap_unordered(worker, file_list, chunksize):
        csv_filename = os.path.basename(file)
        df.to_csv(os.path.join(f'{output_path}', f'{csv_filename}.csv'), index = False)