python中两个复制函数的比较

本文由Markdown语法编辑器编辑完成。

1 需求确定

最近在完成一个需求:需要实现一个拷贝文件夹的功能。
比如原来的目录结构是:
/media/admin/Data/PatientID
现在需要在Data和PatientID之间增加一级目录为CT,因此修改后的目录结构是:
/media/admin/Data/CT/PatientID

对于这个需求,基本的思路是:
(1)根据PatientID的当前路径,获取它的上一级目录:/media/admin/Data
(2)在上一级目录创建一个CT的文件夹,之后目录为:/media/admin/Data/CT
(3)将原来PatientID的文件夹拷贝到新创建的那个目录下,这时在/media/admin/Data下面会有两个文件夹:
/media/admin/Data/CT
/media/admin/Data/PatientID
(4)删除原来的那个目录/media/admin/Data/PatientID。
此时便完成了一个在原有目录结构内增加一级目录的需求。
备注:这里要说明在执行第3步操作时,为什么不是直接把那个PatientID的文件夹移动(剪切)到新创建的CT目录下,而是要先复制,然后再删除原来的文件夹呢。
这是由需求决定的。因为根据程序的配置,除了在/media/admin/Data下会创建CT这一级目录,还有可能会创建DR,MRI等文件夹,然后PatientID的文件夹需要在新创建的这三个目录下都复制一份。因此,只有等PatientID在新建的那三个目录复制结束后,
也就是:
/media/admin/Data/CT/PatientID
/media/admin/Data/DR/PatientID
/media/admin/Data/MRI/PatientID
才能删除原始的文件夹/media/admin/Data/PatientID。

2 复制函数确定

需求确定后,最主要的那个复制操作,经过查找后,发现Python中有两个库可以完成这个复制操作。分别是:
distutils.dir_util.copy_tree(src, dest)
shutil.copytree(src, dest)

当时看这两个函数的区别是,
shutil.copytree在进行复制操作时,如果dest存在时,调用该函数会报错。也就是说,必须确保传入的目标路径之前是不存在的。而第一个复制函数则没有这个限制。

由于在实际测试时,有可能会存在复制前,dest目录已经存在的情况。因此,为了避免这种情况,决定采用distutils.dir_util.copy_tree()这个复制函数。

3 问题出现

后来在实际测试时,在调用这个函数的时候,经常莫名地会报:
DistutilsFileError: file xxx doest not exist的错误。
但是我在本机进行测试的时候,总是无法重现这个错误。正当百思不得其解的时候,在网上看到一个说明:
https://stackoverflow.com/questions/9160227/dir-util-copy-tree-fails-after-shutil-rmtree
看问题的标题是:dir_util.copy_tree fails after shutil.rmtree.
意思是,这个拷贝函数在调用删除文件夹的操作后调用时会报错。
下面的解答是:Seems to be a bug in distutils. If you copy folder, then remove it, then copy again it will fail, because it caches all the created dirs.
就是说,当首先拷贝一个文件夹,然后删除它,然后再次拷贝同一个文件夹的时候,会失败。因为它缓存了之前创建的所有文件夹。

这时,我再回头看之前测试提交给我的系统运行日志,我分明看到了在调用拷贝函数时,我的确是有进行过删除文件夹的操作。也就是说,我恰好撞到了这个拷贝函数自己的bug里了。

问题的原因找到后,我按照回答者给的代码试了一下,
distutils.dir_util._path_created = {}
distutils.dir_util.copy_tree(src, dst)
还是无效。

4. 问题解决

无奈之下,我只能决定弃用这个函数,转而用另一个复制函数了shutil.copytree(src, dest)。

当然这个函数的缺陷之前已经了解了。因此为了避免这个拷贝函数报错,我每次在调用shutil.copytree前,都需要首先判断一下dest的目录是否存在。如果存在,则需要先删除该目录,然后再执行拷贝操作。

import os
import shutil

def copy_func(src, dest):
	if os.path.exists(dest):
		shutil.rmtree(dest)
	shutil.copytree(src, dest)

5. 反思

看似是一个很小的问题。但是当时解决时,还是费了很大的劲和功夫的。而最耗费时间的,实际是在重现这个bug和与测试人员交流的时间成本。

当测试人员提到程序运行时会报这个错误时。我下意识地总感觉,一定是测试在测试前,没有把该清空的目录都清掉,或者是哪里操作不对,而导致报这个错误。因为我自己在写程序和测试时,就从来没有遇到过这个错误哦。来来回回交流了几次,bug每次都会重现,身上的压力也比较大。

后来,我问测试拿到他测试时的测试数据和配置文件,然后再在我本机测试的时候,这个bug终于重现了。当时还是比较兴奋的。因为只有在本机重现了,才可以逐行去debug代码,离解决问题也就不远了。果然,当确保按照测试给的数据和配置文件运行程序时,每次都必然会重现这个bug时,我就比较有方向性了。通过细致地分析日志,才找到了那个复制函数的bug。最终解决了这个bug。

因此,以后当再次出现这类问题时,我想最快的解决方法,一定是要拿原始的数据去测试,而不是随便用自己开发时用的数据去企图重现bug。因为,有的bug的确是跟所使用的测试数据有很大和直接的关系的。

<think>我们被要求用中文回答,并且要遵循特定的格式要求。用户的问题是如何在Python中实现两个函数的并行执行。 根据引用[1]和[2],我们可以知道Python中有多线程、多进程以及异步编程等方式来实现并行。同时,引用[2]提到了使用ray库的方法,但用户可能更希望使用标准库中的方法。 常见的并行方式: 1. 多线程:适用于I/O密集型任务,因为GIL(全局解释器锁)的存在,多线程不适合CPU密集型任务。 2. 多进程:适用于CPU密集型任务,每个进程有自己的Python解释器和内存空间,因此可以绕过GIL。 3. 异步编程(如asyncio):适用于I/O密集型任务,通过单线程内切换任务来实现并发,但不是真正的并行(除非结合多进程)。 另外,还有一些第三方库如concurrent.futures提供了线程池和进程池,可以方便地并行执行函数。 因此,我们可以给出以下几种方法: 方法一:使用`threading`模块(多线程) 方法二:使用`multiprocessing`模块(多进程) 方法三:使用`concurrent.futures`模块(线程池或进程池) 方法四:使用`asyncio`(异步,但注意是单线程并发,不是并行,除非结合多进程) 方法五:第三方库如`ray`(引用[2]提到) 由于用户要求两个函数并行执行,我们假设这两个函数都是计算密集型或I/O密集型,然后根据情况推荐。 我们以两个简单的函数为例: def func1(): # 执行一些操作 def func2(): # 执行一些操作 我们将展示使用多线程和多进程的基本方法。 注意:由于用户问题中没有具体说明函数的类型,我们将提供通用方法。 另外,引用[2]中提到如果两个函数需要同一个大参数,可以使用ray.put()来避免重复复制。但这里我们先不考虑参数的情况,假设函数不需要参数。 步骤: 1. 导入相应的模块 2. 创建线程或进程 3. 启动并等待完成 下面我们分别用多线程和多进程来实现。 多线程示例: ```python import threading def func1(): print("Function 1 starting") # 模拟工作 for i in range(5): print(f"Func1: {i}") print("Function 1 completed") def func2(): print("Function 2 starting") # 模拟工作 for i in range(5): print(f"Func2: {i}") print("Function 2 completed") if __name__ == "__main__": t1 = threading.Thread(target=func1) t2 = threading.Thread(target=func2) t1.start() t2.start() t1.join() t2.join() print("All threads finished") ``` 多进程示例: ```python import multiprocessing def func1(): print("Function 1 starting") # 模拟工作 for i in range(5): print(f"Func1: {i}") print("Function 1 completed") def func2(): print("Function 2 starting") # 模拟工作 for i in range(5): print(f"Func2: {i}") print("Function 2 completed") if __name__ == "__main__": p1 = multiprocessing.Process(target=func1) p2 = multiprocessing.Process(target=func2) p1.start() p2.start() p1.join() p2.join() print("All processes finished") ``` 使用`concurrent.futures`的线程池示例: ```python from concurrent.futures import ThreadPoolExecutor def func1(): print("Function 1 starting") for i in range(5): print(f"Func1: {i}") print("Function 1 completed") def func2(): print("Function 2 starting") for i in range(5): print(f"Func2: {i}") print("Function 2 completed") if __name__ == "__main__": with ThreadPoolExecutor() as executor: executor.submit(func1) executor.submit(func2) print("All tasks completed") ``` 同样,进程池只需要将`ThreadPoolExecutor`改为`ProcessPoolExecutor`。 注意:多进程中,如果打印输出可能会乱序,而且子进程的输出可能会主进程的输出交错。另外,在Windows系统上使用多进程时,必须将代码放在`if __name__ == '__main__':`中,以避免无限创建子进程。 对于I/O密集型任务,多线程通常足够,而且开销小于多进程。对于CPU密集型任务,多进程是更好的选择。 另外,异步编程(asyncio)的方式,如果两个函数都是异步的,可以使用asyncio.gather来并发运行。但要求函数是协程,并且使用await来调用I/O操作。这里不展开,因为用户要求的是并行(同时执行),而异步在单线程中是交替执行,但如果是I/O操作,也可以达到类似并行的效果(因为I/O等待期间可以运行其他协程)。 最后,引用[2]提到的ray库,是一个分布式计算框架,可以很方便地实现并行和分布式计算。如果用户需要处理大规模数据或者分布式环境,可以考虑。但这里我们主要介绍标准库方法。 根据引用[1]和[2],我们需要注意线程、进程间的通信和同步,避免问题。 因此,在回答中,我们将提供多线程和多进程的基本方法,并简要说明适用场景。 我们按照要求生成相关问题。</think>### Python 中实现函数并行执行的方法 在 Python 中实现两个函数的并行执行,主要有以下三种方法,根据任务类型选择合适方案: #### 1. 多线程(`threading`模块) **适用场景**:I/O 密集型任务(如网络请求、文件读写) ```python import threading def task1(): print("函数1执行中...") # 任务逻辑 def task2(): print("函数2执行中...") # 任务逻辑 # 创建线程 t1 = threading.Thread(target=task1) t2 = threading.Thread(target=task2) # 启动线程 t1.start() t2.start() # 等待线程结束 t1.join() t2.join() print("所有函数执行完成") ``` **特点**: - 共享内存,通信简单 - 受 GIL(全局解释器锁)限制,不适合 CPU 密集型任务[^1] #### 2. 多进程(`multiprocessing`模块) **适用场景**:CPU 密集型任务(如数学计算、图像处理) ```python from multiprocessing import Process def task1(): print("进程1执行中...") # 任务逻辑 def task2(): print("进程2执行中...") # 任务逻辑 if __name__ == "__main__": p1 = Process(target=task1) p2 = Process(target=task2) p1.start() p2.start() p1.join() p2.join() print("所有进程执行完成") ``` **特点**: - 绕过 GIL 限制,真正并行 - 内存独立,需通过 IPC(如队列、管道)通信 #### 3. 线程池/进程池(`concurrent.futures`) **适用场景**:简化并行任务管理 ```python from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor def task1(): return "结果1" def task2(): return "结果2" # 线程池(I/O 密集型) with ThreadPoolExecutor() as executor: future1 = executor.submit(task1) future2 = executor.submit(task2) print(future1.result(), future2.result()) # 进程池(CPU 密集型) with ProcessPoolExecutor() as executor: futures = [executor.submit(task1), executor.submit(task2)] for future in futures: print(future.result()) ``` ### 关键注意事项 1. **数据共享**: - 多线程:直接共享变量(需用`Lock`防竞态) - 多进程:需通过`Queue`、`Pipe`或共享内存传递数据 2. **参数传递**: - 大对象使用`ray.put()`避免重复复制(第三方库 ray)[^2] 3. **资源限制**: - 进程数 ≤ CPU 核心数 - 线程数通常设为 I/O 等待时间的倍数 ### 性能对比场景 | 任务类型 | 推荐方法 | 执行时间示例 | |----------------|------------------|-------------| | 网络请求(10次) | 线程池(5线程) | 2秒 ↓ 0.5秒 | | 矩阵计算(2个) | 进程池(2进程) | 6秒 ↓ 3秒 | | 混合任务 | 进程+线程组合 | 视任务而定 | > 提示:调试时可用`logging`模块替代`print`,避免多线程输出混乱[^1]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

inter_peng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值