当前位置: 首页 > 科技观察

深入理解Python字符串对象的实现

时间:2023-03-22 16:16:40 科技观察

本文介绍了python内部是如何管理字符串对象的,以及字符串查找操作是如何实现的。PyStringObject结构Python中的字符串对象在内部对应一个名为PyStringObject的结构。“ob_shash”对应计算出的字符串哈希值,“ob_sval”指向一个长度为“ob_size”的字符串,字符串以'null'结尾(为了兼容C)。“ob_sval”初始大小为1字节,ob_sval[0]=0(对应空串)。如果还想知道“ob_size”是在哪里定义的,可以看一下object.h头文件中PyObject_VAR_HEAD的对应部分。“ob_sstate”用于表示一个字符串是否已经存在于intern机制对应的字典中,这个我们后面会再提到。typedefstruct{PyObject_VAR_HEADlongob_shash;intob_sstate;charob_sval[1];}PyStringObject;字符串对象的创建如下所示,当一个新的字符串被分配给一个变量时会发生什么?1>>>s1='abc'run对于上面的代码,将调用内部C函数“PyString_FromString”并生成类似于以下的伪代码:arguments:stringobject:'abc'returns:Pythonstringobjectwithob_sval='abc'PyString_FromString(string):size=lengthofstringallocatestringobject+sizefor'abc'。ob_svalwillbeofsize:size+1copystringtoob_svalreturnobject每使用一个新的字符串,都会分配一个字符串对象。共享字符串对象Python有一个优雅的特性,即变量之间共享短字符串,从而节省了所需的内存空间。短字符串是那些长度为0或1字节的字符串。而全局变量“interned”对应的是一个用来索引这些短字符串的字典。数组“字符”也可用于索引长度为1个字节的字符串,例如单个字母。稍后我们将看到如何使用数组“characters”。staticPyStringObject*characters[UCHAR_MAX+1];staticPyObject*interned;让我们来看看当您将一个短字符串分配给Python脚本中的变量时,幕后发生了什么。staticPyStringObject*characters[UCHAR_MAX+1];staticPyObject*interned;内容为“a”的字符串对象将被添加到“interned”字典中。字典中的key是指向string对象的指针,对应的value也是同一个指针。在数组“characters”中,这个新的字符串对象在偏移量97处被引用,因为字符“a”的ASCII码值为97。变量“s2”也指向这个字符串对象。然而,当另一个变量也被分配了相同的字符串'a'时会发生什么?1>>>s3='a'上面代码执行后,会返回之前创建的字符串对象,内容相同。因此,变量“s1”和“s3”都指向同一个字符串对象。数组“characters”用于检测字符串'a'是否已经存在,如果存在则返回指向字符串对象的指针。if(size==1&&(op=characters[*str&UCHAR_MAX])!=NULL){...return(PyObject*)op;}接下来,我们创建一个内容为'c'的新短字符串:1>>>s4='c'然后,我们将得到以下结果:我们还可以发现数组“characters”在访问字符串元素时仍然有用,如以下Python脚本所示。>>>s5='abc'>>>s5[0]'a'在上面的第二行代码中,返回的是数组“characters”偏移量97处的指针元素,而不是创建一个新值'a'的字符串。当我们访问字符串中的一个元素时,会调用一个名为“string_item”d的函数,函数体代码如下。其中,参数'a'对应字符串“abc”,参数'i'为访问数组的索引值(本例为0),函数返回一个指向字符串对象的指针。staticPyObject*string_item(PyStringObject*a,registerPy_ssize_ti){charpchar;PyObject*v;...pchar=a->ob_sval[i];v=(PyObject*)characters[pchar&UCHAR_MAX];if(v==NULL)//allocatestringelse{...Py_INCREF(v);}returnv;}函数名长度为1时也可以使用数组“characters”,如下:>>>defa():passstringlookup见下文,当当你在下面的Python代码中执行字符串搜索操作时,会发生什么?>>>s='adcabcdbdabcabd'>>>s.find('abcab')>>>11函数“find”返回一个索引值,表示在字符串“abcd”中的何处找到了字符串“s”。如果未找到该字符串,则该函数返回-1。那么,内部发生了什么?内部调用了一个名为“fastsearch”的函数。该函数是BoyerMoore和Horspool算法的混合版本,具有两者的优良特性。我们将“s”(s='adcabcdbdabcabd')称为主字符串,将“p”(p='abcab')称为模式字符串。n和m分别表示字符串s和字符串p的长度,其中n=15,m=5。在下面的代码段中,很明显程序会进行***判断:如果m>n,我们知道找不到这样的索引号,所以函数直接返回-1。w=n-m;如果(w<0)返回-1;当m=1时,程序在字符串s中逐字符遍历,匹配成功则返回对应的索引位置。本例中变量mode的值为FAST_SEARCH,也就是说我们要获取的是最后一次匹配在主串中的位置,而不是模式串在主串中匹配成功的次数。如果(m<=1){...if(mode==FAST_COUNT){...}else{for(i=0;i1。首先创建一个压缩的boyer-mooredelta1表(对应BM算法中的badcharacterrule),在这个过程中,需要两个变量被宣布:“掩码”和“跳过”。“掩码”是一个32位的位掩码(bitmask),它使用它最重要的5个特征位作为开关位。掩码是通过使用模式字符串“p”进行操作生成的。它被设计成一个布隆过滤器(bloomfilter),用来检测一个字符是否出现在当前字符串中。这种机制使得查找操作非常快,但是存在误报。如果你想了解更多关于布隆过滤器的信息,你可以在这里查看。对于此示例,下面将准确说明位掩码是如何生成的。mlast=m-1/*processpattern[:-1]*/for(mask=i=0;i