本章通过底层C源码来分析Python里的字符串对象,以及字符串相关的基本操作,比如,加法运算,乘法运算,取余运算等。在介绍取余运算时,对字符串格式化相关的内容进行了介绍...

    页面导航: 英文教程的下载地址:

    本篇文章是根据英文教程《Python Tutorial》来写的学习笔记。该英文教程的下载地址如下:

    百度盘地址:http://pan.baidu.com/s/1c0eXSQG

    DropBox地址:点此进入DropBox链接

    Google Drive:点此进入Google Drive链接

    这是学习笔记,不是翻译,因此,内容上会与英文原著有些不同。以下记录是根据英文教程的第九章来写的。(文章中的部分链接,包括下载链接,可能需要通过代理访问!)

    本篇文章也会涉及到Python的C源代码,这些C源码都是2.7.8版本的。想使用gdb来调试python源代码的话,就需要按照前面"Python基本的操作运算符"文章中所说的,使用configure --with-pydebug命令来重新编译安装python。

    如果Python的官方网站取消了Python-2.7.8.tgz的源码包的话,可以在以下两个链接中下载到:

    DropBox:Python-2.7.8.tgz的DropBox网盘链接

    Google Drive:Python-2.7.8.tgz的Google Drive网盘链接

字符串类型:

    先看个简单的例子:

[email protected]:~$ gdb -q python
....................................................
>>> 'hello world'
....................................................

Breakpoint 1, parsestr (c=0xbffff0dc, n=0xb7d8d578, 
    s=0xb7dd4508 "'hello world'") at Python/ast.c:3450
3450	        int quote = Py_CHARMASK(*s);
....................................................

Breakpoint 2, PyString_FromStringAndSize (str=0xbfffe804 "hello world", 
    size=11) at Objects/stringobject.c:60
60	    if (size < 0) {
(gdb) until 111
PyString_FromStringAndSize (str=0xbfffe804 "hello world", size=11)
    at Objects/stringobject.c:111
111	    return (PyObject *) op;
(gdb) ptype (PyStringObject*)op
type = struct {
    struct _object *_ob_next;
    struct _object *_ob_prev;
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
    Py_ssize_t ob_size;
    long int ob_shash;
    int ob_sstate;
    char ob_sval[1];
} *
(gdb) p op->ob_size
$3 = 11
(gdb) p (char*)op->ob_sval 
$4 = 0xb7ca1ba4 "hello world"
(gdb) c
Continuing.

....................................................
'hello world'
[40761 refs]
>>> quit()

....................................................
[email protected]:~$ 


    从上面的gdb调试中,可以看到,'hello world'字符串(包括单引号在内)都会被传到parsestr函数里(该函数定义在Python/ast.c文件中),该函数会对引号(单引号或双引号)进行解析,并读取出里面的hello world字符串,再将该字符串通过PyString_FromStringAndSize函数(定义在Objects/stringobject.c文件里),来创建一个PyStringObject类型的字符串对象。从上面的ptype指令可以看出来,字符串对象的内部C结构如下:

type = struct {
    struct _object *_ob_next;
    struct _object *_ob_prev;
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
    Py_ssize_t ob_size;
    long int ob_shash;
    int ob_sstate;
    char ob_sval[1];
}


    前4个字段,在之前"Python的数字类型及相关的类型转换函数"的文章里介绍过,是每个Python对象都具有的字段。第5个ob_size字段在字符串对象中,用于表示字符串里所包含的字符个数,例如,上面gdb调试中,p op->ob_size指令的结果就显示出"hello world"字符串一共包含11个字符。第6个ob_shash字段用于存储字符串对象的哈希值。第7个ob_sstate字段用于存储字符串对象相关的状态信息。最后一个ob_sval字段是一个char指针类型,用于指向实际的"hello world"字符串信息,虽然上面的C结构里,ob_sval被定义为长度为1的字符数组,但是在PyString_FromStringAndSize函数中,会根据字符串的尺寸,来创建一个新的PyStringObject类型的变量,该变量的底部会给字符串数据预留足够的空间。

    通过中括号和冒号来获取子字符串的操作,最终也是通过PyString_FromStringAndSize函数来完成的:

[email protected]:~$ gdb -q python
....................................................
>>> 'hello world'[2:8]

Breakpoint 1, string_slice (a=0xb7ca1bf8, i=2, j=8)
    at Objects/stringobject.c:1133
1133	    if (i < 0)
(gdb) until 1146
string_slice (a=0xb7ca1bf8, i=2, j=8) at Objects/stringobject.c:1146
1146	    return PyString_FromStringAndSize(a->ob_sval + i, j-i);
(gdb) p (char *)a->ob_sval + i
$1 = 0xb7ca1c16 "llo world"
(gdb) p j-i
$2 = 6
(gdb) c
Continuing.
'llo wo'
....................................................
[email protected]:~$ 


    上面的'hello world'[2:8]脚本语句在执行时,最终会进入到string_slice的底层C函数中,该函数最终会通过PyString_FromStringAndSize(a->ob_sval + i, j-i)函数,来构建一个新的字符串对象,该对象所包含的字符串,是原来的'hello world'的第2个字符到第7个字符的copy拷贝,也就是'llo wo'字符串。

    上面提到的string_slice与PyString_FromStringAndSize函数都位于Objects/stringobject.c文件里,该文件中定义了很多和字符串对象操作相关的底层C函数,例如,string_concat函数:

static PyObject *
string_concat(register PyStringObject *a, register PyObject *bb)
{
    ................................................
    size = Py_SIZE(a) + Py_SIZE(b);
    ................................................
    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, a->ob_sval, Py_SIZE(a));
    Py_MEMCPY(op->ob_sval + Py_SIZE(a), b->ob_sval, Py_SIZE(b));
    op->ob_sval[size] = '\0';
    return (PyObject *) op;
}


    当两个字符串对象进行加法运算时,就会进入到上面的string_concat函数里,在该函数中,会先计算出两个字符串的总字符数,并存储在size变量里,接着通过PyObject_MALLOC创建一个PyStringObject的字符串类型的对象,在该对象所在的堆空间里也为size(字符串数据尺寸)预留了空间,最后会通过Py_MEMCPY将需要连接的两个字符串的实际数据,拷贝到新字符串对象的ob_sval字段所指向的堆空间中。最后将这个新的字符串对象作为结果返回。以下是gdb的调试过程:

[email protected]:~$ gdb -q python
....................................................
>>> "hello" + 'world'
....................................................


Breakpoint 1, string_concat (a=0xb7ca1a38, bb=0xb7ca1a00)
    at Objects/stringobject.c:1019
1019	    if (!PyString_Check(bb)) {
(gdb) until 1042
string_concat (a=0xb7ca1a38, bb=0xb7ca1a00) at Objects/stringobject.c:1042
1042	    size = Py_SIZE(a) + Py_SIZE(b);
(gdb) n
1047	    if (Py_SIZE(a) < 0 || Py_SIZE(b) < 0 ||
(gdb) p size
$1 = 10
(gdb) until 1069
string_concat (a=0xb7ca1a38, bb=0xb7ca1bdc) at Objects/stringobject.c:1069
1069	    return (PyObject *) op;
(gdb) p (char*)op->ob_sval
$2 = 0xb7ca1bdc "helloworld"
(gdb) c
Continuing.
'helloworld'
[40763 refs]
>>> quit()
[18354 refs]

Program exited normally.
(gdb) q
[email protected]:~$ 


转义字符:

    在Python的字符串中,也可以存在Escape Characters(转义字符),上面提到过parsestr的底层C函数,所有的字符串(单引号括起来的、双引号括起来的、三重引号括起来的、utf8编码的、unicode编码的等)在组建字符串对象时,都会先进入到parsestr函数(位于Python/ast.c):

static PyObject *
parsestr(struct compiling *c, const node *n, const char *s)
{
	............................................
	if (rawmode || strchr(s, '\\') == NULL) {
		if (need_encoding) {
	............................................
			v = PyUnicode_AsEncodedString(u, c->c_encoding, NULL);
	............................................
		} else {
	............................................
		}
	}

	return PyString_DecodeEscape(s, len, NULL, unicode,
			need_encoding ? c->c_encoding : NULL);
}


    上面函数里,在非rawmode模式(下面会介绍)下,当strchr检测到s(指向字符串数据)里没有包含‘\\’反斜杠时,就说明字符串中没有转义字符,就会通过PyUnicode_AsEncodedString函数并最终调用上面提到过的PyString_FromStringAndSize函数来创建字符串对象。而非rawmode模式下,当strchr检测到反斜杠字符时,就会进入PyString_DecodeEscape函数来解析转义字符,并创建对应的字符串对象,以下是PyString_DecodeEscape函数的部分代码(该函数也位于Objects/stringobject.c文件中):

PyObject *PyString_DecodeEscape(const char *s,
                                Py_ssize_t len,
                                const char *errors,
                                Py_ssize_t unicode,
                                const char *recode_encoding)
{
	...............................................
	switch (*s++) {
	/* XXX This assumes ASCII! */
	case '\n': break;
	case '\\': *p++ = '\\'; break;
	case '\'': *p++ = '\''; break;
	case '\"': *p++ = '\"'; break;
	case 'b': *p++ = '\b'; break;
	case 'f': *p++ = '\014'; break; /* FF */
	case 't': *p++ = '\t'; break;
	case 'n': *p++ = '\n'; break;
	case 'r': *p++ = '\r'; break;
	case 'v': *p++ = '\013'; break; /* VT */
	case 'a': *p++ = '\007'; break; /* BEL, not classic C */
	case '0': case '1': case '2': case '3':
	case '4': case '5': case '6': case '7':
		...............................................
		break;
	case 'x':
		...............................................
		break;
#ifndef Py_USING_UNICODE
	case 'u':
	case 'U':
	case 'N':
		if (unicode) {
			PyErr_SetString(PyExc_ValueError,
					  "Unicode escapes not legal "
					  "when Unicode disabled");
			goto failed;
		}
#endif
	default:
		*p++ = '\\';
		s--;
		goto non_esc; /* an arbitrary number of unescaped
						 UTF-8 bytes may follow. */
	}
	...............................................
}


    上面函数里,显示出了PyStringObject字符串对象所支持的转义字符,上面的'u' ,'U' 和 'N'这三个转义字符只在unicode编码的字符串中起作用(unicode字符串下面会介绍),其余的转义字符如下表所示:

转义字符
(包括反斜杠)
转义描述
\\ 两个反斜杠在一起,会转成ASCII码为0x5C的字符,
也就是普通的反斜杠字符。
\' 反斜杠加单引号,会转成ASCII码为0x27的字符,
也就是普通的单引号字符。
\" 反斜杠加双引号,会转成ASCII码为0x22的字符,
也就是普通的双引号字符。
\b 反斜杠加字符b,会转成ASCII码为0x08的字符,
也就是ASCII码表里的BS (backspace 退格符)。
\f 反斜杠加字符f,会转成ASCII码为0x0C的字符,
也就是ASCII码表里的FF (NP form feed, new page 换页符)。
\t 反斜杠加字符t,会转成ASCII码为0x09的字符,
也就是ASCII码表里的HT (horizontal tab 水平制表符)。
\n 反斜杠加字符n,会转成ASCII码为0x0A的字符,
也就是ASCII码表里的LF (NL line feed, new line 换行符)。
\r 反斜杠加字符r,会转成ASCII码为0x0D的字符,
也就是ASCII码表里的CR (carriage return 回车符)
\v 反斜杠加字符v,会转成ASCII码为0x0B的字符,
也就是ASCII码表里的VT (vertical tab 垂直制表符)
\a 反斜杠加字符a,会转成ASCII码为0x07的字符,
也就是ASCII码表里的BEL (bell 响铃符)
\nnn 反斜杠加三个数字(这三个数字都必须是0到7的数字),
会转成这三个数字所构成的八进制值。
例如:\101会转成的八进制值为0101,对应的十六进制值为0x41,
0x41为'A'的ASCII码,因此,'\101'就可以转成字符'A'。
\xnn 反斜杠加两个数(这两个数都必须是0到9,或A到F,或a到f的十六进制数),
会转成这两个数所构成的十六进制值。
例如:\x41会转成的十六进制值就是0x41 ,也就是字符'A'的ASCII码。

    以下是Python转义字符的简单例子:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> '%#x' % ord('\\')
'0x5c'
>>> print('\\')
\
>>> '%#x' % ord('\'')
'0x27'
>>> print('\'')
'
>>> '%#x' % ord("\"")
'0x22'
>>> print("\"")
"
>>> '%#x' % ord('\b')
'0x8'
>>> '%#x' % ord('\f')
'0xc'
>>> '%#x' % ord('\t')
'0x9'
>>> '%#x' % ord('\n')
'0xa'
>>> '%#x' % ord('\r')
'0xd'
>>> '%#x' % ord('\v')
'0xb'
>>> '%#x' % ord('\a')
'0x7'
>>> '%#x' % ord('\101')
'0x41'
>>> '\101'
'A'
>>> '%#x' % ord('\x41')
'0x41'
>>> '\x41'
'A'
>>> print '\'\\hello world\\\x41\x42\n\t\x43\x44\''
'\hello world\AB
	CD'
>>> quit()
[email protected]:~$ 


    上面例子中,这些字符串对象所使用的字符编码,是与本地系统相关的。例如,在作者的Linux环境下,默认就是UTF8编码。要使用unicode编码的话,可以在字符串前面加上 u 或 U 的前缀,在之前介绍的parsestr函数里,就可以看到相关的C代码:

static PyObject *
parsestr(struct compiling *c, const node *n, const char *s)
{
	.......................................
	if (isalpha(quote) || quote == '_') {
		if (quote == 'u' || quote == 'U') {
			quote = *++s;
			unicode = 1;
		}
		.......................................
	}
	.......................................
#ifdef Py_USING_UNICODE
	if (unicode || Py_UnicodeFlag) {
		return decode_unicode(c, s, len, rawmode, c->c_encoding);
	}
#endif
	.......................................
}


    从上面的C代码中,可以看到,当字符串前面(单引号或双引号前面)有 'u''U' 的前缀字符时,就会将unicode变量设置为1,在unicode变量为1时,就会通过decode_unicode函数来创建一个unicode字符串对象,该对象里的字符编码都是UCS-2或UCS-4的unicode字符编码(UCS-2用两个字节来编码,UCS-4用四个字节)。下面是uU前缀的简单例子:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> u'hello'
u'hello'
>>> U'hello'
u'hello'
>>> quit()
[email protected]:~$ 


    上面的decode_unicode函数,会通过PyUnicode_DecodeUnicodeEscape函数(位于Objects/unicodeobject.c文件中)来处理unicode字符串中的转义字符:

PyObject *PyUnicode_DecodeUnicodeEscape(const char *s,
                                        Py_ssize_t size,
                                        const char *errors)
{
	................................................
	switch (c) {

	/* \x escapes */
	case '\n': break;
	case '\\': *p++ = '\\'; break;
	case '\'': *p++ = '\''; break;
	case '\"': *p++ = '\"'; break;
	case 'b': *p++ = '\b'; break;
	case 'f': *p++ = '\014'; break; /* FF */
	case 't': *p++ = '\t'; break;
	case 'n': *p++ = '\n'; break;
	case 'r': *p++ = '\r'; break;
	case 'v': *p++ = '\013'; break; /* VT */
	case 'a': *p++ = '\007'; break; /* BEL, not classic C */

	/* \OOO (octal) escapes */
	case '0': case '1': case '2': case '3':
	case '4': case '5': case '6': case '7':
		................................................

	/* hex escapes */
	/* \xXX */
	case 'x':
		digits = 2;
		message = "truncated \\xXX escape";
		goto hexescape;

	/* \uXXXX */
	case 'u':
		digits = 4;
		message = "truncated \\uXXXX escape";
		goto hexescape;

	/* \UXXXXXXXX */
	case 'U':
		digits = 8;
		message = "truncated \\UXXXXXXXX escape";
	hexescape:
		................................................

	/* \N{name} */
	case 'N':
		message = "malformed \\N character escape";
		................................................
	default:
		................................................
	}
	................................................
}


    可以看到,unicode字符串里的'\x','\u' 和 '\U'这三个转义字符都是hex escapes(十六进制转义字符),只不过\x后面只能跟随两个数,也就只能表示两位十六进制数(其实是4位,只不过转成UCS-2字符时,高位会被0填充)。\u后面则可以跟随4个数,以构成包含4位的十六进制数,这样就可以用来表示UCS2(标准的unicode编码,由两个字节组成)中的unicode编码了。\U后面可以跟随8个数,以构成包含8位的十六进制数,这样就可以用来表示UCS4(也是unicode编码,由4个字节组成)中的编码了。

    '\N'转义字符则可以根据Unicode database(Unicode数据库)中的字符名称,来转成对应的unicode字符,下面是\u\U\N这三个转义字符的例子:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> u'你好'
u'\u4f60\u597d'
>>> print u'\u4f60\u597d'
你好
>>> print u'\x4f\x60\x59\x7d'
O`Y}
>>> print u'\U00004f60\U0000597d'
你好
>>> import unicodedata
>>> unicodedata.name(u'a')
'LATIN SMALL LETTER A'
>>> u'\N{LATIN SMALL LETTER A}'
u'a'
>>> print u'\N{LATIN SMALL LETTER A}'
a
>>> quit()
[email protected]:~$ 


    中文'你好'的unicode编码分别为0x4f60与0x597d,由于需要4位十六进制,因此,只能使用\u\U转义字符,如果使用\x的话,将无法得到对应的编码,因为,\x4f在转成对应的UCS-2字符编码时,高位会被0填充,因此\x4f转成的其实是0x004f的编码。此外,通过unicodedata模块里的name函数,我们知道字符'a'在unicode数据库里的名称为'LATIN SMALL LETTER A',这样,在unicode字符串中,通过向\N转义符后面的大括号里,写入该名称,就可以得到字符'a'对应的unicode编码了。

字符串的基本操作:

    在Objects/stringobject.c文件中,有一个string_as_sequence的结构体变量,该变量中包含了和字符串基本操作相关的底层C函数:

static PySequenceMethods string_as_sequence = {
    (lenfunc)string_length, /*sq_length*/
    (binaryfunc)string_concat, /*sq_concat*/
    (ssizeargfunc)string_repeat, /*sq_repeat*/
    (ssizeargfunc)string_item, /*sq_item*/
    (ssizessizeargfunc)string_slice, /*sq_slice*/
    0,                  /*sq_ass_item*/
    0,                  /*sq_ass_slice*/
    (objobjproc)string_contains /*sq_contains*/
};


    下面是string_length函数的例子:

[email protected]:~$ gdb -q python
....................................................
>>> len('hello')
....................................................
Breakpoint 1, string_length (a=0xb7ca1a00) at Objects/stringobject.c:1011
1011	    return Py_SIZE(a);
(gdb) p a->ob_size
$1 = 5
(gdb) c
Continuing.
5


    len是内建模块里的脚本函数,当该函数的参数是字符串对象时,它在内部会通过string_length这个底层C函数,来获取字符串里的字符个数,字符个数是存储在字符串对象的ob_size字段中的。

    下面是string_concat的例子:

[email protected]:~$ gdb -q python
....................................................
>>> 'hello' + 'world'
....................................................
Breakpoint 1, string_concat (a=0xb7ca1b50, bb=0xb7ca1a00)
    at Objects/stringobject.c:1019
1019	    if (!PyString_Check(bb)) {
(gdb) until 1069
string_concat (a=0xb7ca1b50, bb=0xb7ca1bdc) at Objects/stringobject.c:1069
1069	    return (PyObject *) op;
(gdb) p (char *)op->ob_sval 
$2 = 0xb7ca1bdc "helloworld"
(gdb) c
Continuing.
'helloworld'
....................................................
[email protected]:~$ 


    当对字符串执行加法运算时,在内部会通过string_concat这个底层C函数,来创建一个新的字符串对象,并将加运算左右两侧的字符串连接在一起,作为该对象的字符串数据,最后将这个新的字符串对象作为结果返回。

    下面是string_repeat的例子:

[email protected]:~$ gdb -q python
....................................................
>>> 'Hello' * 3
....................................................
Breakpoint 1, string_repeat (a=0xb7ca1b50, n=3) at Objects/stringobject.c:1081
1081	    if (n < 0)
(gdb) until 1123
string_repeat (a=0x5, n=15) at Objects/stringobject.c:1123
1123	    return (PyObject *) op;
(gdb) p (char *)op->ob_sval 
$3 = 0xb7c9efc4 "HelloHelloHello"
(gdb) c
Continuing.
'HelloHelloHello'
....................................................
[email protected]:~$ 


    当对字符串执行乘法运算时,在内部会通过string_repeat这个底层C函数,来创建出字符串的repeat数据,例如上面的'Hello' * 3就是将'Hello'重复3次,从而得到一个新的'HelloHelloHello'字符串。

    下面是string_item的例子:

[email protected]:~$ gdb -q python
....................................................
>>> 'hello'[1]
....................................................
Breakpoint 1, string_item (a=0xb7ca1b50, i=1) at Objects/stringobject.c:1173
1173	    if (i < 0 || i >= Py_SIZE(a)) {
(gdb) until 1187
string_item (a=0xb7ca1b50, i=-1210396440) at Objects/stringobject.c:1187
1187	    return v;
(gdb) p (char *)((PyStringObject*)v)->ob_sval 
$4 = 0xb7dad104 "e"
(gdb) c
Continuing.
'e'
....................................................
[email protected]:~$ 


    当通过中括号加索引值来获取字符串里的字符时,内部会通过string_item来获取字符串里的元素,从string_item函数的C源码中可以看到,它会为该字符准备一个字符串对象,该对象的ob_size值为1,因此,python中没有像C语言里面的字符的概念,只有字符串的概念,哪怕是看起来像字符,例如上面的'e'其实是长度为1的字符串对象。

    下面是string_slice的例子:

[email protected]:~$ gdb -q python
....................................................
>>> 'hello'[1:4]
....................................................
Breakpoint 1, string_slice (a=0xb7ca1b50, i=1, j=4)
    at Objects/stringobject.c:1133
1133	    if (i < 0)
(gdb) c
Continuing.
'ell'
....................................................
[email protected]:~$ 


    当字符串后面跟随中括号加冒号时,内部会通过string_slice这个底层C函数来获取到对应的子字符串数据,例如上面的'hello'[1:4]脚本执行时,得到的就是从索引值1开始到索引值4结束的子字符串(不包括4在内),因此,结果就是'ell'。

    下面是string_contains的例子:

[email protected]:~$ gdb -q python
....................................................
>>> 'el' in 'hello'
....................................................
Breakpoint 1, string_contains (str_obj=0xb7ca1bc0, sub_obj=0xb7ca0178)
    at Objects/stringobject.c:1152
1152	    if (!PyString_CheckExact(sub_obj)) {
(gdb) c
Continuing.
True
[40761 refs]
>>> 'xd' not in 'hello'
....................................................
Breakpoint 1, string_contains (str_obj=0xb7ca1bc0, sub_obj=0xb7ca0178)
    at Objects/stringobject.c:1152
1152	    if (!PyString_CheckExact(sub_obj)) {
(gdb) c
Continuing.
True
....................................................
[email protected]:~$ 


    当对字符串使用 innot in 运算符时,内部会通过string_contains这个底层C函数,在右侧的字符串里查找左侧的字符串,当可以找到左侧的字符串时,就返回1,如果找不到,则返回0 。对于in运算符,1会被上层函数转为True对象,0转为False对象。对于not in运算符,则刚好相反,1被转为False,0被转为True。因此,上面的 'hello' 中可以找到 'el' 子字符串,那么 'el' in 'hello' 的结果就是True。另外,'hello'中找不到'xd',因此'xd' not in 'hello'就返回True。

取余运算及相关格式化操作:

    Python的字符串对象除了可以进行加法运算和乘法运算外,还可以进行取余运算:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 'my scores is %d' % 98
'my scores is 98'
>>> 'my scores is %d and your scores is %d' % (98, 99)
'my scores is 98 and your scores is 99'
>>> quit()
[email protected]:~$ 


    从上面的例子中,可以看出来,Python中字符串的取余运算,类似于C语言里的printf函数,可以对字符串进行格式化操作。

    和字符串格式化相关的底层C函数位于Objects/stringobject.c文件中:

static PyObject *
string_mod(PyObject *v, PyObject *w)
{
    ................................................
    return PyString_Format(v, w);
}

PyObject *
PyString_Format(PyObject *format, PyObject *args)
{
	................................................
}


    当执行取余操作时,会先进入到string_mod函数中,该函数会通过PyString_Format函数来完成具体的格式化操作:

PyObject *
PyString_Format(PyObject *format, PyObject *args)
{
	................................................
	PyObject *dict = NULL;
	................................................
	if (PyTuple_Check(args)) {
		arglen = PyTuple_GET_SIZE(args);
		argidx = 0;
	}
	else {
		arglen = -1;
		argidx = -2;
	}
	if (Py_TYPE(args)->tp_as_mapping && Py_TYPE(args)->tp_as_mapping->mp_subscript &&
		!PyTuple_Check(args) && !PyObject_TypeCheck(args, &PyBaseString_Type))
		dict = args;
	................................................
}


    从PyString_Format函数开头的C代码中,可以看到,取余运算符右侧的操作数可以是Dict(词典类型),Tuple(元组类型) 或者单个非元组的对象(如整数,浮点数,字符串),如下例所示:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> '%(key1)d %(key2)d %(key3)d' % {'key1':123, 'key2':456, 'key3':789}
'123 456 789'
>>> '%d %d %d' % (95, 96, 97)
'95 96 97'
>>> '%s' % 'hello world'
'hello world'
>>> '%f' % 3.14159
'3.141590'
>>> quit()
[email protected]:~$ 


    取余运算符左侧的操作数则是格式化字符串,格式化字符串里的百分号的格式如下:

%[key][flags][width][prec]type

    上面方括号里的部分都是可选的部分,在上面介绍的PyString_Format这个底层C函数中,我们可以看到这几个部分相关的C源码:

PyObject *
PyString_Format(PyObject *format, PyObject *args)
{
	................................................
	if (*fmt == '(') {
		................................................
		keystart = fmt;
		/* Skip over balanced parentheses */
		while (pcount > 0 && --fmtcnt >= 0) {
			if (*fmt == ')')
				--pcount;
			else if (*fmt == '(')
				++pcount;
			fmt++;
		}
		keylen = fmt - keystart - 1;
		................................................
		key = PyString_FromStringAndSize(keystart,
					keylen);
		................................................
		args = PyObject_GetItem(dict, key);
		................................................
	}
	................................................
}


    当%后面有小括号时,小括号里的字符串会作为词典中的Key,该key对应的值,就会作为当前百分号的格式化的结果,如下所示:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> '%(k2)d %(k1)d' % {'k1':123, 'k2':456}
'456 123'
>>> quit()
[email protected]:~$ 


    以上就是百分号后面的key部分,接着就是可选的flags部分,对应的C源码如下:

PyObject *
PyString_Format(PyObject *format, PyObject *args)
{
	................................................
	if (*fmt == '(') {
		................................................
	}
	while (--fmtcnt >= 0) {
		switch (c = *fmt++) {
		case '-': flags |= F_LJUST; continue;
		case '+': flags |= F_SIGN; continue;
		case ' ': flags |= F_BLANK; continue;
		case '#': flags |= F_ALT; continue;
		case '0': flags |= F_ZERO; continue;
		}
		break;
	}
	................................................
}


    可以看出来,flags部分可以是 '-''+'' ''#''0' 这些字符。其中,'-'字符可以进行左对齐。'+'字符可以让数字显示出正负符号位。' '即空格符可以让数字前面的正号用空格来代替。'#'字符会在十六进制数前面加上 0x 或 0X 的前缀,以及在八进制数前面加上0的前缀。'0'字符表示用字符'0'代替空格符来进行填充。这些flags的用法,如下所示:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> '%5d' % 12
'   12'
>>> '%-5d' % 12
'12   '
>>> '%+d' % 12
'+12'
>>> '% d' % 12
' 12'
>>> '%d' % 12
'12'
>>> '%#x' % 95
'0x5f'
>>> '%x' % 95
'5f'
>>> '%#X' % 95
'0X5F'
>>> '%X' % 95
'5F'
>>> '%05d' % 12
'00012'
>>> '%5d' % 12
'   12'
>>> quit()
[email protected]:~$ 


    在flags后面,是可选的width部分,如上例中的'%-5d'里的数字5就是width宽度部分,和width相关的C源码如下:

PyObject *
PyString_Format(PyObject *format, PyObject *args)
{
	................................................
	if (*fmt == '(') {
		................................................
	}
	while (--fmtcnt >= 0) {
		................................................
	}
	if (c == '*') {
		v = getnextarg(args, arglen, &argidx);
		................................................
		width = PyInt_AsSsize_t(v);
		................................................
	}
	else if (c >= 0 && isdigit(c)) {
		width = c - '0';
		while (--fmtcnt >= 0) {
			c = Py_CHARMASK(*fmt++);
			if (!isdigit(c))
				break;
			................................................
			width = width*10 + (c - '0');
		}
	}
	................................................
}


    从上面的代码里可以看到,当width部分为 * 时,可以从取余运算符后面的参数里获取到宽度信息,如果不是星号字符,则直接使用你提供的数字作为width宽度,如下所示:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> '%*d' % (5, 12)
'   12'
>>> '%5d' % 12
'   12'
>>> quit()
[email protected]:~$ 


    width后面是可选的prec部分,相关C源码如下:

PyObject *
PyString_Format(PyObject *format, PyObject *args)
{
	................................................
	if (c == '.') {
		................................................
		if (c == '*') {
			v = getnextarg(args, arglen, &argidx);
			................................................
			prec = _PyInt_AsInt(v);
			................................................
		}
		else if (c >= 0 && isdigit(c)) {
			prec = c - '0';
			while (--fmtcnt >= 0) {
				c = Py_CHARMASK(*fmt++);
				if (!isdigit(c))
					break;
				................................................
				prec = prec*10 + (c - '0');
			}
		}
	} /* prec */
	................................................
}


    prec部分以'.'即小数点开始,它和width一样,也可以设置星号来从后面的参数中获取到prec信息,prec主要是用于浮点数,对于e和f类型,它表示浮点数的小数点后的有效位数。对于g类型,它则表示的是整数与小数部分的总有效位数,如下所示:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> '%.3e' % 12.3456789
'1.235e+01'
>>> '%.3f' % 12.3456789
'12.346'
>>> '%.3g' % 12.3456789
'12.3'
>>> '%.*g' % (3, 12.3456789)
'12.3'
>>> quit()
[email protected]:~$ 


    prec后面就是必须有的type部分,如上例中的e 、f 、g这些字符,和type部分相关的C源码如下:

PyObject *
PyString_Format(PyObject *format, PyObject *args)
{
	................................................
	switch (c) {
	case '%':
		............................................
	case 's':
		............................................
	case 'r':
		............................................
	case 'i':
	case 'd':
	case 'u':
	case 'o':
	case 'x':
	case 'X':
		............................................
	case 'e':
	case 'E':
	case 'f':
	case 'F':
	case 'g':
	case 'G':
		............................................
	case 'c':
		............................................
	default:
		............................................
	}
	................................................
}


    可以看到,type部分可以是'%' 、's' 、'r' 、'i' 、'd' 、'u' 、'o' 、'x' 、'X' 、'e' 、'E' 、'f' 、'F' 、'g' 、'G' 和 'c'这些字符。

    当type为'%'时,得到的就是普通的百分号字符,当type为's'时,它会将后面的字符串参数作为格式化的结果。如下所示:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> '%% %s' % 'hello world'
'% hello world'
>>> quit()
[email protected]:~$ 


    当type为'r'时,它会将参数对象的representation作为格式化的结果(比如模块对象,就会通过module_repr这个底层的C函数,将自己的模块名等信息以字符串的形式进行返回,这样r类型就可以将这段字符串作为格式化的结果了)。如下所示:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import random
>>> '%r' % random
"<module 'random' from '/usr/local/lib/python2.7/random.pyc'>"
>>> '%r' % __builtins__
"<module '__builtin__' (built-in)>"
>>> '%r' % max
'<built-in function max>'
>>> quit()
[email protected]:~$ 


    'i' ,'d','u'这三个type字符并没有什么区别,因为在C源码中,'i'在一开始就会被转为'd'字符,而'u'字符在处理负数时,也会被转为'd'字符,因此,它们三者之间并没什么太大的区别。如下所示:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> '%i' % 12345
'12345'
>>> '%d' % 12345
'12345'
>>> '%u' % 12345
'12345'
>>> '%i' % -12345
'-12345'
>>> '%d' % -12345
'-12345'
>>> '%u' % -12345
'-12345'
>>> '%d' % 0xfffffffffffff
'4503599627370495'
>>> '%u' % 0xfffffffffffff
'4503599627370495'
>>> '%d' % -0xfffffffffffff
'-4503599627370495'
>>> '%u' % -0xfffffffffffff
'-4503599627370495'
>>> u'%d' % -0xfffffffffffff
u'-4503599627370495'
>>> u'%u' % -0xfffffffffffff
u'-4503599627370495'
>>> quit()
[email protected]:~$ 


    当type为'o'时,将使用参数的八进制值,来作为格式化的结果。当type为'x'或'X'时,将使用参数的十六进制值,来作为格式化的结果。如下所示:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> '%o' % 123
'173'
>>> '%#o' % 123
'0173'
>>> '%x' % 123
'7b'
>>> '%#x' % 123
'0x7b'
>>> '%X' % 123
'7B'
>>> quit()
[email protected]:~$ 


    上面123的八进制值为173,123的十六进制值为7b 。'x'与'X'的区别只是在于,十六进制相关的字母是用小写字母还是用大写字母而已。

    当type为'e','E','f','F','g' 或 'G'字符时,就表示将浮点数格式化为字符串的形式,'e'和'E'会以指数的形式来表示浮点数,'f'和'F'则以普通的形式来表示浮点数,'g'和'G',会根据浮点数的实际情况以及prec的值,在指数与普通格式之间进行选择,如下所示:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> '%e' % 13.12345
'1.312345e+01'
>>> '%E' % 13.12345
'1.312345E+01'
>>> '%f' % 13.12345
'13.123450'
>>> '%F' % 13.12345
'13.123450'
>>> '%g' % 0.0000123456
'1.23456e-05'
>>> '%G' % 0.0000123456
'1.23456E-05'
>>> '%g' % 234.5678
'234.568'
>>> '%G' % 234.5678
'234.568'
>>> '%.2g' % 234.5678
'2.3e+02'
>>> '%.2G' % 234.5678
'2.3E+02'
>>> quit()
[email protected]:~$ 


    如果想了解g与G何时会采用指数格式以及何时会采用普通格式的话,可以参考Python/pystrtod.c文件中的format_float_short这个底层C函数:

static char *
format_float_short(double d, char format_code,
                   int mode, Py_ssize_t precision,
                   int always_add_sign, int add_dot_0_if_integer,
                   int use_alt_formatting, char **float_strings, int *type)
{
	................................................
	switch (format_code) {
	case 'e':
		use_exp = 1;
		vdigits_end = precision;
		break;
	case 'f':
		vdigits_end = decpt + precision;
		break;
	case 'g':
		if (decpt <= -4 || decpt >
			(add_dot_0_if_integer ? precision-1 : precision))
			use_exp = 1;
		if (use_alt_formatting)
			vdigits_end = precision;
		break;
	................................................
}


    上面函数里,当decpt小于等于-4,或者decpt大于precision时,就会使用指数格式,否则就使用普通格式,decpt表示浮点数的十进制小数点的位置,例如:浮点数123.1的decpt为3,浮点数12.1的decpt为2,浮点数1.1的decpt为1,浮点数0.1的decpt为0,浮点数0.01的decpt为-1,浮点数0.001的decpt为-2,浮点数0.0001的decpt为-3等等,当整数部分大于0时,小数点左侧的实际的整数位数就是decpt的值,当整数部分等于0时,小数点右侧开头的0的个数的负值就是decpt值,当decpt小于等于-4时就会使用指数格式。precision变量就是前面提到过的prec部分,当你指定的prec小于decpt时也会使用指数格式。

rawmode模式:

    之前提到过一个rawmode模式,当字符串前面存在'r'或'R'字符时,就会使用该模式,可以在之前介绍过的parsestr函数中看到相关的C代码:

static PyObject *
parsestr(struct compiling *c, const node *n, const char *s)
{
	............................................
	if (isalpha(quote) || quote == '_') {
		............................................
		if (quote == 'r' || quote == 'R') {
			quote = *++s;
			rawmode = 1;
		}
	}
	............................................
	if (rawmode || strchr(s, '\\') == NULL) {
		if (need_encoding) {
			............................................
			v = PyUnicode_AsEncodedString(u, c->c_encoding, NULL);
			............................................
		}
		............................................
	}

	return PyString_DecodeEscape(s, len, NULL, unicode,
		      need_encoding ? c->c_encoding : NULL);
}


    在rawmode模式下,C函数里就直接通过PyUnicode_AsEncodedString来创建字符串对象,而不会去调用PyString_DecodeEscape函数对字符串里的转义字符进行解析,因此,rawmode模式下字符串中的转义字符不会被转成别的字符,它们会原样保存在字符串里。如下所示:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print r'\n\r\thello\'\''
\n\r\thello\'\'
>>> quit()
[email protected]:~$ 


三重引号:

    Python里还有一个三重引号,三重引号也是在上面提到过的parsestr的C函数里进行处理的,三重引号与单个引号在创建字符串对象,以及转义字符的解析方面没什么区别,因为它们在创建字符串对象以及解析转义字符时,都是调用的相同的底层C函数,唯一的区别在于,三重引号里面可以写入多行字符串:

[email protected]:~$ python
Python 2.7.8 (default, Feb 20 2015, 12:54:46) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> '''hello world
... how do you do 
... "I'm fine "
... thanks '''
'hello world\nhow do you do \n"I\'m fine "\nthanks '
>>> u'''hello world
... hello world'''
u'hello world\nhello world'
>>> quit()
[email protected]:~$ 


    多行字符串之间的换行操作,都被自动转成了对应的'\n'换行符。

结束语:

    限于篇幅,本章先到这里,下一篇将介绍与字符串相关的函数。

    OK,休息,休息一下 o(∩_∩)o~~

    源代码就是最好的手册。

——  unknown
 
上下篇

下一篇: Python字符串相关的函数

上一篇: Python三角函数

相关文章

Python的基本语法

Python的time模块

Python的数字类型及相关的类型转换函数

Python的安装与使用

Python基本的I/O操作 (五)

Python基本的I/O操作 (三)