《Python源码剖析》之字符串拼接的一个效率问题

前言

我们常用的字符串拼接方法有两个,一个是通过“+”号实现字符串的拼接,还一个就是通过join方法来实现拼接,前者在写法上更加便利,和数字之间的加法运算一样,通常只有两个运算对象,只不过他们的运算规则有所不同,字符的加法规则是“拼接”,数字的加法规则是“数值相加”;而join方法处理的对象通常是多个字符串,他们使用相同的拼接符号进行拼接最终得到一个字符串。值得注意的是,除了操作对象的个数不同以外,这两个功能几乎可以相互平替对方。

例子

来看一个具体例子:分别使用"+“方法和"join"方法实现n个字符串的拼接,如果使用”+"号实现可能会相对复杂:需要一个额外的for循环,因为它一次性只能操作两个字符串,而使用join则方便很多,具体代码实现如下,当然除了简单实现这两个方法外,还实现了clock这个装饰器用来统计执行的耗时和空间大小(粗略计算)。

from tools import clock@clock(True, False)
def add(s_list):res = ''for s in s_list:res += sreturn res@clock(True, False)
def join(s_list):return ''.join(s_list)def main():for i in range(9):s_count = int(pow(10, i))s_list = ['abc' for _ in range(s_count)]add(s_list)join(s_list)add_cost_time = add.cost_timejoin_cost_time = join.cost_timeprint(f'字符串的个数:{s_count} add耗时:{add_cost_time}ns join耗时:{join_cost_time}ns', end=' ')if join_cost_time > 0:print(f'add耗时是join的{add_cost_time // join_cost_time}倍')else:print()if __name__ == '__main__':main()

执行结果如下:image.4d37ae48da1e11ee9de617490ed73bd0.png
通过运行结果可以发现,随着操作的字符串的个数的增加,add方法和join方法他们使用的空间大小几乎保持一致(因为得到的结果是一样的),但是从10^5这个量级开始,add方法的耗时就比join方法的耗时明显高很多,并且每增加一个量级,耗时也会相应增加一个量级。那么为什么会有这样的一个结果呢?

源码探索

如果想知道为什么,那就必须要搞清楚这两个方法的实现方式和细节,才能搞明白为什么会有如此大的差距。如果你经常看某个方法的具体实现方式的话,以join方法为例,我相信你肯定会立马按住ctrl键,然后鼠标左键(当然这里不同的编辑器和快捷键会有所差别),跳到它的源代码:image.8b96f5bcda1a11ee9de617490ed73bd0.png
可惜这次不幸的是:它只给你留下了一段注释和和一个占位符pass,通过注释可以知道,它告诉了我们join方法的功能就是通过指定的分隔符来拼接多个字符串的,但是却没有透露给你它的实现细节。
对于有一定经验的小伙伴来说可能已经猜到答案了:它的实现在"它的源码中",在更深的一个层次。没错,它的实现在python的源代码中,在c语言这一层。通过下面这个图你可能就知道了:

image.6d1b1712da1a11ee9de617490ed73bd0.png
str作为python中最常用的内建对象之一,当然也在"Objects"这个目录中。(至于它是如何找到并调用objects中对应方法的,这个问题可以留给大家去探索,虽然我也还没搞明白🙃🙃🙃 )

如何找源码

在进行源码分析之前,首要的任务就是如何找到它,最重要的一个参考就是如上的截图,它列举出来了python源代码中每个目录代表的含义,其中,Lib和Modules中包含了所有的标准库,Objects包含了所有的Python内建对象,通过这三个目录我们应该就可以找到大部分我们需要的内容了。

注意:虽然这个是py2(具体一点是py2.4左右)的,但是根据我的对比,这些目录的含义基本上是没有变化的,只不过它内部的具体内容(特别是源代码)可能发生了很大的变化,如果你的版本越高的话。就拿我现在看的是py3.11的代码来看,它的源代码几乎是重新写了一遍(虽然只对比了几处)。

join源码分析

str是python的内置对象,因此它的源代码应该在Objects目录中,具体的位置可以根据该目录下文件的命名来判断(这个方法可能有点愚蠢,因为完全凭经验,没有具体的逻辑,主要是我也没有找到更好的方法😅 )。这里join方法的实现就在这个join.h文件中,具体方法是(bytes_join)(PyObject *sep, PyObject *iterable)。我不知道大家第一眼看到这个源码的感觉是什么样的,如果你对c语言掌握的比较好的话,可能会感到很亲切;如果你像我一样只是了解一点(对c语言的掌握已经停留在了大一学习那会儿…)的话可能会比较头疼哈哈哈。不过这些都不重要,因为当我真正沉下心去看还是能够理解它的大概意思的,看的过程中特别要注意它的注释变量名(对于python这样的知名项目的源码,你绝对可以相信它取的变量名能够达到“见名之意”的作用),这两个我认为是理解的它的关键切入点。此外,还可以借助强大的AI来协助我们理解带代码,如下图所示,它基本上完全地解释了整个方法的步骤。

image.bfd50166da1f11ee9de617490ed73bd0.png

接下来我们进入正题,排除掉开头的一些逻辑判断,我们可以将这个方法的核心逻辑分为三点:

  1. 计算出序列中所有字符串拼接起来需要开辟的空间

    • 在这个循环计算的过程中可能有两个报错情况,一个是itemlen > PYSSIZE_T_MAX - szseplen > PY_SSIZE_T_MAX - sz,这个主要是防止需要开辟的空间大于最大值PYSSIZE_T_MAX
    • 另外一个报错就是sequence changed size during iteration,这个报错我相信大家比较熟悉,一不小心在循环中修改列表的大小就会报出这个错误。
  2. 申请空间

  3. 写入数据

注意:这里是先计算出需要开辟的空间,然后只进行一次空间的申请。

image.e9263facdae311ee9de617490ed73bd0.png

"+"法的源码分析

还是和上面的一样,如果对c语言了解比较浅的同学,可以借助强大的ai来协助我们一起来理解源码:

image.09987e84daea11ee9de617490ed73bd0.png
抛开一些判断的逻辑,其大致的核心逻辑如下:

  • 1.计算出旧字符串的长度
  • 2.根据旧字符串的长度之和创建新的字符串对象
  • 3.分别将旧的left和right字符串写入到新的字符串中

注意:这里创建新的对象时就会进行空间的申请

image.ffe466cade2011ee9de617490ed73bd0.png
大家有没有发现,不管是join方法还是“+”法方法,都会创建新的对象,进行一次空间申请,但是细心的小伙伴一定发现了,如果随着操作对象的数量增加,join方法始终都只需要进行一次空间的申请,而“+”法方法随着操作对象的数量的增加,它申请空间的次数也会随之增加,准确的说:如果有n(n>=2)个操作对象,那么“+”法需要进行n-1次空间申请,假设它们每次申请空间的耗时都相同,那么对n个对象进行拼接的耗时比就是:“+”法/join =n-1/1,所以上面例子是不是就说得通啦。😉

更多内容可以前往博主的个人博客系统:白日梦想园。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/733064.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新贵Claude 3家族强势登场,AI领域掀起新一轮浪潮!

人工智能领域的风云再起,Anthropic公司日前放出狠招,推出了全新的大模型家族Claude 3系列。Claude 3由三款不同级别的大模型组成,分别是Claude 3 Haiku、Claude 3 Sonnet和Claude 3 Opus。这一系列产品的推出,不仅扩充了AI生态,更是对OpenAI的GPT-4等龙头产品发出了挑战。让我们…

函数柯里化:JavaScript中的高级技巧

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…

ospf静态路由实验简述

1、ospf静态路由实验简述 实验拓扑图 实验命令 r2: sys sysname r2 undo info enable int loopb 0 ip add 2.2.2.2 32 quit int e0/0/0 ip add 23.1.1.2 24 quit ospf 1 area 0 network 23.1.1.0 0.0.0.255 network 2.2.2.2 0.0.0.0 ret r3: sys sysname r3 undo info enable …

python 蓝桥杯之动态规划入门

文章目录 DFS滑行(DFS 记忆搜索) 思路: 要思考回溯怎么写(入参与返回值、递归到哪里,递归的边界和入口) DFS 滑行(DFS 记忆搜索) 代码分析: 学会将输入的数据用二维列表…

变换,动画

面试题——需求:在不知道父元素与子元素的宽高时 如何让子元素在父元素内居中? 1.定位 父相子绝 2.子元素 top:50% left:50% 3.子元素 transform: translate(-50%,-50%) .parent{height: 500px;background-color: red;position: relative;}.c…

在QDialog中嵌入QML

在一些一开始使用QWidget的项目,现由于要支持的硬件及系统已升级,可以很好的使用QML。在这种情况下,就需要通过QWidget与QML混合使用的方式来慢慢把整个项目过渡到纯QML工程。这时在QWidget中嵌入QML是经常要做的事,现就说一说在Q…

Lesson 6 Convolutional Neural Network(CNN)

听课(李宏毅老师的)笔记,方便梳理框架,以作复习之用。本节课主要讲了CNN的适用范围,整体架构与工作流程,CNN的应用,CNN的缺点以及解决方法。 1. CNN的输入与输出 CNN是专门为了图像而设计的一…

面试经典150题——合并两个有序链表

You just work on it. Time will do the rest! 1. 题目描述 2. 题目分析与解析 2.1 思路一 这个题目还是比较简单的,通过分析题目,我们可以知道题目中关键信息为: 所以我们只需要从前向后遍历两个链表,在两个链表不空的情况下&…

HTML静态网页成品作业(HTML+CSS)——原神介绍设计制作(4个页面)

🎉不定期分享源码,关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 🏷️本套采用HTMLCSS,未使用Javacsript代码,共有4个页面。 二、作品演示 三、代…

YOLOv8.1.0安装

【YOLO】YOLOv8训练环境配置 python 3.8.18 cuda 11.3.1 cudnn 8.2.1 pytorch 1.12.1-gpu版 - 知乎 (zhihu.com) 一、Anaconda 默认装好了可用的Anaconda,安装教程见Win10系统anaconda安装 - 知乎 (zhihu.com) 二、在虚拟环境下用conda安装 1.创建虚拟环境 …

【力扣白嫖日记】1164.指定日期的产品价格

前言 练习sql语句,所有题目来自于力扣(https://leetcode.cn/problemset/database/)的免费数据库练习题。 今日题目: 1164.指定日期的铲平价格 表:Products 列名类型product_idintnew_priceintchange_datedate (pr…

记录汇川:IO隔离编程

IO隔离:方便程序修改 无论是输入点坏了还是输出点坏了,或者人为接错线,或者对调点,我们只需要更改IO隔离得输入输出就可以了。方便。 停止按钮外接常闭,里面也使用常闭,为了断线检测功能(安全)&#xff…

300分钟吃透分布式缓存-23讲:Redis是如何淘汰key的?

淘汰原理 首先我们来学习 Redis 的淘汰原理。 系统线上运行中,内存总是昂贵且有限的,在数据总量远大于 Redis 可用的内存总量时,为了最大限度的提升访问性能,Redis 中只能存放最新最热的有效数据。 当 key 过期后,或…

一个爬虫自动化数据采集的故事~

目录 一、原文二、故事前半段背景内容三、正经的讲点DrissionPage知识四、故事的收尾 一、原文 原文来自一个爬虫自动化数据采集的故事~ , 建议点击链接看文章末尾的视频笔者不擅长自动化,一个小小故事分享给大家,仅个人观点 二、故事前半段背景内容 …

IP-guard邮件管控再升级,记录屏幕画面,智能阻断泄密邮件

邮件是工作沟通以及文件传输的重要工具,却也成为了信息泄露的常见渠道。员工通过邮件对外发送了什么内容,是否含有敏感信息都无从得知,机密通过邮件渠道外泄也难以制止。想要防止企业的重要信息通过邮件方式泄露,我们不仅需要通过技术措施对外发邮件的行为进行规范,也要对…

L-2:插松枝(Python)

作者 陈越 单位 浙江大学 人造松枝加工场的工人需要将各种尺寸的塑料松针插到松枝干上,做成大大小小的松枝。他们的工作流程(并不)是这样的: 每人手边有一只小盒子,初始状态为空。每人面前有用不完的松枝干和一个推送…

手机号验证码重新发送

前文叙述 很久以前做的一个 demo ,纯 HTML 、CSS、js 制作,一定时间段之后才可以重新发送验证码,如 60s 后再次发送验证码,在该时间段内发送验证码按钮为禁用状态,实战开发过程也亦是同理,因此记录一手。 一…

【笔记】Android ServiceStateTracker 网络状态变化逻辑及SPN更新影响

业务简介 在网络状态变化的时候(数据或WiFi),会更新SPN。 基于Android U的代码分析。 分类:SPN Data_Dic-的博客-CSDN博客 功能逻辑 状态说明 飞行模式下注册上WFC的话,注册状态MD上报 regState: NOT_REG_MT_NOT…

一文读懂MySQL7大日志(slow、redo、undo、bin、relay、general、error)

Slow Log 简介 用于记录执行时间超过指定值的 SQL 语句的详细信息,多用于调试和监控。 配置 因为开启会略微影响性能,所以默认没有开启,所以需要配置。 查看是否开启 show variables like %slow%; ------------------------------------…

计算机找不到api-ms-win-core-path-l1-1-0的5种解决方法

在计算机使用过程中,我们可能会遇到各种问题,其中之一就是找不到某些系统文件。最近,许多用户反映他们在使用电脑时遇到了“找不到api-ms-win-core-path-l1-1-0文件”的问题。这个问题通常出现在Windows操作系统中,可能会影响到一…