【通过Cpython3.9源码看看python字符串拼接:“+”为什么比join低效】

news2024/10/4 9:22:11

在这里插入图片描述

基本说明

Python字符串拼接中,使用join()方法比+运算符更高效,主要原因在于字符串对象的不可变性和内存分配策略。

首先,我们要知道Python字符串是不可变的对象。这意味着,每次使用+运算符进行字符串拼接时,Python需要为新的字符串分配一块新的内存,并将原始字符串和要添加的字符串复制到新内存中。这导致了大量的内存分配和复制操作,尤其是在循环中使用+拼接字符串时,这种效率低下的行为会变得更为明显。

举个例子,假设我们有以下代码:

codes = ''
for string in string_list:
    s += string

在这个例子中,每次循环迭代时,都会创建一个新的字符串对象,导致了大量的内存分配和复制操作。

相比之下,使用join()方法能够显著提高拼接效率。这是因为join()方法在执行时,首先会计算拼接后字符串的总长度,并一次性为结果字符串分配足够的内存。然后,它会将所有要拼接的字符串复制到已分配的内存中。这样,内存分配和字符串复制操作只需要执行一次,大大降低了时间和空间复杂度。

例如,使用join()方法的代码如下:

s = ''.join(string_list)

CPython源码中,我们可以看到join()方法首先计算了结果字符串的总长度,并使用_PyUnicode_New一次性为结果字符串分配内存。然后,它遍历输入的字符串列表,将每个字符串复制到结果字符串的相应位置。

相关源码-> “+”

PyObject *
PyUnicode_Concat(PyObject *left, PyObject *right)
{
    PyObject *result;
    Py_UCS4 maxchar, maxchar2;
    Py_ssize_t left_len, right_len, new_len;

    if (ensure_unicode(left) < 0)
        return NULL;

    if (!PyUnicode_Check(right)) {
        PyErr_Format(PyExc_TypeError,
                     "can only concatenate str (not \"%.200s\") to str",
                     Py_TYPE(right)->tp_name);
        return NULL;
    }
    if (PyUnicode_READY(right) < 0)
        return NULL;

    /* Shortcuts */
    if (left == unicode_empty)
        return PyUnicode_FromObject(right);
    if (right == unicode_empty)
        return PyUnicode_FromObject(left);

    left_len = PyUnicode_GET_LENGTH(left);
    right_len = PyUnicode_GET_LENGTH(right);
    if (left_len > PY_SSIZE_T_MAX - right_len) {
        PyErr_SetString(PyExc_OverflowError,
                        "strings are too large to concat");
        return NULL;
    }
    new_len = left_len + right_len;

    maxchar = PyUnicode_MAX_CHAR_VALUE(left);
    maxchar2 = PyUnicode_MAX_CHAR_VALUE(right);
    maxchar = Py_MAX(maxchar, maxchar2);

    /* Concat the two Unicode strings */
    result = PyUnicode_New(new_len, maxchar);
    if (result == NULL)
        return NULL;
    _PyUnicode_FastCopyCharacters(result, 0, left, 0, left_len);
    _PyUnicode_FastCopyCharacters(result, left_len, right, 0, right_len);
    assert(_PyUnicode_CheckConsistency(result, 1));
    return result;
}

源码解释–>“+”

这段代码是用于实现字符串连接的 Python C API 函数 PyUnicode_Concat。下面是逐行的详解和中文注释:

PyObject *
PyUnicode_Concat(PyObject *left, PyObject *right)
{

定义一个名为 PyUnicode_Concat 的函数,它接受两个 PyObject 指针,left 和 right。它将返回一个 PyObject 指针,表示连接后的字符串。

PyObject *result;
    Py_UCS4 maxchar, maxchar2;
    Py_ssize_t left_len, right_len, new_len;

声明一些变量:

  • result:用来存储连接后的字符串对象。
  • maxcharmaxchar2:用于存储两个字符串中最大的 Unicode 字符(4 字节整数)。
  • left_lenright_lennew_len:分别存储左字符串的长度、右字符串的长度和新字符串的长度。
if (ensure_unicode(left) < 0)
        return NULL;

确保 left 是一个 Unicode 字符串对象。如果不是,返回 NULL。

if (!PyUnicode_Check(right)) {
        PyErr_Format(PyExc_TypeError,
                     "can only concatenate str (not \"%.200s\") to str",
                     Py_TYPE(right)->tp_name);
        return NULL;
    }

检查 right 是否为一个 Unicode 字符串对象。如果不是,抛出一个 TypeError 异常,提示只能将 str 对象连接到 str 对象上,并返回 NULL。

if (PyUnicode_READY(right) < 0)
        return NULL;

确保 right 对象已经准备好,如果失败,返回 NULL。

/* Shortcuts */
    if (left == unicode_empty)
        return PyUnicode_FromObject(right);
    if (right == unicode_empty)
        return PyUnicode_FromObject(left);

快捷方式:如果 leftright 是空字符串,则直接返回另一个字符串对象的副本

left_len = PyUnicode_GET_LENGTH(left);
    right_len = PyUnicode_GET_LENGTH(right);
    if (left_len > PY_SSIZE_T_MAX - right_len) {
        PyErr_SetString(PyExc_OverflowError,
                        "strings are too large to concat");
        return NULL;
    }
    new_len = left_len + right_len;

计算左右两个字符串的长度,检查连接后的字符串长度是否会溢出。如果溢出,抛出 OverflowError 异常,并返回 NULL。然后计算连接后的字符串长度。

	maxchar = PyUnicode_MAX_CHAR_VALUE(left);
    maxchar2 = PyUnicode_MAX_CHAR_VALUE(right);
    maxchar = Py_MAX(maxchar, maxchar2);

计算两个字符串中的最大 Unicode 字符值,并存储在 maxchar 变量中。

/* Concat the two Unicode strings */
    result = PyUnicode_New(new_len, maxchar);
    if (result == NULL)
        return NULL;

创建一个新的 Unicode 字符串对象,长度为 new_len,最大字符值为 maxchar。如果创建失败,返回 NULL。


_PyUnicode_FastCopyCharacters(result, 0, left, 0,left_len);
_PyUnicode_FastCopyCharacters(result, left_len, right, 0, right_len);

left 字符串从位置 0 开始的 left_len 个字符复制到 result 字符串的位置 0。然后将 right 字符串从位置 0 开始的 right_len 个字符复制到 result 字符串的位置 left_len

assert(_PyUnicode_CheckConsistency(result, 1));

使用断言检查 result 字符串的一致性,确保它是有效的 Unicode 字符串。最后返回result字符串对象

举例:

在 Python 代码中,如果我们要连接两个字符串,例如:

s1 = "hello"
s2 = "world"
s3 = s1 + s2

在底层,这将调用 PyUnicode_Concat 函数,传入 s1 和 s2 对应的 PyObject 指针,最后返回一个 PyObject 指针,表示连接后的字符串 “helloworld”。

总结: 这段代码实现了 Python 中字符串连接的功能。它首先确保输入是有效的 Unicode 字符串,然后计算连接后的字符串的长度和最大字符值。接着,创建一个新的 Unicode 字符串对象,将输入的两个字符串连接起来。最后,返回连接后的字符串对象。

相关源码及解释–>join

PyObject *
PyUnicode_Join(PyObject *separator, PyObject *seq)
{
    PyObject *res;
    PyObject *fseq;
    Py_ssize_t seqlen;
    PyObject **items;

    fseq = PySequence_Fast(seq, "can only join an iterable");
    if (fseq == NULL) {
        return NULL;
    }

    /* NOTE: the following code can't call back into Python code,
     * so we are sure that fseq won't be mutated.
     */

    items = PySequence_Fast_ITEMS(fseq);
    seqlen = PySequence_Fast_GET_SIZE(fseq);
    res = _PyUnicode_JoinArray(separator, items, seqlen);
    Py_DECREF(fseq);
    return res;
}

这段代码是 PyUnicode_Join 函数的 C 语言实现。该函数是 Python 内置函数,用于将一系列字符串使用分隔符连接在一起。

函数有两个参数:separator 和 seq。其中,separator 是用于连接字符串的分隔符,seq 是需要连接的字符串序列。

函数首先调用 PySequence_Fast 函数来获取一个快速序列对象。如果获取失败,函数会返回 NULL。如果成功,函数会使用 PySequence_Fast_GET_SIZE 和 PySequence_Fast_ITEMS 函数来获取序列的长度和元素。

然后,函数调用 _PyUnicode_JoinArray 函数,将分隔符、元素和长度作为参数传入。_PyUnicode_JoinArray 函数实际上执行连接操作,并返回表示连接后的字符串的新 Unicode 对象。

最后,函数使用 Py_DECREF 函数减少快速序列对象的引用计数,并返回新的 Unicode 对象。需要注意的是,该函数的后续代码无法回调 Python 代码,因此可以确保序列对象 fseq 不会被修改

PyObject *
_PyUnicode_JoinArray(PyObject *separator, PyObject *const *items, Py_ssize_t seqlen)
{
    PyObject *res = NULL; /* the result */
    PyObject *sep = NULL;
    Py_ssize_t seplen;
    PyObject *item;
    Py_ssize_t sz, i, res_offset;
    Py_UCS4 maxchar;
    Py_UCS4 item_maxchar;
    int use_memcpy;
    unsigned char *res_data = NULL, *sep_data = NULL;
    PyObject *last_obj;
    unsigned int kind = 0;

    /* If empty sequence, return u"". */
    if (seqlen == 0) {
        _Py_RETURN_UNICODE_EMPTY();
    }

    /* If singleton sequence with an exact Unicode, return that. */
    last_obj = NULL;
    if (seqlen == 1) {
        if (PyUnicode_CheckExact(items[0])) {
            res = items[0];
            Py_INCREF(res);
            return res;
        }
        seplen = 0;
        maxchar = 0;
    }
    else {
        /* Set up sep and seplen */
        if (separator == NULL) {
            /* fall back to a blank space separator */
            sep = PyUnicode_FromOrdinal(' ');
            if (!sep)
                goto onError;
            seplen = 1;
            maxchar = 32;
        }
        else {
            if (!PyUnicode_Check(separator)) {
                PyErr_Format(PyExc_TypeError,
                             "separator: expected str instance,"
                             " %.80s found",
                             Py_TYPE(separator)->tp_name);
                goto onError;
            }
            if (PyUnicode_READY(separator))
                goto onError;
            sep = separator;
            seplen = PyUnicode_GET_LENGTH(separator);
            maxchar = PyUnicode_MAX_CHAR_VALUE(separator);
            /* inc refcount to keep this code path symmetric with the
               above case of a blank separator */
            Py_INCREF(sep);
        }
        last_obj = sep;
    }

    /* There are at least two things to join, or else we have a subclass
     * of str in the sequence.
     * Do a pre-pass to figure out the total amount of space we'll
     * need (sz), and see whether all argument are strings.
     */
    sz = 0;
#ifdef Py_DEBUG
    use_memcpy = 0;
#else
    use_memcpy = 1;
#endif
    for (i = 0; i < seqlen; i++) {
        size_t add_sz;
        item = items[i];
        if (!PyUnicode_Check(item)) {
            PyErr_Format(PyExc_TypeError,
                         "sequence item %zd: expected str instance,"
                         " %.80s found",
                         i, Py_TYPE(item)->tp_name);
            goto onError;
        }
        if (PyUnicode_READY(item) == -1)
            goto onError;
        add_sz = PyUnicode_GET_LENGTH(item);
        item_maxchar = PyUnicode_MAX_CHAR_VALUE(item);
        maxchar = Py_MAX(maxchar, item_maxchar);
        if (i != 0) {
            add_sz += seplen;
        }
        if (add_sz > (size_t)(PY_SSIZE_T_MAX - sz)) {
            PyErr_SetString(PyExc_OverflowError,
                            "join() result is too long for a Python string");
            goto onError;
        }
        sz += add_sz;
        if (use_memcpy && last_obj != NULL) {
            if (PyUnicode_KIND(last_obj) != PyUnicode_KIND(item))
                use_memcpy = 0;
        }
        last_obj = item;
    }

    res = PyUnicode_New(sz, maxchar);
    if (res == NULL)
        goto onError;

    /* Catenate everything. */
#ifdef Py_DEBUG
    use_memcpy = 0;
#else
    if (use_memcpy) {
        res_data = PyUnicode_1BYTE_DATA(res);
        kind = PyUnicode_KIND(res);
        if (seplen != 0)
            sep_data = PyUnicode_1BYTE_DATA(sep);
    }
#endif
    if (use_memcpy) {
        for (i = 0; i < seqlen; ++i) {
            Py_ssize_t itemlen;
            item = items[i];

            /* Copy item, and maybe the separator. */
            if (i && seplen != 0) {
                memcpy(res_data,
                          sep_data,
                          kind * seplen);
                res_data += kind * seplen;
            }

            itemlen = PyUnicode_GET_LENGTH(item);
            if (itemlen != 0) {
                memcpy(res_data,
                          PyUnicode_DATA(item),
                          kind * itemlen);
                res_data += kind * itemlen;
            }
        }
        assert(res_data == PyUnicode_1BYTE_DATA(res)
                           + kind * PyUnicode_GET_LENGTH(res));
    }
    else {
        for (i = 0, res_offset = 0; i < seqlen; ++i) {
            Py_ssize_t itemlen;
            item = items[i];

            /* Copy item, and maybe the separator. */
            if (i && seplen != 0) {
                _PyUnicode_FastCopyCharacters(res, res_offset, sep, 0, seplen);
                res_offset += seplen;
            }

            itemlen = PyUnicode_GET_LENGTH(item);
            if (itemlen != 0) {
                _PyUnicode_FastCopyCharacters(res, res_offset, item, 0, itemlen);
                res_offset += itemlen;
            }
        }
        assert(res_offset == PyUnicode_GET_LENGTH(res));
    }

    Py_XDECREF(sep);
    assert(_PyUnicode_CheckConsistency(res, 1));
    return res;

  onError:
    Py_XDECREF(sep);
    Py_XDECREF(res);
    return NULL;
}

这段代码是 PyUnicode_Join 函数中被调用的 _PyUnicode_JoinArray 函数的 C 语言实现。该函数实现了将一组字符串使用分隔符连接起来的功能。该函数的参数包括分隔符 separator、字符串数组 items,以及 items 的长度 seqlen。
函数首先判断 items 是否为空,如果为空则直接返回一个空的 Unicode 对象。
接下来,函数会处理 separator。如果 separator 为 NULL,则使用空格作为默认分隔符;否则检查 separator 是否为 Unicode 对象,并获取其长度和最大字符值。
接着,函数进行了预处理,计算连接后的字符串需要的总空间,并检查 items 中的所有元素是否都是字符串。
然后,函数创建了一个 Unicode 对象 res,用于存储连接后的字符串。在创建 res 时,函数使用了 items 中所有元素的长度之和作为参数,同时将其中最大的字符值作为 Unicode 对象的最大字符值。
最后,函数使用 memcpy 函数将 items 中的所有元素和分隔符连接起来,并将结果存储在 res 中。

总结

总结一下,Python字符串拼接中,join()方法比+运算符更高效,主要原因在于:

  1. 字符串对象的不可变性导致使用+运算符进行拼接时需要大量的内存分配和复制操作。
  2. join()方法一次性分配内存并复制所有字符串,降低了时间和空间复杂度。
    因此,在实际编程中,为了提高字符串拼接的效率,建议使用join()方法。

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

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

相关文章

Vue2-黑马(四)

目录&#xff1a; &#xff08;1&#xff09;axios-响应格式 &#xff08;2&#xff09;axios-拦截器 &#xff08;3&#xff09;vue2-条件渲染 &#xff08;4&#xff09;vue2-列表渲染 &#xff08;1&#xff09;axios-响应格式 下面看axios的返回响应对象的内部组成 后…

【grpc02】安装protobuf和protoc

目录 Windows环境 下载通用编译器 配置环境变量 安装go专用的protoc的生成器 GoLang中安装插件 如何使用protobuf呢&#xff1f; Mac环境 Protoc安装 Protoc-gen-go的安装 Windows环境 下载通用编译器 下载地址&#xff1a;v3.20.1 Releases protocolbuffers/pr…

【优化算法】使用遗传算法优化MLP神经网络参数(TensorFlow2)

文章目录任务查看当前的准确率情况使用遗传算法进行优化完整代码任务 使用启发式优化算法遗传算法对多层感知机中中间层神经个数进行优化&#xff0c;以提高模型的准确率。 待优化的模型&#xff1a; 基于TensorFlow2实现的Mnist手写数字识别多层感知机MLP # MLP手写数字识别…

Java支付SDK接口远程调试 - 支付宝沙箱环境【公网地址调试】

文章目录1.测试环境2.本地配置3. 内网穿透3.1 下载安装cpolar内网穿透3.2 创建隧道4. 测试公网访问5. 配置固定二级子域名5.1 保留一个二级子域名5.2 配置二级子域名6. 使用固定二级子域名进行访问转发自CSDN远程穿透的文章&#xff1a;Java支付宝沙箱环境支付&#xff0c;SDK接…

Linux命令·traceroute

通过traceroute我们可以知道信息从你的计算机到互联网另一端的主机是走的什么路径。当然每次数据包由某一同样的出发点&#xff08;source&#xff09;到达某一同样的目的地(destination)走的路径可能会不一样&#xff0c;但基本上来说大部分时候所走的路由是相同的。linux系统…

移动端项目开发总结(一)

移动端项目开发总结&#xff08;一&#xff09; 前阵子做租赁项目&#xff0c;风风火火的上线&#xff0c;趁现在还没忘&#xff0c;把用到的东西整理以下&#xff0c;算是对于这个项目的回顾吧。 特效一 &#xff1a; 移动端适配 需求 移动端适配&#xff0c;采用rem单位。…

深入理解Java虚拟机——Java内存区域

1.前言 Java内存区域也叫运行时数据区域&#xff0c;要记得把Java内存模型&#xff08;JMM区分开来&#xff09;。 根据线程是否共享可以把运行时数据区如上图所分。 线程共享 堆内存方法区 线程私有 栈内存 本地方法栈虚拟机栈 程序计数器 接下来&#xff0c;将逐个介绍…

什么是文件传输协议,文件传输协议又是怎么工作的

文件传输协议FTP是一种仍在使用的协议&#xff0c;在上载和下载文件时仍然比较流行&#xff0c;通常是那些太大的文件&#xff0c;需要花费很长时间才能通过常规电子邮件程序作为附件下载进行传输。 从技术上讲&#xff0c;它是“文件传输实用程序”&#xff0c;是许多TCP / I…

腾讯云4核8G12M轻量服务器配置性能评测

腾讯云轻量4核8G12M服务器&#xff0c;之前是4核8G10M配置&#xff0c;现在公网带宽和月流量包整体升级&#xff0c;12M公网带宽下载速度可达1536KB/秒&#xff0c;系统盘为180GB SSD盘&#xff0c;每月2000GB免费流量&#xff0c;腾讯云百科来详细说下4核8G12M轻量应用服务器配…

碳化硅材料在功率半导体中的优劣

开关电源工作频率的提高受到开关损耗的制约 开关电源的工作频率是指开关变换器操作的频率。在开关电源中&#xff0c;一个开关变换器被用来将直流&#xff08;DC&#xff09;能源转换为可用于电子设备的交流&#xff08;AC&#xff09;能源。开关变换器的基本原理是通过对开关…

3.4 函数的单调性和曲线的凹凸性

学习目标&#xff1a; 如果我要学习函数的单调性和曲线的凹凸性&#xff0c;我会采取以下几个步骤&#xff1a; 理解概念和定义&#xff1a;首先&#xff0c;我会学习单调性和凹凸性的定义和概念。单调性是指函数的增减性质&#xff0c;可以分为单调递增和单调递减&#xff1b…

Python使用PyQt5实现指定窗口置顶

文章目录前言一、网上找到的代码二、尝试与借鉴后的代码——加入PyQt界面1.引入库2.主代码3.完整主代码4.UI界面代码总结前言 工作中&#xff0c;同事随口提了一句&#xff1a;要是能让WPS窗口置顶就好了&#xff0c;老是将窗口切换来切换去的太麻烦了。 然后&#xff0c;这个…

docker-compose 安装nginx php mysql phpadmin

一 摘要 本文主要介绍基于docker docker-compose 安装 lnmp 三件套&#xff0c;以及用phpmysadmin 验证下部署可正确。 二 环境信息 2.1 操作系统 [root2023001 ~]# cat /etc/centos-release CentOS Linux release 7.9.2009 (Core) [root2023001 ~]#2.2 docker [root20230…

【opencv】图像数字化——认识OpenCV中的Mat类( 7 访问多通道Mat对象中的值)

7 访问多通道Mat对象中的值 7.1使用成员函数at() #include <opencv2/core/core.hpp> #include<iostream> using namespace std; using namespace cv; int main() {Mat mm (Mat_<Vec3f>(2, 2) << Vec3f(1, 11, 21), Vec3f(2, 12, 32), Vec3f(3, …

C++【深入理解多态】

文章目录一、多态概念与实现&#xff08;1&#xff09;多态的概念&#xff08;2&#xff09;怎么构成多态&#xff08;3&#xff09;虚函数重写的2个例外&#xff08;4&#xff09;经典剖析巩固知识点&#xff08;5&#xff09; override 和 final&#xff08;6&#xff09;小总…

YOLO算法改进指南【初阶改进篇】:2.改进DIoU-NMS,SIoU-NMS,EIoU-NMS,CIoU-NMS,GIoU-NMS

非极大值抑制(Non-maximum Suppression (NMS))的作用简单说就是模型检测出了很多框,我应该留哪些。 本篇将演示如何修改:NMS、Merge-NMS、Soft-NMS、CIoU-NMS、DIoU-NMS、GIoU-NMS、EIoU-NMS、SIoU-NMS 1. NMS过程 NMS过程 For a prediction bounding box B, the model c…

基于JDK11从源码角度剖析可重入锁ReentrantLock的获取锁和解锁

ReentrantLock是可重入的独占锁&#xff0c;同时只能有一个线程可以获取该锁&#xff0c;其他获取该锁的线程会被阻塞而被放入该锁的AQS阻塞队列里面。 ReentrantLock是JUC包提供的显式锁的一个基础实现类&#xff0c;实现了Lock接口。我们先来看下ReentrantLock的类图&#x…

SpringBoot WebSocket服务端创建

引入maven <!--websocket--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>新建WebSocket配置文件 import org.springframework.context.annotatio…

【蓝桥杯嵌入式】第十四届蓝桥杯嵌入式省赛(第一场)客观题及详细题解

题1 解析  编码器&#xff0c;具有编码功能的逻辑电路&#xff0c;能将每一个编码输入信号变换为不同的二进制的代码输出&#xff0c;是一个组合逻辑电路。 答案 ABC 题2 解析   减法计数器的计数值到0时&#xff0c;会产生一个重装载值&#xff0c;此处重载后就会变成111…

改进YOLO系列:CVPR2023最新 PConv |提供 YOLOv5 / YOLOv8 模型 YAML 文件

论文链接:https://arxiv.org/pdf/2303.03667v2.pdf 一、论文介绍 为了设计快速神经网络,许多工作都集中在减少浮点运算(FLOPs)的数量上。然而,作者观察到FLOPs的这种减少不一定会带来延迟的类似程度的减少。这主要源于每秒低浮点运算(FLOPS)效率低下。 为了实现更快的…