页面导航:
英文教程的下载地址:
本篇文章是根据英文教程《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用四个字节)。下面是
u和
U前缀的简单例子:
[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]:~$
|
当对字符串使用
in 或
not 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