http://www.laurentluce.com/posts/python-string-objects-implementation/

Python string objects implementation

June 19, 2011

This article describes how string objects are managed by Python internally and how string search is done.

PyStringObject structure
New string object
Sharing string objects
String search

PyStringObject structure

A string object in Python is represented internally by the structure PyStringObject. “ob_shash” is the hash of the string if calculated. “ob_sval” contains the string of size “ob_size”. The string is null terminated. The initial size of “ob_sval” is 1 byte and ob_sval[0] = 0. If you are wondering where “ob_size is defined”, take a look at PyObject_VAR_HEAD in object.h. “ob_sstate” indicates if the string object is in the interned dictionary which we are going to see later.

1 typedef struct {
2     PyObject_VAR_HEAD
3     long ob_shash;
4     int ob_sstate;
5     char ob_sval[1];
6 } PyStringObject;

New string object

What happens when you assign a new string to a variable like this one?

1 >>> s1 = 'abc'

The internal C function “PyString_FromString” is called and the pseudo code looks like this:

1 arguments: string object: 'abc'
2 returns: Python string object with ob_sval = 'abc'
3 PyString_FromString(string):
4     size = length of string
5     allocate string object + size for 'abc'. ob_sval will be of size: size + 1
6     copy string to ob_sval
7     return object

Each time a new string is used, a new string object is allocated.

Sharing string objects

There is a neat feature where small strings are shared between variables. This reduces the amount of memory used. Small strings are strings of size 0 or 1 byte. The global variable “interned” is a dictionary referencing those small strings. The array “characters” is also used to reference the strings of length 1 byte: i.e. single characters. We will see later how the array “characters” is used.

1 static PyStringObject *characters[UCHAR_MAX + 1];
2 static PyObject *interned;

Let’s see what happens when a new small string is assigned to a variable in your Python script.

1 >>> s2 = 'a'

The string object containing ‘a’ is added to the dictionary “interned”. The key is a pointer to the string object and the value is the same pointer. This new string object is also referenced in the array characters at the offset 97 because value of ‘a’ is 97 in ASCII. The variable “s2” is pointing to this string object.

What happens when a different variable is assigned to the same string ‘a’?

1 >>> s3 = 'a'

The same string object previously created is returned so both variables are pointing to the same string object. The “characters” array is used during that process to check if the string already exists and returns the pointer to the string object.

1 if (size == 1 && (op = characters[*str & UCHAR_MAX]) != NULL)
2 {
3     ...
4     return (PyObject *)op;
5 }

Let’s create a new small string containing the character ‘c’.

1 >>> s4 = 'c'

We end up with the following:

We also find the “characters” array at use when a string’s item is requested like in the following Python script:

1 >>> s5 = 'abc'
2 >>> s5[0]
3 'a'

Instead of creating a new string containing ‘a’, the pointer at the offset 97 of the “characters” array is returned. Here is the code of the function “string_item” which is called when we request a character from a string. The argument “a” is the string object containing ‘abc’ and the argument “i” is the index requested: 0 in our case. A pointer to a string object is returned.

01 static PyObject *
02 string_item(PyStringObject *a, register Py_ssize_t i)
03 {
04     char pchar;
05     PyObject *v;
06     ...
07     pchar = a->ob_sval[i];
08     v = (PyObject *)characters[pchar & UCHAR_MAX];
09     if (v == NULL)
10         // allocate string
11     else {
12         ...
13         Py_INCREF(v);
14     }
15     return v;
16 }

The “characters” array is also used for function names of length 1:

1 >>> def a(): pass

String search

Let’s take a look at what happens when you perform a string search like in the following Python code:

1 >>> s = 'adcabcdbdabcabd'
2 >>> s.find('abcab')
3 >>> 11

The “find” function returns the index where the string ‘abcd’ is found in the string “s”. It returns -1 if the string is not found.

So, what happens internally? The function “fastsearch” is called. It is a mix between Boyer-Moore and Horspool algorithms plus couple of neat tricks.

Let’s call “s” the string to search in and “p” the string to search for. s = ‘adcabcdbdabcabd’ and p = ‘abcab’. “n” is the length of “s” and “m” is the length of “p”. n = 18 and m = 5.

The first check in the code is obvious, if m > n then we know that we won’t be able to find the index so the function returns -1 right away as we can see in the following code:

1 w = n - m;
2 if (w < 0)
3     return -1;

When m = 1, the code goes through “s” one character at a time and returns the index when there is a match. mode = FAST_SEARCH in our case as we are looking for the index where the string is found first and not the number of times the string if found.

01 if (m <= 1) {
02     ...
03     if (mode == FAST_COUNT) {
04         ...
05     else {
06         for (i = 0; i < n; i++)
07             if (s[i] == p[0])
08                 return i;
09     }
10     return -1;
11 }

For other cases i.e. m > 1. The first step is to create a compressed boyer-moore delta 1 table. Two variables will be assigned during that step: “mask” and “skip”.

“mask” is a 32-bit bitmask, using the 5 least significant bits of the character as the key. It is generated using the string to search “p”. It is a bloom filter which is used to test if a character is present in this string. It is really fast but there are false positives. You can read more about bloom filters here. This is how the bitmask is generated in our case:

1 mlast = m - 1
2 /* process pattern[:-1] */
3 for (mask = i = 0; i < mlast; i++) {
4     mask |= (1 << (p[i] & 0x1F));
5 }
6 /* process pattern[-1] outside the loop */
7 mask |= (1 << (p[mlast] & 0x1F));

First character of “p” is ‘a’. Value of ‘a’ is 97 = 1100001 in binary format. Using the 5 least significants bits, we get 00001 so “mask” is first set to: 1 << 1 = 10. Once the entire string "p" is processed, mask = 1110. How do we use this bitmask? By using the following test where "c" is the character to look for in the string "p".

1 if ((mask & (1 << (c & 0x1F))))

Is ‘a’ in “p” where p = ‘abcab’? Is 1110 & (1 << ('a' & 0X1F)) true? 1110 & (1 << ('a' & 0X1F)) = 1110 & 10 = 10. So, yes 'a' is in 'abcab'. If we test with 'd', we get false and also with the characters from 'e' to 'z' so this filter works pretty well in our case. "skip" is set to the index of the character with the same value as the last character in the string to search for. "skip" is set to the length of "p" - 1 if the last character is not found. The last character in the string to search for is 'b' which means "skip" will be set to 2 because this character can also be found by skipping over 2 characters down. This variable is used in a skip method called the bad-character skip method. In the following example: p = 'abcab' and s = 'adcabcaba'. The search starts at index 4 of "s" and checks backward if there is a string match. This first test fails at index = 1 where 'b' is different than 'd'. We know that the character 'b' in "p" is also found 3 characters down starting from the end. Because 'c' is part of "p", we skip to the following 'b'. This is the bad-character skip. 

Next is the search loop itself (real code is in C instead of Python):

01 for = 0 to n - = 13:
02     if s[i+m-1== p[m-1]:
03         if s[i:i+mlast] == p[0:mlast]:
04             return i
05         if s[i+m] not in p:
06             += m
07         else:
08             += skip
09     else:
10         if s[i+m] not in p:
11             += m
12 return -1

The test “s[i+m] not in p” is done using the bitmask. “i += skip” is the bad-character skip. “i += m” is done when the next character is not found in “p”.

Let’s see how this search algorithm works with our strings “p” and “s”. The first 3 steps are familiar. After that, the character ‘d’ is not in the string “p” so we skip the length of “p” and quickly find a match after that.

Python string objects implementation的更多相关文章

  1. Python integer objects implementation

    http://www.laurentluce.com/posts/python-integer-objects-implementation/ Python integer objects imple ...

  2. The internals of Python string interning

    JUNE 28TH, 2014Tweet This article describes how Python string interning works in CPython 2.7.7. A fe ...

  3. Python string interning原理

    原文链接:The internals of Python string interning 由于本人能力有限,如有翻译出错的,望指明. 这篇文章是讲Python string interning是如何 ...

  4. Exploring Python Code Objects

    Exploring Python Code Objects https://late.am/post/2012/03/26/exploring-python-code-objects.html Ins ...

  5. python string module

    String模块中的常量 >>> import string >>> string.digits ' >>> string.letters 'ab ...

  6. python string

    string比较连接 >>> s1="python string" >>> len(s) 13 >>> s2=" p ...

  7. Python string replace 方法

    Python string replace   方法 方法1: >>> a='...fuck...the....world............' >>> b=a ...

  8. python string与list互转

    因为python的read和write方法的操作对象都是string.而操作二进制的时候会把string转换成list进行解析,解析后重新写入文件的时候,还得转换成string. >>&g ...

  9. python string 文本常量和模版

        最近在看python标准库这本书,第一感觉非常厚,第二感觉,里面有很多原来不知道的东西,现在记下来跟大家分享一下.     string类是python中最常用的文本处理工具,在python的 ...

随机推荐

  1. HDU 4638-Group(线段树+离线处理)

    题意: 给n个编号,m个查询每个查询l,r,求下标区间[l,r]中能分成标号连续的组数(一组内的标号是连续的) 分析: 我们认为初始,每个标号为一个组(线段树维护区间组数),从左向右扫序列,当前标号, ...

  2. 【剑指offer 面试题16】反转链表

    思路: 用三个指针preNode.curNode.nextNode完成. #include <iostream> using namespace std; struct ListNode ...

  3. 常用的正则表达式归纳—JavaScript正则表达式

    来源:http://www.ido321.com/856.html 1.正则优先级 首先看一下正则表达式的优先级,下表从最高优先级到最低优先级列出各种正则表达式操作符的优先权顺序: 2.常用的正则表达 ...

  4. bzoj 3931 [CQOI2015]网络吞吐量(最短路,最大流)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=3931 [题意] 只能通过1-n的最短路,求网络最大流 [思路] 分别以1,n为起点做最 ...

  5. public private protected和默认的区别(转自百度)

    public private protected和默认的区别 Java中对类以及类中的成员变量和成员方法通过访问控制符(access specifier)进行区分控制.刚学Java语言的同学可能对pu ...

  6. Tkinter教程之Menu篇

    本文转载自:http://blog.csdn.net/jcodeer/article/details/1811321 '''Tkinter教程之Menu篇''''''1.创建一个简单的Menu'''# ...

  7. C++ 我想这样用(七)

    话接前篇,继续基于对象编程语法的剩余部分: 6.类的const成员函数和const对象 const数据成员:跟const常量一样,只是一个在类里(而且是在构造函数里),一个在类外而已,都必须初始化. ...

  8. C++ 我想这样用(三)

    话接前篇,继续谈在C++环境下使用C风格编程时的注意点: 6.关于原型的声明 在C里,调用一个未声明的函数是允许的,但是在C++里,必须先声明才能调用函数.另外,如果函数的参数是空的,那么在c里面是未 ...

  9. Some_sort_algorithms

    复习下数据结构常用的排序算法,更多内容上wiki 快速排序(不稳定 O(n log n)) 快速排序(Quicksort)是对冒泡排序的一种改进.由C. A. R. Hoare在1962年提出.它的基 ...

  10. uva202:循环小数(循环节+抽屉原理)

    题意: 给出两个数n,m,0<=n,m<=3000,输出n/m的循环小数表示以及循环节长度. 思路: 设立一个r[]数组记录循环小数,u[]记录每次的count,用于标记,小数计算可用 r ...