# 聊聊Python里的mmap把文件当内存用平时处理文件的时候大多数人想到的都是open、read、write这些常规操作。但如果你需要处理特别大的文件或者想在多个进程间共享数据常规的文件操作就显得有些力不从心了。这时候可以看看mmap这个模块它提供了一种不太一样的文件处理思路。它到底是什么mmap的全称是memory-mapped files翻译过来叫内存映射文件。这个名字听起来有点技术化但其实概念挺直观的。想象一下你在看一张很大的地图这张地图铺满了整张桌子。你不需要把整张地图都拿起来看只需要走到桌子的某个位置低头就能看到那部分区域。mmap做的事情就类似——它把磁盘上的文件“映射”到内存的地址空间里让你可以像访问内存一样访问文件内容而不需要真的把整个文件都读到内存里。在Python里mmap模块就是对操作系统这个功能的封装。不同操作系统底层实现不太一样但Python的接口基本统一用起来还算方便。它能解决什么问题最直接的用途就是处理大文件。如果你有个几十GB的日志文件要分析用read()全部读到内存里显然不现实。用mmap的话你可以只映射文件的一部分或者虽然映射了整个文件但操作系统会按需加载实际占用的物理内存并不会那么大。另一个重要场景是进程间通信。多个Python进程可以映射同一个文件然后通过读写这个共享的内存区域来交换数据。这比用socket或者管道要简单直接特别是数据量大的时候。还有些比较特殊的用途比如某些数据库引擎用mmap来加速数据访问或者一些科学计算程序用它来处理超大的数据文件。总的来说当你需要频繁随机访问大文件或者需要在进程间共享大量数据时mmap就值得考虑了。怎么用起来用mmap之前得先打开文件这跟普通文件操作一样。不过要注意打开模式通常需要可读可写的话就用rb模式。importmmapwithopen(data.bin,rb)asf:# 创建内存映射mmmmap.mmap(f.fileno(),0)# 现在可以像操作字节串一样操作文件内容了datamm[100:200]# 读取100到200字节的内容mm[500:600]bx*100# 写入100个x# 修改会同步到文件mm.flush()# 用完记得关闭mm.close()这里有几个参数需要注意。length参数指定映射多少字节0表示映射整个文件。access参数控制访问权限比如mmap.ACCESS_READ是只读mmap.ACCESS_WRITE是可写写操作会同步到文件mmap.ACCESS_COPY是写时复制修改不会写回文件。如果是进程间共享每个进程都需要映射同一个文件。第一个进程创建文件并映射后面的进程直接映射就行。操作系统会保证数据的一致性。一些实际使用中的细节虽然mmap用起来简单但有些细节不注意容易出问题。文件大小是个需要关注的点。如果你映射的文件可能会增长最好预留足够的空间。可以在创建映射时指定一个比当前文件大的长度mmap会自动扩展文件。但要注意扩展文件的操作在某些系统上可能比较耗时。性能方面mmap在小数据量随机访问时表现很好因为避免了系统调用的开销。但如果是顺序读写大块数据传统的read/write可能更快因为少了缺页中断的开销。错误处理也不能忽视。mmap操作可能会抛出各种异常比如文件不存在、权限不足、内存不足等等。实际使用中最好加上适当的异常处理。还有个容易忽略的问题是修改了映射区域后数据不会立即写回磁盘除非调用flush()或者映射被关闭。如果程序意外退出可能会丢失数据。对于重要数据可以考虑定期flush或者用msync()强制同步。和其他技术的对比和传统的文件IO相比mmap最大的优势是减少了数据拷贝次数。普通read()需要把数据从内核缓冲区拷贝到用户空间而mmap直接让用户程序访问内核缓冲区少了一次拷贝。对于大文件这个优势比较明显。和共享内存相比mmap的优点是数据有文件作为后备存储进程退出后数据不会丢失。而且文件可以跨机器共享通过网络文件系统适用场景更广。缺点是性能可能比纯内存的共享内存稍差一些。在多进程环境下mmap比用Queue或者Pipe传输大量数据要高效因为不需要序列化和反序列化。但同步问题需要自己解决比如用锁或者信号量。Python标准库里还有个array模块它支持内存视图但只能处理内存中的数据不能直接映射文件。如果需要文件持久化还是得用mmap。最后说几句mmap不是银弹它适合特定的场景。如果你需要随机访问大文件或者要在进程间共享大量数据可以试试它。但如果只是顺序读写小文件传统的文件操作可能更简单直接。实际项目中用mmap的话建议先在小规模数据上测试确认没问题再应用到生产环境。特别是多进程共享的时候要仔细考虑同步和一致性问题。# 聊聊Python CFFI当Python想和C说悄悄话在Python的世界里待久了总会遇到一些需要突破语言边界的情况。比如某个计算密集的任务用纯Python写起来太慢或者需要调用一些现成的C语言库。这时候就得想办法让Python和C语言“对话”。今天想聊的CFFI就是这种对话的其中一种方式。它到底是什么CFFI的全称是C Foreign Function Interface字面意思就是“C语言外部函数接口”。这个名字听起来有点学术但拆开来看就明白了——它让Python能够调用用C语言写的函数。可以把它想象成一个翻译官。Python和C语言说着不同的“方言”Python是动态类型、解释执行C是静态类型、编译执行。当Python代码里想调用一个C函数时CFFI就负责在中间做翻译把Python的数据转换成C能理解的格式调用C函数再把C函数返回的结果翻译回Python能理解的格式。不过CFFI这个翻译官有点特别。它不像有些工具需要提前准备一大堆“翻译手册”比如用SWIG要写接口文件而是更像一个现场口译——你直接在Python代码里描述C函数长什么样它就能现场翻译。它能解决什么问题最直接的用途就是性能优化。Python的灵活是有代价的某些计算密集型任务用纯Python写速度可能差C语言几十倍甚至上百倍。用CFFI可以把关键部分用C重写然后在Python里调用既保持了Python的开发效率又获得了C的运行速度。另一个常见场景是复用现有的C库。开源社区有大量优秀的C语言库从加密算法到图像处理从科学计算到硬件驱动。如果每个库都要用Python重写一遍既不现实也没必要。CFFI让你能直接“拿来就用”。还有些情况是不得不和C打交道。比如操作系统的某些底层接口只提供了C的API或者硬件厂商只提供了C语言的驱动SDK。这时候CFFI就成了Python和这些系统之间的桥梁。怎么把它用起来用CFFI的基本流程其实挺直观的。首先得安装它用pip就行。然后通常有两种使用模式官方文档里叫“ABI模式”和“API模式”。ABI模式比较简单粗暴直接加载动态链接库.so或.dll文件然后调用里面的函数。这种方式不需要C编译器参与但功能也有限制比如不能直接操作结构体。API模式就更强大一些。需要写一小段C的声明代码描述要调用的函数和数据结构。CFFI会把这些声明编译成Python能直接调用的模块。这种方式需要C编译器但能做的事情也多得多。举个例子假设有个简单的C函数计算两个整数的和。用CFFI的API模式来调用大概是这样的步骤先定义C函数的声明然后让CFFI编译最后在Python里像调用普通函数一样使用。实际项目中可能会遇到更复杂的情况比如传递字符串、处理结构体、管理内存。CFFI对这些都有相应的处理方式。字符串要转换成C风格的字符数组结构体可以定义成Python的类内存管理要注意避免泄露……这些细节刚开始可能有点绕但用多了就习惯了。一些实践中的体会用CFFI这些年积累了一些不算官方但很实用的经验。如果是调用现成的C库尽量用API模式。虽然多了一步编译但类型检查更严格出错时更容易定位问题。而且性能通常也更好一些。定义C接口时不要一次性把整个头文件都复制过来。只声明真正用到的函数和结构体。这样编译快出错信息也更清晰。那些复杂的宏定义、条件编译能避开就避开实在避不开再想办法。内存管理要特别小心。CFFI提供了几种管理内存的方式最简单的就是用它的new和free函数。但如果是调用现有的C库库函数里分配的内存可能需要用库提供的函数来释放。这个对应关系不能搞混。错误处理也很重要。C函数通常用返回值表示错误而Python用异常。用CFFI时最好在Python层做个包装把C的错误码转换成Python的异常。这样用起来更符合Python的习惯。调试CFFI代码有时会有点棘手。特别是当C代码崩溃时Python的解释器可能直接退出连个错误信息都不留。这时候可以在C代码里加些日志或者用gdb附加到进程上调试。和其他方案的比较让Python调用C代码CFFI不是唯一的选择。常见的还有ctypes、Cython、SWIG这些。ctypes是Python标准库自带的不需要额外安装也不需要C编译器。对于简单的调用场景ctypes确实方便。但它的功能相对有限比如对C的支持就不太好。而且因为是运行时动态绑定类型检查比较弱出错时可能不太好查。Cython是另一种思路。它让写一种类似Python的语言然后编译成C扩展。对于需要大量和C交互的项目Cython写起来可能更自然。但它的学习曲线比CFFI陡一些而且生成的代码可能比较“重”。SWIG更像是一个全自动的绑定生成器。给它一个C/C头文件它能生成多种语言的绑定代码包括Python。对于大型的、接口稳定的库SWIG可能更省事。但它的定制性不如CFFI灵活遇到复杂的C特性时可能得花不少时间调配置。CFFI在这些方案中找到了一个不错的平衡点。它比ctypes强大比Cython轻量比SWIG灵活。特别是它的“即时编译”特性让开发体验很接近纯Python——改完代码直接运行不用手动编译链接。不过没有哪个工具是万能的。如果是调用几个简单的C函数ctypes可能就够了。如果是写一个全新的、性能关键的模块Cython可能更合适。如果是给一个大型的C库做完整的Python绑定SWIG可能更省力。CFFI最适合的场景是调用中小型的C库或者自己写一些C扩展来加速Python代码。最后一点想法技术选型很多时候不是找“最好”的工具而是找“最合适”的工具。CFFI的哲学很Python——追求简洁、明确、实用。它不试图解决所有问题而是在自己的领域里把事情做到足够好。用CFFI的时候能感受到一种“恰到好处”的抽象。它没有把C的所有细节都暴露给Python也没有把Python的所有特性都强加给C。而是在两者之间找到一个平衡点让跨语言调用变得既强大又可控。当然任何跨语言的调用都是有代价的。数据转换的开销、内存管理的复杂性、调试的难度……这些都是要考虑的成本。CFFI的价值在于它把这些成本降到了比较合理的水平。如果项目里确实需要Python和C混合编程CFFI值得认真考虑。它可能不是最出名的方案但很可能是那个“刚刚好”的方案。Python的mmap接口设计得还算友好但底层毕竟是系统调用不同平台的行为可能略有差异。如果代码需要跨平台运行最好在各个平台上都测试一下。工具本身没有好坏关键看用在什么地方。了解每个工具的特点才能在合适的时候做出合适的选择。mmap就是这样一种工具——平时可能用不上但真遇到合适的问题时它能提供一种简洁高效的解决方案。