《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 += s
    return 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_time
        join_cost_time = join.cost_time
        print(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,所以上面例子是不是就说得通啦。😉

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

每日学习笔记:C++ STL 的Array

Array定义 Array模板有两个参数,一个是元素类型,一个是数组大小 Array初始化 Array的操作 Array当作C数组 Array的Tuple接口

搜维尔科技:捕获、分析、优化,使用 Xsens Ergo 创建更安全的工作空间

简化人体工程学分析,优先考虑员工福祉,并利用客观数据和见解提高生产力。 捕获。分析。优化。使用 Xsens Ergo 创建更安全的工作空间 1.质量数据 使用高质量、客观且经过验证的运动数据进行详细的人体工程学分析 2.随处使用 在最具挑战性的工作环境中…

黑马点评-异步秒杀实现

异步秒杀思路 我们来回顾一下下单流程 当用户发起请求,此时会请求nginx,nginx会访问到tomcat,而tomcat中的程序,会进行串行操作,分成如下几个步骤 1、查询优惠卷 2、判断秒杀库存是否足够 3、查询订单 4、校验是…

数据库的筛选条件

【一】筛选过滤条件 【1】完整的查询语句 -- 查询当前表中的全部数据select * from 表名 where 筛选条件;​-- 查询当前表中的指定字段的数据select 字段名,字段名 from 表名 where 筛选条件;# 执行顺序from where select ​select 你选择的列1, 你选择的列2, ... from 查询的…

UE5.1_使用技巧(常更)

UE5.1_使用技巧(常更) 1. 清除所有断点 运行时忘记蓝图中的断点可能会出现运行错误的可能,务必运行是排除一切断点,逐个排查也是办法,但是在事件函数多的情况下会很复杂且慢节奏,学会一次性清除所有很有必…

Vision Transformer 代码实现

论文链接:An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale 最近开始恶补CV了(指->新建文件夹)。作为CV Transformer的开山大作,首先要学习的就是ViT(Vision Transformer)…

2024年3月10日 十二生肖 今日运势

小运播报:2024年3月10日,星期日,农历二月初一 (甲辰年丁卯月癸酉日),法定节假日。 红榜生肖:龙、牛、蛇 需要注意:鸡、狗、兔 喜神方位:东南方 财神方位:…

oracle报错(ORA-06575: 程序包或函数 WM_CONCAT 处于无效状态)

之前的项目突然出现一个错误,ORA-06575: 程序包或函数 WM_CONCAT 处于无效状态 对应的sql如下 SELECT u.LOGIN_NAME,u.REAL_NAME,u.ID,wm_concat(u.ORG_ID) AS ORG_ID,wm_concat(u.ORG_NAME) AS ORG_NAME,wm_concat(u.ORG_CODE) AS ORG_CODE,u.SEX,u.PHONE,u.EMAIL,u.AVATAR…

计算两帧雷达数据之间的变换矩阵

文章目录 package.xmlCMakeLists.txtpoint_cloud_registration.cc运行结果 package.xml <?xml version"1.0"?> <package format"2"><name>point_cloud_registration</name><version>0.0.0</version><descriptio…

Dbeaver:Ubuntu Linux 20.04 mysql 驱动损坏或者没有驱动,无法联网更新下载

下载方法&#xff1a; https://blog.csdn.net/wangpaiblog/article/details/112057533 Ubuntu Linux 20.04 (Architecture Independent), DEB Package 下载地址&#xff1a; https://downloads.mysql.com/archives/c-j/ 安装deb&#xff1a; sudo dpkg -i mysql-connector-java…

存储引擎的简介

简介&#xff1a; 1.在mysql存储引擎可以说就是指表的类型&#xff0c;可以称为表处理器&#xff0c;以表的形式存储。 2.他的功能就是接收上层传下来的指令&#xff0c;然后对表中的数据进行提取写入操作。 目的&#xff1a; 为了管理方便&#xff0c;我们把连接管理&#xf…

【Linux】Linux C编程

gcc编译器 gcc [options] [filenames] 其中&#xff0c;options是编译器所需要的选项参数&#xff0c;filenames是文件名。 gcc编译过程 C语言编译过程一般可以分为预处理、编译、汇编、链接四个步骤。 1.预处理阶段 预处理阶段主要处理宏定义和include&#xff0c;并进行语…

List(CS61B学习记录)

问题引入 上图中&#xff0c;赋给b海象的weight会改变a海象的weight&#xff0c;但x的赋值又不会改变y的赋值 Bits 要解释上图的问题&#xff0c;我们应该从Java的底层入手 相同的二进制编码&#xff0c;却因为数据类型不同&#xff0c;输出不同的值 变量的声明 基本类型…

使用Git将代码上传至代码托管平台GitCode

使用像GitLbi、GitHub、Gitee等代码托管平台用于版本控制非常滴方便&#xff0c;能够跟踪代码的变化和历史记录&#xff0c;方便管理和回滚&#xff0c;还允许多个开发者同时在一个项目上进行开发和协作&#xff0c;提高团队协作效率。 这些平台的代码托管和上传方式都大同小异…

MongoDB开启事务

MongoDB开启事务 配置单节点。到路径C:\Program Files\MongoDB\Server\4.0\bin 使用记事本以管理员权限打开文件mongod.cfg添加如下配置&#xff1a; replication:replSetName: rs02. 重启MongoDB服务 3. 重启后执行命令 rs.initiate()

java集合题库详解

1. Arraylist与LinkedList区别 可以从它们的底层数据结构、效率、开销进行阐述哈 ArrayList是数组的数据结构&#xff0c;LinkedList是链表的数据结构。 随机访问的时候&#xff0c;ArrayList的效率比较高&#xff0c;因为LinkedList要移动指针&#xff0c;而ArrayList是基于索…

【嵌入式高级C语言】10:C语言文件

文章目录 1 文件的概述1.1 文件分类&#xff08;存储介质&#xff09;1.2 磁盘文件分类&#xff08;存储方式&#xff09;1.3 二进制文件和文本文件的区别 2 文件缓冲区3 文件指针4 文件的API4.1 打开文件4.2 关闭文件4.3 重新定位流4.3.1 fseek4.3.2 ftell4.3.3 rewind 4.4 字…

基于SpringBoot的快递配送规划系统的设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 相关技术 3 1.1 Spring Boot框架 3 1.2 Vue框架 4 1.4 Bootstrap框架 4 1.5 JQuery技术 5 1.6 Ajax技术 5 1.7 ECharts 5 1.8 MySQL 6 1.9本章小结 6 2 系统分析 7 2.1 需求分析 7 2.2 非功能需求 10 2.3 本章小结 10 3 系统设计 11 3.1 …

如何发布新华网稿件,新华网报价多少钱?

在当今信息爆炸的时代&#xff0c;媒体的重要性不言而喻。而作为国内最具影响力的媒体之一&#xff0c;新华网更是备受关注。那么&#xff0c;作为一名公关从业者或者自媒体人士&#xff0c;如何能够在媒介多多网成功发布新华网的稿件呢&#xff1f;接下来&#xff0c;我们就来…

基于PSO粒子群算法的三角形采集堆轨道优化matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 假设一个收集轨道&#xff0c;上面有5个采集堆&#xff0c;这5个采集堆分别被看作一个4*20的矩阵&#xff08;下面只有4*10&#xff09;&#xff0c;每个模块&…