返回顶部
首页 > 资讯 > 后端开发 > Python >Python内建类型bytes实例代码分析
  • 886
分享到

Python内建类型bytes实例代码分析

2023-06-30 14:06:30 886人浏览 八月长安

Python 官方文档:入门教程 => 点击学习

摘要

这篇文章主要讲解了“python内建类型bytes实例代码分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python内建类型bytes实例代码分析”吧!1 bytes和str之间的关系不

这篇文章主要讲解了“python内建类型bytes实例代码分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python内建类型bytes实例代码分析”吧!

1 bytes和str之间的关系

不少语言中的字符串都是由字符数组(或称为字节序列)来表示的,例如C语言

char str[] = "Hello World!";

由于一个字节最多只能表示256种字符,要想覆盖众多的字符(例如汉字),就需要通过多个字节来表示一个字符,即多字节编码。但由于原始字节序列中没有维护编码信息,操作不慎就很容易导致各种乱码现象。

Python提供的解决方法是使用Unicode对象(也就是str对象),Unicode口语表示各种字符,无需关心编码。但是在存储或者网络通讯时,字符串对象需要序列化成字节序列。为此,Python额外提供了字节序列对象——bytes。

str和bytes的关系如图所示:

Python内建类型bytes实例代码分析


str对象统一表示一个字符串,不需要关心编码;计算机通过字节序列与存储介质和网络介质打交道,字节序列用bytes对象表示;存储或传输str对象时,需要将其序列化成字节序列,序列化过程也是编码的过程。

2 bytes对象的结构:PyBytesObject

C源码

typedef struct {    PyObject_VAR_HEAD    Py_hash_t ob_shash;    char ob_sval[1];    } PyBytesObject;

源码分析

字符数组ob_sval存储对应的字符,但是ob_sval数组的长度并不是ob_size,而是ob_size + 1.这是Python为待存储的字节序列额外分配了一个字节,用于在末尾处保存’\0’,以便兼容C字符串。

ob_shash:用于保存字节序列的哈希值。由于计算bytes对象的哈希值需要遍历其内部的字符数组,开销相对较大。因此Python选择将哈希值保存起来,以空间换时间(随处可见的思想,hh),避免重复计算。

图示如下:

Python内建类型bytes实例代码分析

3 bytes对象的行为

3.1 PyBytes_Type

C源码:

PyTypeObject PyBytes_Type = {    PyVarObject_HEAD_INIT(&PyType_Type, 0)    "bytes",    PyBytesObject_SIZE,    sizeof(char),    // ...    &bytes_as_number,                               &bytes_as_sequence,                             &bytes_as_mapping,                              (hashfunc)bytes_hash,                           // ...};

数值型操作bytes_as_number:

static PyNumberMethods bytes_as_number = {    0,                  0,                  0,                  bytes_mod,      };

bytes_mod:

static PyObject *bytes_mod(PyObject *self, PyObject *arg){    if (!PyBytes_Check(self)) {        Py_RETURN_NOTIMPLEMENTED;    }    return _PyBytes_FORMatEx(PyBytes_AS_STRING(self), PyBytes_GET_SIZE(self),                             arg, 0);}

可以看到,bytes对象只是借用%运算符实现字符串格式化,并不是真正意义上的数值运算(这里其实和最开始的分类标准是有点歧义的,按标准应该再分一个“格式型操作”,不过灵活处理也是必须的):

>>> b'msg: a = %d, b = %d' % (1, 2)b'msg: a = 1, b = 2'

序列型操作bytes_as_sequence:

static PySequenceMethods bytes_as_sequence = {    (lenfunc)bytes_length,     (binaryfunc)bytes_concat,     (ssizeargfunc)bytes_repeat,     (ssizeargfunc)bytes_item,     0,                      0,                      0,                      (objobjproc)bytes_contains };

bytes支持的序列型操作包括以下5个:

  • bytes_length:查询序列长度

  • bytes_concat:将两个序列合并为一个

  • bytes_repeat:将序列重复多次

  • bytes_item:取出给定下标的序列元素

  • bytes_contains:包含关系判断

关联型操作bytes_as_mapping:

static PyMappingMethods bytes_as_mapping = {    (lenfunc)bytes_length,    (binaryfunc)bytes_subscript,    0,};

可以看到bytes支持获取长度和切片两个操作。

3.2 bytes_as_sequence

这里我们主要介绍以下bytes_as_sequence相关的操作

bytes_as_sequence中的操作都不复杂,但是会有一个“陷阱”,这里我们以bytes_concat操作来认识一下这个问题。C源码如下:

static PyObject *bytes_concat(PyObject *a, PyObject *b){    Py_buffer va, vb;    PyObject *result = NULL;    va.len = -1;    vb.len = -1;    if (PyObject_GetBuffer(a, &va, PyBUF_SIMPLE) != 0 ||        PyObject_GetBuffer(b, &vb, PyBUF_SIMPLE) != 0) {        PyErr_Format(PyExc_TypeError, "can't concat %.100s to %.100s",                     Py_TYPE(b)->tp_name, Py_TYPE(a)->tp_name);        Goto done;    }        if (va.len == 0 && PyBytes_CheckExact(b)) {        result = b;        Py_INCREF(result);        goto done;    }    if (vb.len == 0 && PyBytes_CheckExact(a)) {        result = a;        Py_INCREF(result);        goto done;    }    if (va.len > PY_SSIZE_T_MAX - vb.len) {        PyErr_NoMemory();        goto done;    }    result = PyBytes_FromStringAndSize(NULL, va.len + vb.len);    if (result != NULL) {        memcpy(PyBytes_AS_STRING(result), va.buf, va.len);        memcpy(PyBytes_AS_STRING(result) + va.len, vb.buf, vb.len);    }  done:    if (va.len != -1)        PyBuffer_Release(&va);    if (vb.len != -1)        PyBuffer_Release(&vb);    return result;}

bytes_concat源码大家可自行分析,这里直接以图示形式来展示,主要是为了说明其中的“陷阱”。图示如下:

Python内建类型bytes实例代码分析

  • Py_buffer提供了一套操作对象缓冲区的统一接口,屏蔽不同类型对象的内部差异

  • bytes_concat则将两个对象的缓冲区拷贝到一起,形成新的bytes对象

上述的拷贝过程是比较清晰的,但是这里隐藏着一个问题——数据拷贝的陷阱。

以合并3个bytes对象为例:

>>> a = b'abc'>>> b = b'def'>>> c = b'ghi'>>> result = a + b + c>>> resultb'abcdefghi'

本质上这个过程会合并两次

>>> t = a + b>>> result = t + c

在这个过程中,a和b的数据都会被拷贝两遍,图示如下:

Python内建类型bytes实例代码分析


不难推出,合并n个bytes对象,头两个对象需要拷贝n - 1次,只有最后一个对象不需要重复拷贝,平均下来每个对象大约要拷贝n/2次。因此,下面的代码:

>>> result = b''>>> for b in segments:    result += s

效率是很低的。我们可以使用join()来优化

>>> result = b''.join(segments)

join()方法是bytes对象提供的一个内建方法,可以高效合并多个bytes对象。join方法对数据拷贝进行了优化:先遍历待合并对象,计算总长度;然后根据总长度创建目标对象;最后再遍历待合并对象,逐一拷贝数据。这样一来,每个对象只需要拷贝一次,解决了重复拷贝的陷阱。(具体源码大家可以自行去查看)

4 字符缓冲池

和小整数一样,字符对象(即单字节的bytes对象)数量也很少,只有256个,但使用频率非常高,因此以空间换时间能明显提升执行效率。字符缓冲池源码如下:

static PyBytesObject *characters[UCHAR_MAX + 1];

下面我们从创建bytes对象的过程来看一下字符缓冲池的使用:PyBytes_FromStringAndSize()函数是负责创建bytes对象的通用接口,源码如下:

PyObject *PyBytes_FromStringAndSize(const char *str, Py_ssize_t size){    PyBytesObject *op;    if (size < 0) {        PyErr_SetString(PyExc_SystemError,            "Negative size passed to PyBytes_FromStringAndSize");        return NULL;    }    if (size == 1 && str != NULL &&        (op = characters[*str & UCHAR_MAX]) != NULL)    {#ifdef COUNT_ALLOCS        one_strings++;#endif        Py_INCREF(op);        return (PyObject *)op;    }    op = (PyBytesObject *)_PyBytes_FromSize(size, 0);    if (op == NULL)        return NULL;    if (str == NULL)        return (PyObject *) op;    memcpy(op->ob_sval, str, size);        if (size == 1) {        characters[*str & UCHAR_MAX] = op;        Py_INCREF(op);    }    return (PyObject *) op;}

其中涉及字符缓冲区维护的关键步骤如下:

第10~17行:如果创建的对象为单字节对象,会先在characters数组的对应序号判断是否已经有相应的对象存储在了缓冲区中,如果有则直接取出

第28~31行:如果创建的对象为单字节对象,并且之前已经判断了不在缓冲区中,则将其放入字符缓冲池的对应位置

由此可见,当Python程序开始运行时,字符缓冲池是空的。随着单字节bytes对象的创建,缓冲池中的对象就慢慢多了起来。当缓冲池已缓存b&rsquo;1&rsquo;、b&rsquo;2&rsquo;、b&rsquo;3&rsquo;、b&rsquo;a&rsquo;、b&rsquo;b&rsquo;、b&rsquo;c&rsquo;这几个字符时,内部结构如下:

Python内建类型bytes实例代码分析

示例:

注:这里大家可能在IDLE和PyCharm中获得的结果不一致,这个问题在之前的博客中也提到过,查阅资料后得到的结论是:IDLE运行和PyCharm运行的方式不同。这里我将PyCharm代码对应的代码对象反编译的结果展示给大家,但我对IDLE的认识还比较薄弱,以后有机会再给大家详细补充这个知识(抱拳~)。这里大家还是先以认识字符缓冲区这个概念为主,当然字节码的相关知识掌握好了也是很有帮助的。以下是PyCharm运行的结果:

以下操作的相关讲解可以看这篇博客:

Python源码学习笔记:Python程序执行过程与字节码

示例1:

Python内建类型bytes实例代码分析

Python内建类型bytes实例代码分析

下面我们来看一下反编译的结果:(下面的文件路径我省略了,大家自己试验的时候要输入正确的路径)

>>> text = open('D:\\...\\test2.py').read()>>> result= compile(text,'D:\\...\\test2.py', 'exec')>>> import dis>>> dis.dis(result)  1           0 LOAD_CONST               0 (b'a')              2 STORE_NAME               0 (a)  2           4 LOAD_CONST               0 (b'a')              6 STORE_NAME               1 (b)  3           8 LOAD_NAME                2 (print)             10 LOAD_NAME                0 (a)             12 LOAD_NAME                1 (b)             14 IS_OP                    0             16 CALL_FUNCTioN            1             18 POP_TOP             20 LOAD_CONST               1 (None)             22 RETURN_VALUE

可以很清晰地看到,第5行和第8行的LOAD_CONST指令操作的都是下标为0的常量b&rsquo;a&rsquo;,因此此时a和b对应的是同一个对象,我们打印看一下:

>>> result.co_consts[0]b'a'

示例2:

为了确认只会缓存单字节的bytes对象,我在这里又尝试了多字节的bytes对象,同样还是在PyCharm环境下尝试:

Python内建类型bytes实例代码分析

Python内建类型bytes实例代码分析

结果是比较出乎意料的:多字节的bytes对象依然是同一个。为了验证这个想法,我们先来看一下对代码对象的反编译结果:

>>> text = open('D:\\...\\test3.py').read()>>> result= compile(text,'D:\\...\\test3.py', 'exec')>>> import dis>>> dis.dis(result)  1           0 LOAD_CONST               0 (b'abc')              2 STORE_NAME               0 (a)  2           4 LOAD_CONST               0 (b'abc')              6 STORE_NAME               1 (b)  3           8 LOAD_NAME                2 (print)             10 LOAD_NAME                0 (a)             12 LOAD_NAME                1 (b)             14 IS_OP                    0             16 CALL_FUNCTION            1             18 POP_TOP             20 LOAD_CONST               1 (None)             22 RETURN_VALUE>>> result.co_consts[0]b'abc'

可以看到,反编译的结果和单字节的bytes对象没有区别。

感谢各位的阅读,以上就是“Python内建类型bytes实例代码分析”的内容了,经过本文的学习后,相信大家对Python内建类型bytes实例代码分析这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

--结束END--

本文标题: Python内建类型bytes实例代码分析

本文链接: https://lsjlt.com/news/329573.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Python内建类型bytes实例代码分析
    这篇文章主要讲解了“Python内建类型bytes实例代码分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python内建类型bytes实例代码分析”吧!1 bytes和str之间的关系不...
    99+
    2023-06-30
  • Python数字类型实例代码分析
    这篇文章主要介绍了Python数字类型实例代码分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Python数字类型实例代码分析文章都会有所收获,下面我们一起来看看吧。Python 数字类型Python 中有三...
    99+
    2023-07-06
  • Python内建类型int源码分析
    今天小编给大家分享一下Python内建类型int源码分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。问题:对于C语言,下面...
    99+
    2023-06-30
  • Python内建类型float源码分析
    这篇文章主要介绍“Python内建类型float源码分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Python内建类型float源码分析”文章能帮助大家解决问题。1 回顾float的基础知识1....
    99+
    2023-06-30
  • Python内建类型str源码分析
    这篇文章主要讲解了“Python内建类型str源码分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python内建类型str源码分析”吧!1 Unicode计算机存储的基本单位是字节,由8...
    99+
    2023-06-30
  • Python内建类型dict源码分析
    本篇内容主要讲解“Python内建类型dict源码分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python内建类型dict源码分析”吧!深入认识Python内建类型&mdash;&...
    99+
    2023-07-05
  • Python内建类型bytes深入理解
    目录引言1 bytes和str之间的关系2 bytes对象的结构:PyBytesObject3 bytes对象的行为3.1 PyBytes_Type3.2 bytes_as_sequ...
    99+
    2024-04-02
  • Python数据类型入门实例代码分析
    本文小编为大家详细介绍“Python数据类型入门实例代码分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“Python数据类型入门实例代码分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学...
    99+
    2024-04-02
  • TypeScript类型级别实例代码分析
    本篇内容介绍了“TypeScript类型级别实例代码分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!代码如下:type Hell...
    99+
    2023-07-05
  • PHP类型转换实例代码分析
    今天小编给大家分享一下PHP类型转换实例代码分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。基本数据类型首先,我们需要了解...
    99+
    2023-07-05
  • Python中的bytes类型用法及实例分享
    目录1.bytes定义2.bytes方法3.使用不同方式创建 bytes 对象前言; Python bytes 类型用来表示一个字节串。“字节串“不是编程术语...
    99+
    2024-04-02
  • Redis数据结构类型实例代码分析
    这篇“Redis数据结构类型实例代码分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Redis数据结构类型实例代码分析”文...
    99+
    2023-07-05
  • Golang数据类型实例代码比较分析
    这篇文章主要讲解了“Golang数据类型实例代码比较分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Golang数据类型实例代码比较分析”吧!分类说明是否能比较说明基本类型整型( int/...
    99+
    2023-07-06
  • Python实例代码分析
    这篇“Python实例代码分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Python实例代码分析”文章吧。1.交换两个变...
    99+
    2023-06-27
  • python中函数的返回值及类型实例代码分析
    这篇“python中函数的返回值及类型实例代码分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“python中函数的返回值及...
    99+
    2023-07-05
  • Python数据类型实例分析
    本篇内容主要讲解“Python数据类型实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python数据类型实例分析”吧!一、内容概要字符串(str)列表(list)元组(tup)字典(di...
    99+
    2023-06-02
  • python数据类型的详细分析(附示例代码)
    目录前言1. 列表2. 元组3. 集合4. 字典总结前言 Python 四种集合数据类型: 列表(List)是一种有序和可更改的集合。允许重复的成员。 元组(Tup...
    99+
    2024-04-02
  • Go语言基础数据类型实例代码分析
    这篇文章主要讲解了“Go语言基础数据类型实例代码分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Go语言基础数据类型实例代码分析”吧!布尔型布尔型是Go最简单的数据类型,因为布尔型只有两个...
    99+
    2023-07-05
  • Python内置数据类型中的集合实例分析
    本文小编为大家详细介绍“Python内置数据类型中的集合实例分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“Python内置数据类型中的集合实例分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。1. 集合 s...
    99+
    2023-06-29
  • Golang内存模型实例源码分析
    这篇文章主要介绍“Golang内存模型实例源码分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Golang内存模型实例源码分析”文章能帮助大家解决问题。1. 简介(Introduction)Go ...
    99+
    2023-07-05
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作