返回顶部
首页 > 资讯 > 后端开发 > Python >5. Python3源码—字符串(st
  • 554
分享到

5. Python3源码—字符串(st

字符串源码st 2023-01-31 08:01:32 554人浏览 安东尼

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

摘要

5.1. 字符串对象 字符串对象是“变长对象”。 5.1.1. Python中的创建 python中字符串(strs)对象最重要的创建方法为PyUnicode_DecodeUTF8Stateful,如下Python语句最终会调用到PyUn

5.1. 字符串对象

字符串对象是“变长对象”。

5.1.1. Python中的创建

python中字符串(strs)对象最重要的创建方法为PyUnicode_DecodeUTF8Stateful,如下Python语句最终会调用到PyUnicode_DecodeUTF8Stateful:

a = 'hello
b = str('world')

5.1.2. PyUnicode_DecodeUTF8Stateful的C调用栈

词法解析,最终调到PyUnicode_DecodeUTF8Stateful,调用顺序如下:

// ast.c
ast_for_expr
=>ast_for_power
=>ast_for_atom_expr
=>ast_for_atom (case STRING)
=>parsestrplus
=>parsestr

// unicodeobject.c
=> PyUnicode_DecodeUTF8Stateful

5.1.3. PyUnicode_DecodeUTF8Stateful源码

// unicodeobject.c
PyObject *
PyUnicode_DecodeUTF8Stateful(const char *s,
                             Py_ssize_t size,
                             const char *errors,
                             Py_ssize_t *consumed)
{
    _PyUnicodeWriter writer;
    const char *starts = s;
    const char *end = s + size;

    Py_ssize_t startinpos;
    Py_ssize_t endinpos;
    const char *errmsg = "";
    PyObject *error_handler_obj = NULL;
    PyObject *exc = NULL;
    _Py_error_handler error_handler = _Py_ERROR_UNKNOWN;

    if (size == 0) {
        if (consumed)
            *consumed = 0;
        _Py_RETURN_UNICODE_EMPTY();
    }

    
    if (size == 1 && (unsigned char)s[0] < 128) {
        if (consumed)
            *consumed = 1;
        return get_latin1_char((unsigned char)s[0]);
    }

    _PyUnicodeWriter_Init(&writer);
    writer.min_length = size;
    if (_PyUnicodeWriter_Prepare(&writer, writer.min_length, 127) == -1)
        Goto onError;

    writer.pos = ascii_decode(s, end, writer.data);
    s += writer.pos;
    while (s < end) {
        // ascii解码后的size小于传入的size
    }

End:
    if (consumed)
        *consumed = s - starts;

    Py_XDECREF(error_handler_obj);
    Py_XDECREF(exc);
    return _PyUnicodeWriter_Finish(&writer);

onError:
    Py_XDECREF(error_handler_obj);
    Py_XDECREF(exc);
    _PyUnicodeWriter_Dealloc(&writer);
    return NULL;
}

可以看到:

  • 空串缓存:空串(unicode_empty)为同一个地址,第二次需要空串时,只是将计数加1,在_PyUnicodeWriter_Finish中实现空串缓存。
// unicodeobject.c
static PyObject *unicode_empty = NULL;

#define _Py_INCREF_UNICODE_EMPTY()                      \
    do {                                                \
        if (unicode_empty != NULL)                      \
            Py_INCREF(unicode_empty);                   \
        else {                                          \
            unicode_empty = PyUnicode_New(0, 0);        \
            if (unicode_empty != NULL) {                \
                Py_INCREF(unicode_empty);               \
                assert(_PyUnicode_CheckConsistency(unicode_empty, 1)); \
            }                                           \
        }                                               \
    } while (0)

#define _Py_RETURN_UNICODE_EMPTY()                      \
    do {                                                \
        _Py_INCREF_UNICODE_EMPTY();                     \
        return unicode_empty;                           \
    } while (0)

// PyUnicode_DecodeUTF8Stateful->
// _PyUnicodeWriter_Finish->
// unicode_result_ready
static PyObject*
unicode_result_ready(PyObject *unicode)
{
    Py_ssize_t length;

    length = PyUnicode_GET_LENGTH(unicode);
    if (length == 0) {
        if (unicode != unicode_empty) {
            Py_DECREF(unicode);
            _Py_RETURN_UNICODE_EMPTY();
        }
        return unicode_empty;
    }

    if (length == 1) {
        void *data = PyUnicode_DATA(unicode);
        int kind = PyUnicode_KIND(unicode);
        Py_UCS4 ch = PyUnicode_READ(kind, data, 0);
        if (ch < 256) {
            PyObject *latin1_char = unicode_latin1[ch];
            if (latin1_char != NULL) {
                if (unicode != latin1_char) {
                    Py_INCREF(latin1_char);
                    Py_DECREF(unicode);
                }
                return latin1_char;
            }
            else {
                assert(_PyUnicode_CheckConsistency(unicode, 1));
                Py_INCREF(unicode);
                unicode_latin1[ch] = unicode;
                return unicode;
            }
        }
    }

    assert(_PyUnicode_CheckConsistency(unicode, 1));
    return unicode;
}
  • 字符缓冲池:字符(unicode_latin1)为同一个地址,第二次需要该字符时,只是将计数加1,在get_latin1_char中实现字符缓存。
// unicodeobject.c
static PyObject *unicode_latin1[256] = {NULL};

PyObject *
PyUnicode_DecodeUTF8Stateful(const char *s,
                             Py_ssize_t size,
                             const char *errors,
                             Py_ssize_t *consumed)
{
      // do sth.

    
    if (size == 1 && (unsigned char)s[0] < 128) {
        if (consumed)
            *consumed = 1;
        return get_latin1_char((unsigned char)s[0]);
    }

      // do sth.
}

static PyObject*
get_latin1_char(unsigned char ch)
{
    PyObject *unicode = unicode_latin1[ch];
    if (!unicode) {
        unicode = PyUnicode_New(1, ch);
        if (!unicode)
            return NULL;
        PyUnicode_1BYTE_DATA(unicode)[0] = ch;
        assert(_PyUnicode_CheckConsistency(unicode, 1));
        unicode_latin1[ch] = unicode;
    }
    Py_INCREF(unicode);
    return unicode;
}

5.2. 常量字符串池

a = 'hello'
b = 'hello'
a is b  #True

由上例可以看出Python对常量字符串做了缓存。缓存的关键性实现在PyUnicode_InternInPlace方法中。

5.2.1. PyUnicode_InternInPlace的C调用堆栈

// compile.c
assemble
=>makecode
// codeobject.c
=>PyCode_New
=>intern_string_constants
// unicodeobject.c
=>PyUnicode_InternInPlace

5.2.2. PyUnicode_InternInPlace源码

// unicodeobject.c
static PyObject *interned = NULL;

void
PyUnicode_InternInPlace(PyObject **p)
{
    PyObject *s = *p;
    PyObject *t;
#ifdef Py_DEBUG
    assert(s != NULL);
    assert(_PyUnicode_CHECK(s));
#else
    if (s == NULL || !PyUnicode_Check(s))
        return;
#endif
    
    if (!PyUnicode_CheckExact(s))
        return;
    if (PyUnicode_CHECK_INTERNED(s))
        return;
    if (interned == NULL) {
        interned = PyDict_New();
        if (interned == NULL) {
            PyErr_Clear(); 
            return;
        }
    }
    Py_ALLOW_RECURSioN
    t = PyDict_SetDefault(interned, s, s);
    Py_END_ALLOW_RECURSION
    if (t == NULL) {
        PyErr_Clear();
        return;
    }
    if (t != s) {
        Py_INCREF(t);
        Py_SETREF(*p, t);
        return;
    }
    
    Py_REFCNT(s) -= 2;
    _PyUnicode_STATE(s).interned = SSTATE_INTERNED_MORTAL;
}

其中最关键的方法为PyDict_SetDefault,该方法存在于字典对象dictobject.c中。如果没有相同的key(此处为s),则返回defaultobject(此处也为s),否则如果有相同的key则返回对应的value。所以如果t与s不同,则说明字典中有相应的key,此时将t的计数加1,并且将之前常量字符串的对象指向t。

如此一来,常量字符串的对象地址就一致了,此时s的计数会被消除,如果s的计数为0,则会被释放。值得注意的是,常量字符串的对象每次仍旧会被多分配一次内存,只是如果之前有分配过,且如果此次分配的对象计数为0,则会被释放。

有些情况下(字符串包含非0-9a-zA-Z)不会放到字典里,这时候可以通过sys.intern进行性能优化

import sys
a = '啊'
b = '啊'
a is b    # False

a = sys.intern('啊')
b = sys.intern('啊')
a is b    # True

具体可以参考:memory - What does python sys.intern do, and when should it be used? - Stack Overflow

5.3. 字符串对象的特性

支持tp_as_number、tp_as_sequence、tp_as_mapping这三种操作。

5.3.1. 数值操作

// unicodeobject.c
&unicode_as_number,                         

5.3.2. 序列操作

// unicodeobject.c
&unicode_as_sequence,                     
// unicodeobject.c
static PySequenceMethods unicode_as_sequence = {
    (lenfunc) unicode_length,       
    PyUnicode_Concat,           
    (ssizeargfunc) unicode_repeat,  
    (ssizeargfunc) unicode_getitem,     
    0,                  
    0,                  
    0,                  
    PyUnicode_Contains,         
};

因为没有实现PySequenceMethods中的设置方法,所以字符串不可变。

其中:

  • unicode_length
len('hello')
  • PyUnicode_Concat
'hello' + 'wolrd'

多个字符串相加效率低于join,join只分配一次内存;

  • unicode_repeat
'hello'*10

效率要高于同个字符串相加;

  • unicode_getitem:暂时没有找到相应Python语句;
  • PyUnicode_Contains
'h' in 'hello'

5.3.3. 关联操作

// unicodeobject.c
&unicode_as_mapping,                        
// unicodeobject.c
static PyMappingMethods unicode_as_mapping = {
    (lenfunc)unicode_length,        
    (binaryfunc)unicode_subscript,  
    (objobjargproc)0,           
};

其中:

  • unicode_subscript
test = 'hello world'
test[1]
test[0:5]

test[1]会走unicode_subscript方法的index分支,test[0:5]会走slice分支;

5.3.4. to string

// unicodeobject.c
unicode_repr,                                   
(reprfunc) unicode_str,                         

5.3.5. hash

// unicodeobject.c
(hashfunc) unicode_hash,                        

5.3.6. 比较

// unicodeobject.c
PyUnicode_RichCompare,                      

5.3.7. 内置方法

// unicodeobject.c
unicode_methods,                              

5.4 参考

本文作者:whj0709
阅读原文
本文为云栖社区原创内容,未经允许不得转载。

--结束END--

本文标题: 5. Python3源码—字符串(st

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

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

猜你喜欢
  • 5. Python3源码—字符串(st
    5.1. 字符串对象 字符串对象是“变长对象”。 5.1.1. Python中的创建 Python中字符串(strs)对象最重要的创建方法为PyUnicode_DecodeUTF8Stateful,如下Python语句最终会调用到PyUn...
    99+
    2023-01-31
    字符串 源码 st
  • python3 字符串/列表/元组(st
    在抓取网络数据的时候,有时会用正则对结构化的数据进行提取,比如 href="https://www.1234.com"等。python的re模块的findall()函数会返回一个所有匹配到的内容的列表,在将数据存入数据库时,列表数据类型是...
    99+
    2023-01-31
    字符串 列表 st
  • Python3 字符串
    字符串是 Python 中最常用的数据类型。我们可以使用引号('或")来创建字符串。创建字符串很简单,只要为变量分配一个值即可。例如:var1 = 'Hello World!'var2 = "Runoob"Python 访问字符串中的值Py...
    99+
    2023-01-31
    字符串
  • python3字符串操作
    python3字符串操作 1 x = 'abc' 2 y = 'defgh' 3 4 print(x + y) #x+y 5 print(x * 3) #x*n 6 print(x[2]) ...
    99+
    2023-01-31
    字符串 操作
  • python3-字符串操作
    name = "my \tname is {name}, age is {age}." '''print(name.capitalize()) #这段话的首字母大写 print(name.count("a"))...
    99+
    2023-01-31
    字符串 操作
  • python3 字符串操作
          name = "My \tname is  {name} and my age is {year} old"#大写 print(name.capitalize()) # 首字母大写 打印显示 My  name is  {na...
    99+
    2023-01-31
    字符串 操作
  • python3 把字符串 (str) \
    直接str.decode() 找不到decode() 方法,所以要用以下方法 测试程序: test = '\u5220\u9664' test.encode('utf-8').decode('unicode_escape') print(t...
    99+
    2023-01-31
    字符串 str
  • python3 字符串操作
    字符串操作: 1、 下载python官方手册 2、 先定义一个字符串变量 A = ‘abc’ A.两次TAB键 help(A.选项) #查看帮助 'ABC'.lower() #XXX...
    99+
    2023-01-31
    字符串 操作
  • python3——字符串基础
    字符串可以使用一对单引号或一对双引号指定起止位置,两种方式指定的字符串完全等价。如'Hello'和"World"可以用三引号("""或''')指定多行字符串,其中可自由使用单、双引号而不需转义。如'''"What's your name,"...
    99+
    2023-01-31
    字符串 基础
  • python3学习之字符串
    s='this is test message's.capitalize()    首字母大写,其它小写               s.find(sub[, start[, end]])      在指定范围内(默认全部字符串),查找su...
    99+
    2023-01-31
    字符串
  • python3-列表与字符串
    del如下。pop弹出元素并返回。 print(x) [1, 2, 5, 6, 8, 4, 3, 5] del x[3] print(x) [1, 2, 5, 8, 4, 3, 5] y=x.pop() print(y) 5 x pr...
    99+
    2023-01-31
    字符串 列表
  • Python3 字符编码
    原文出处:http://www.cnblogs.com/284628487a/p/5584714.html编码字符串是一种数据类型,但是,字符串比较特殊的是还有一个编码问题。因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才...
    99+
    2023-01-31
    字符
  • redis源码学习01:字符串sds
    前言 本文是redis源码关于字符串处理的学习笔记,欢迎指正。 redis版本是5.0.5,redis的功能、用途及性能我就不做赘述了。 正文 进入正题,redis提供了自己的字符串存储及相关操作,源码文件在sds.h和sds.c里。 在学...
    99+
    2019-03-04
    redis源码学习01:字符串sds
  • Python3基础数据-字符串
    一、创建字符串 字符串是 Python 中最常用的数据类型。我们可以使用引号('或")来创建字符串。创建字符串很简单,只要为变量分配一个值即可。例如: var1 = 'Hello World!' 二、访问字符串中的值 #!/usr/bin...
    99+
    2023-01-31
    字符串 基础 数据
  • python3的字符串格式化
    我们知道Python3.x引入了新的字符串格式化语法。不同于Python2.x的"%s %s "%(a,b)  Python3.x是"{0} {1}".format(a,b) '{0},{1}'.format('jack',22)Out[3...
    99+
    2023-01-31
    字符串
  • #Python3中字符串的比较
    20.字符串的比较 从第一个字符开始比较谁的ASCII值谁就大 如果前面相同 则比较后一位直到比较出谁大 如果都相同 则相等 print("acc"<"b") #(输出)True print("sunck"=="sunc...
    99+
    2023-01-31
    字符串
  • redis源码阅读——动态字符串sds
    redis中动态字符串sds相关的文件为:sds.h与sds.c 一、数据结构 redis中定义了自己的数据类型"sds",用于描述 char*,与一些数据结构 1 typedef char *sds; 2 3 5 ...
    99+
    2015-11-21
    redis源码阅读——动态字符串sds
  • python3.x中bytes与字符串详
    原文地址:http://www.crifan.com/summary_python_string_encoding_decoding_difference_and_comparation_python_2_x_str_unicode_vs...
    99+
    2023-01-31
    字符串 bytes
  • Python字符串的5个知识点
    使用方法修改字符串的大小写在msg.title()中,msg后面的句点(.)让python对变量msg执行方法title()指定的操作。每个方法后面都跟着一对括号,这是因为方法通常需要额外的信息来完成其工作。这种信息是在括号内提供的。函数t...
    99+
    2023-06-02
  • redis 5.0.7 源码阅读——动态字符串sds
    redis中动态字符串sds相关的文件为:sds.h与sds.c 一、数据结构 redis中定义了自己的数据类型"sds",用于描述 char*,与一些数据结构 1 typedef char *sds; 2 3 5 ...
    99+
    2020-03-12
    redis 5.0.7 源码阅读——动态字符串sds
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作