在Python世界中将对象分为两种:一种是定长对象,比如整数,整数对象定义的时候就能确定它所占用的内存空间大小,另一种是变长对象,在对象定义时并不知道是多少,比如:str,list, set, dict等。

>>> import sys

>>> sys.getsizeof(1000)

28

>>> sys.getsizeof(2000)

28

>>> sys.getsizeof("python")

55

>>> sys.getsizeof("java")

53

如上,整数对象所占用的内存都是28字节,和具体的值没关系,而同样都是字符串对象,不同字符串对象所占用的内存是不一样的,这就是变长对象,对于变长对象,在对象定义时是不知道对象所占用的内存空间是多少的。

字符串对象在Python内部用PyStringObject表示,PyStringObject和PyIntObject一样都属于不可变对象,对象一旦创建就不能改变其值。(注意:变长对象和不可变对象是两个不同的概念)。PythonStringObject的定义:

[stringobject.h]

typedef struct {

PyObject_VAR_HEAD

long ob_shash;

int ob_sstate;

char ob_sval[1];

} PyStringObject;

不难看出Python的字符串对象内部就是由一个字符数组维护的,在整数的实现原理一文中提到PyObject_HEAD,对于PyObject_VAR_HEAD就是在PyObject_HEAD基础上多出一个ob_size属性:

[object.h]

#define PyObject_VAR_HEAD

PyObject_HEAD

int ob_size; /* Number of items in variable part */

typedef struct {

PyObject_VAR_HEAD

} PyVarObject;

ob_size保存了变长对象中元素的长度,比如PyStringObject对象"Python"的ob_size为6。

ob_sval是一个初始大小为1的字符数组,且ob_sval[0] = '\0',但实际上创建一个PyStringObject时ob_sval指向的是一段长为ob_size+1个字节的内存。

ob_shash是字符串对象的哈希值,初始值为-1,在第一次计算出字符串的哈希值后,会把该值缓存下来,赋值给ob_shash。

ob_sstate用于标记该字符串对象是否进过intern机制处理(后文会介绍)。

PYSTRINGOBJECT对象创建过程

[stringobject.c]

PyObject * PyString_FromString(const char *str)

{

register size_t size;

register PyStringObject *op;

assert(str != NULL);

size = strlen(str);

// [1]

if (size > PY_SSIZE_T_MAX - PyStringObject_SIZE) {

PyErr_SetString(PyExc_OverflowError,

"string is too long for a Python string");

return NULL;

}

// [2]

if (size == 0 && (op = nullstring) != NULL) {

#ifdef COUNT_ALLOCS

null_strings++;

#endif

Py_INCREF(op);

return (PyObject *)op;

}

// [3]

if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL) {

#ifdef COUNT_ALLOCS

one_strings++;

#endif

Py_INCREF(op);

return (PyObject *)op;

}

// [4]

/* Inline PyObject_NewVar */

op = (PyStringObject *)PyObject_MALLOC(PyStringObject_SIZE + size);

if (op == NULL)

return PyErr_NoMemory();

PyObject_INIT_VAR(op, &PyString_Type, size);

op->ob_shash = -1;

op->ob_sstate = SSTATE_NOT_INTERNED;

Py_MEMCPY(op->ob_sval, str, size+1);

/* share short strings */

if (size == 0) {

PyObject *t = (PyObject *)op;

PyString_InternInPlace(&t);

op = (PyStringObject *)t;

nullstring = op;

Py_INCREF(op);

} else if (size == 1) {

PyObject *t = (PyObject *)op;

PyString_InternInPlace(&t);

op = (PyStringObject *)t;

characters[*str & UCHAR_MAX] = op;

Py_INCREF(op);

}

return (PyObject *) op;

}

如果字符串的长度超出了Python所能接受的最大长度(32位平台是2G),则返回Null。

如果是空字符串,那么返回特殊的PyStringObject,即nullstring。

如果字符串的长度为1,那么返回特殊PyStringObject,即onestring。

其他情况下就是分配内存,初始化PyStringObject,把参数str的字符数组拷贝到PyStringObject中的ob_sval指向的内存空间。

字符串的INTERN机制

PyStringObject的ob_sstate属性用于标记字符串对象是否经过intern机制处理,intern处理后的字符串,比如"Python",在解释器运行过程中始终只有唯一的一个字符串"Python"对应的PyStringObject对象。

>>> a = "python"

>>> b = "python"

>>> a is b

True

如上所示,创建a时,系统首先会创建一个新的PyStringObject对象出来,然后经过intern机制处理(PyString_InternInPlace),接着查找经过intern机制处理的PyStringObject对象,如果发现有该字符串对应的PyStringObject存在,则直接返回该对象,否则把刚刚创建的PyStringObject加入到intern机制中。由于a和b字符串字面值是一样的,因此a和b都指向同一个PyStringObject("python")对象。那么intern内部又是一个什么样的机制呢?

[stringobject.c]

static PyObject *interned;

void PyString_InternInPlace(PyObject **p)

{

register PyStringObject *s = (PyStringObject *)(*p);

PyObject *t;

if (s == NULL || !PyString_Check(s))

Py_FatalError("PyString_InternInPlace: strings only please!");

/* If it's a string subclass, we don't really know what putting

it in the interned dict might do. */

// [1]

if (!PyString_CheckExact(s))

return;

// [2]

if (PyString_CHECK_INTERNED(s))

return;

// [3]

if (interned == NULL) {

interned = PyDict_New();

if (interned == NULL) {

PyErr_Clear(); /* Don't leave an exception */

return;

}

}

t = PyDict_GetItem(interned, (PyObject *)s);

if (t) {

Py_INCREF(t);

Py_DECREF(*p);

*p = t;

return;

}

if (PyDict_SetItem(interned, (PyObject *)s, (PyObject *)s) < 0) {

PyErr_Clear();

return;

}

/* The two references in interned are not counted by refcnt.

The string deallocator will take care of this */

Py_REFCNT(s) -= 2;

PyString_CHECK_INTERNED(s) = SSTATE_INTERNED_MORTAL;

}

1.先类型检查,intern机制只处理字符串

2.如果该PyStringObject对象已经进行过intern机制处理,则直接返回

3.interned其实一个字典对象,当它为null时,初始化一个字典对象,否则,看该字典中是否存在一个key为(PyObject *)s的value,如果存在,那么就把该对象的引用计数加1,临时创建的那个对象的引用计数减1。否则,把(PyObject *)s同时作为key和value添加到interned字典中,与此同时它的引用计数减2,这两个引用计数减2是因为被interned字典所引用,但这两个引用不作为垃圾回收的判断依据,否则,字符串对象永远都不会被垃圾回收器收集了。

上述代码中,给b赋值为"python"后,系统中创建了几个PyStringObject对象呢?答案是:2,在创建b的时候,一定会有一个临时的PyStringObject作为字典的key在interned中查找是否存在一个PyStringObject对象的值为"python"。

字符串的缓冲池

字符串除了有intern机制缓存字符串之外,字符串还有一种专门的短字符串缓冲池characters。用于缓存字符串长度为1的PyStringObject对象。

static PyStringObject *characters[UCHAR_MAX + 1]; //UCHAR_MAX = 255

创建长度为1的字符串时流程:

...

else if (size == 1) {

PyObject *t = (PyObject *)op;

PyString_InternInPlace(&t);

op = (PyStringObject *)t;

characters[*str & UCHAR_MAX] = op;

Py_INCREF(op);

首先创建一个PyStringObject对象。

进行intern操作

将PyStringObject缓存到characters中

引用计数增1

总结:

1. 字符串用PyStringObject表示

2. 字符串属于变长对象

3. 字符串属于不可变对象

4. 字符串用intern机制提高python的效率

5. 字符串有专门的缓冲池存储长度为1的字符串对象

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

python对象底层原理_Python字符串对象实现原理详解相关推荐

  1. python解释器的工作原理_Python GIL全局解释器锁详解(深度剖析)

    通过前面的学习,我们了解了 Pyton 并发编程的特性以及什么是多线程编程.其实除此之外,Python 多线程还有一个很重要的知识点,就是本节要讲的 GIL. GIL,中文译为全局解释器锁.在讲解 G ...

  2. python基础数据实例_Python基本数据类型及实例详解

    Python 中的变量不需要声明.每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建. 在 Python 中,变量就是变量,它没有类型,我们所说的"类型"是变量所指的内存中对 ...

  3. python函数参数定义_Python函数定义及参数详解

    函数定义 首先我们来创建一个函数,输出指定范围内的斐波拉契数列(Fibonacci series). #!/usr/bin/env python #coding=utf-8 ''' Created o ...

  4. python 正则式替换_python 正则表达式参数替换实例详解

    正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配. Python 自1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式. re 模块使 Python ...

  5. python多进程应用场景_python使用多进程的实例详解

    python多线程适合IO密集型场景,而在CPU密集型场景,并不能充分利用多核CPU,而协程本质基于线程,同样不能充分发挥多核的优势. 针对计算密集型场景需要使用多进程,python的multipro ...

  6. python的socket模块_Python socket模块方法实现详解

    这篇文章主要介绍了python socket模块方法实现详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 socket ssh (不带防止粘包的方 ...

  7. python语法错误概述_Python 错误和异常代码详解

    程序中的错误一般被称为 Bug,无可否认,这几乎总是程序员的错... 程序员的一生,始终伴随着一件事 - 调试(错误检测.异常处理).反反复复,最可怕的是:不仅自己的要改,别人的也要改...一万头草泥 ...

  8. python平方数迭代器_Python三大神器之迭代器详解

    我们将要来学习python的重要概念迭代和迭代器,通过简单实用的例子如列表迭代器和xrange. 可迭代 一个对象,物理或者虚拟存储的序列.list,tuple,strins,dicttionary, ...

  9. python语言格式化输出_Python format()格式化输出方法详解

    原标题:Python format()格式化输出方法详解 format() 方法的语法格式如下: str.format(args) 此方法中,str 用于指定字符串的显示样式:args 用于指定要进行 ...

最新文章

  1. TCL中关于Pins的一些使用方法?
  2. vlc的应用之二:vlc的ActiveX及cab
  3. 出来工作五年的经历总结(五年前,你在干嘛?)
  4. 【dfs】I Like Matrix!
  5. 滤波电容的选择(调试中)
  6. spring 配置异步要点
  7. 产品管理系列(一)---优秀的产品经理所具有的素质 王泽宾
  8. 一个项目的404错误处理页面
  9. 框架学习 Spring之概念
  10. C/C++的转义字符
  11. apache-apollo Dockfile 镜像制作
  12. 2021年下半年信息安全工程师上午真题及答案解析
  13. cad刷新快捷键_47个CAD快捷键+50个技巧,了解这些CAD操作,你就偷着乐吧
  14. 傅里叶变换及其实现(MATLAB)
  15. 常用APP签名存档以及获取签名的几种方式介绍
  16. 如何实现团队高效协作办公?
  17. 高性能台式计算机一体机,一体机电脑与台式机电脑,究竟选哪个好?
  18. 不同介质中的运动目标检测(虚拟潜望镜)
  19. 秣小白的C语言旅程——第二站(含自我介绍)
  20. MPB:青岛大学苏晓泉组分享基于分类学和系统发育的宏基因组比较DMS算法

热门文章

  1. Scala println的实现原理调试
  2. 在SAP HANA Express Edition里创建数据库表
  3. SAP ABAP SICF事务码和SAP Hybris的链式过滤器filter chain
  4. SAP C4C一个典型的客户Organization和employee的同步需求
  5. What does SAP UI5 bindItem occurs
  6. 递归调用cl_crm_oi_docx_transform_rt=process_node_cc
  7. SAP CRM BSP component在test mode下launch的执行顺序
  8. initializeAdvisorChain
  9. Netweaver是如何判断哪些对象可以被增强的
  10. integer 负数字符串比较_Integer与int的区别 (== 与 equal)