按照⽤途不同, Python 内置类型可分为 数据程序两⼤类。
数据类型:
空值: None
数字: bool, int, long, float, complex
序列: str, unicode, list, tuple
字典: dict
集合: set, frozenset
2.1 数字
bool
None0、空字符串、以及没有元素的容器对象都可视为 False,反之为 True
>>> map(bool, [None, 0, “”, u””, list(), tuple(), dict(), set(), frozenset()])
[False, False, False, False, False, False, False, False, False]
虽然有点古怪,但 TrueFalse 的确可以当数字使⽤。

>>> int(True)
1
>>> int(False)
0
>>> range(10)[True]
1
>>> x = 5
>>> range(10)[x > 3]
1

int
64 位平台上, int 类型是 64 位整数 (sys.maxint),这显然能应对绝⼤多数情况。整数是虚拟机
特殊照顾对象:
从堆上按需申请名为 PyIntBlock 的缓存区域存储整数对象。
使⽤固定数组缓存 [-5, 257)
之间的⼩数字,只需计算下标就能获得指针。
• PyIntBlock
内存不会返还给操作系统,直⾄进程结束。
看看 ⼩数字⼤数字的区别:

>>> a = 15
>>> b = 15
>>> a is b
True
>>> sys.getrefcount(a)
47
>>> a = 257
>>> b = 257
>>> a is b
False
>>> sys.getrefcount(a)
2

PyIntBlock 内存只复⽤不回收,同时持有⼤量整数对象将导致内存暴涨,且不会在这些对象被回收后释放内存,造成事实上的内存泄露。
range 创建⼀个巨⼤的数字列表,这就需要⾜够多的 PyIntBlock 为数字对象提供存储空间。但换成 xrange 就不同了,每次迭代后,数字对象被回收,其占⽤内存空闲出来并被复⽤,内存也就不会暴涨了。
运⾏下⾯测试代码前,必须先安装
psutil 包,⽤来获取内存统计数据

$ sudo easy_install -U psutil

$ cat test.py
#!/usr/bin/env python
import gc, os, psutil
def test():
x = 0
for i in range(10000000):! # xrange
x += i
return x
def main():
print test()
gc.collect()
p = psutil.Process(os.getpid())
print p.get_memory_info()
if __name__ == "__main__":
main()

对⽐ range xrange 所需的 RSS 值。
range: meminfo(rss=93339648L, vms=2583552000L)! # 89 MB
xrange: meminfo(rss=8638464L, vms=2499342336L)! # 8 MB
在实际开发中,很少会遇到这样的情形。就算是海量整数去重、排序,我们也可⽤位图等算法来节约内存使⽤。 Python 3 已经⽤ xrange 替换掉了默认的 range,我们使⽤ 2.x 时稍微注意⼀下即可。
long
当超出 int 限制时, 会⾃动转换成 long。 作为变⻓对象,只要有内存⾜够,⾜以存储⽆法想象的天⽂数字。

>>> a = sys.maxint
>>> type(a)
<type 'int'>
>>> b = a + 1!! ! # 超出,⾃动使⽤ long 类型。
>>> type(b)
<type 'long'>
>>> 1 << 3000
12302319221611....890612250135171889174899079911291512399773872178519018229989376L
>>> sys.getsizeof(1 << 0xFFFFFFFF)
572662332

使⽤ long 的机会不多, Python 也就没有必要专⻔为其设计优化策略。
float
使⽤双精度浮点数 (float),不能 精确表⽰某些⼗进制的⼩数值。尤其是 四舍五⼊ (round)” 结果,可能和预想不同。

>>> 3 / 2! ! ! # 除法默认返回整数,在 Python 3 中返回浮点数。
1
>>> float(3) / 2
1.5
26>>> 3 * 0.1 == 0.3! ! # 这个容易导致莫名其妙的错误。
False
>>> round(2.675, 2)!! # 并没有想象中的四舍五⼊。
2.67

如果需要,可⽤ Decimal 代替,它能精确控制运算精度、有效数位和 round 的结果。

>>> from decimal import Decimal, ROUND_UP, ROUND_DOWN
>>> float('0.1') * 3 == float('0.3')! ! ! ! ! # float 转型精度不同
False
>>> Decimal('0.1') * 3 == Decimal('0.3')! ! ! ! # decimal 没有问题
True
>>> Decimal('2.675').quantize(Decimal('.01'), ROUND_UP)! ! # 精确控制 round
Decimal('2.68')
>>> Decimal('2.675').quantize(Decimal('.01'), ROUND_DOWN)
Decimal('2.67')

在内存管理上, float 也采⽤ PyFloatBlock 模式,但没有特殊的 ⼩浮点数
2.2 字符串
与字符串相关的问题总是很多,⽐如池化 (intern)、编码 (encode) 等。字符串是不可变类型,保存字符序列或⼆进制数据。
短字符串存储在 arena 区域, strunicode 单字符会被永久缓存。
• str 没有缓存机制, unicode 则保留 1024 个宽字符⻓度⼩于 9
的复⽤对象。
内部包含 hash 值, str
另有标记⽤来判断是否被池化。
字符串常量定义简单⾃由,可以是单引号、双引号或三引号。但我个⼈建议⽤双引号表⽰字符串,⽤单引号表⽰字符,和其他语⾔习惯保持⼀致。

>>> "It's a book."! ! ! ! # 双引号⾥⾯可以⽤单引号。
"It's a book."
>>> 'It\'s a book.'!! ! ! # 转义
"It's a book."
>>> '{"name":"Tom"}'! ! ! # 单引号⾥⾯正常使⽤双引号。
'{"name":"Tom"}'
>>> """! ! ! ! ! # 多⾏
... line 1
... line 2
... """
>>> r"abc\x"!! ! ! ! # r 前缀定义⾮转义的 raw-string。
'abc\\x'
>>> "a" "b" "c"! ! ! ! # ⾃动合并多个相邻字符串。
'abc'
>>> "中国⼈"! ! ! ! ! # UTF-8 字符串 (Linux 系统默认)。
'\xe4\xb8\xad\xe5\x9b\xbd\xe4\xba\xba'
>>> type(s), len(s)
<type 'str'>, 9
>>> u"中国⼈"!! ! ! ! # 使⽤ u 前缀定义 UNICODE 字符串。
u'\u4e2d\u56fd\u4eba'
>>> type(u), len(u)
<type 'unicode'>, 3

基本操作:

>>> "a" + "b"
'ab'
>>> "a" * 3
'aaa'
>>> ",".join(["a", "b", "c"])! ! ! ! ! # 合并多个字符串。
'a,b,c'
>>> "a,b,c".split(",")! ! ! ! ! ! # 按指定字符分割。
['a', 'b', 'c']
>>> "a\nb\r\nc".splitlines()! ! ! ! ! # 按⾏分割。
['a', 'b', 'c']
>>> "a\nb\r\nc".splitlines(True)!! ! ! ! # 分割后,保留换⾏符。
['a\n', 'b\r\n', 'c']
>>> "abc".startswith("ab"), "abc".endswith("bc")! ! # 判断是否以特定⼦串开始或结束。
True, True
>>> "abc".upper(), "Abc".lower()!! ! ! ! # ⼤⼩写转换。
'ABC', 'abc'
>>> "abcabc".find("bc"), "abcabc".find("bc", 2)! ! # 可指定查找起始结束位置。
1, 4
>>> " abc".lstrip(), "abc ".rstrip(), " abc ".strip()! # 剔除前后空格。
'abc', 'abc', 'abc'
>>> "abc".strip("ac")! ! ! ! ! ! # 可删除指定的前后缀字符。
'b'
>>> "abcabc".replace("bc", "BC")!! ! ! ! # 可指定替换次数。
'aBCaBC'
>>> "a\tbc".expandtabs(4)!! ! ! ! ! # 将 tab 替换成空格。
'a bc'
>>> "123".ljust(5, '0'), "456".rjust(5, '0'), "abc".center(10, '*')! # 填充
'12300', '00456', '***abc****'
>>> "123".zfill(6), "123456".zfill(4)! ! ! ! ! ! # 数字填充
'000123', '123456'

编码
Python 2.x 默认采⽤ ASCII 编码。为了完成编码转换,必须和操作系统字符编码统⼀起来。

>>> import sys, locale
>>> sys.getdefaultencoding()! ! # Python 默认编码。
'ascii'
>>> c = locale.getdefaultlocale(); c! # 获取当前系统编码。
('zh_CN', 'UTF-8')
>>> reload(sys)! ! ! ! # setdefaultencoding 在被初始化时被 site.py 删掉了。
<module 'sys' (built-in)>
>>> sys.setdefaultencoding(c[1])!! # 重新设置默认编码。

strunicode 都提供了 encode decode 编码转换⽅法。
• encode:
将默认编码转换为其他编码。
• decode: 将默认或者指定编码字符串转换为 unicode

>>> s = "中国⼈"; s
'\xe4\xb8\xad\xe5\x9b\xbd\xe4\xba\xba'
>>> u = s.decode(); u! ! ! # UTF-8 -> UNICODE
u'\u4e2d\u56fd\u4eba'
>>> gb = s.encode("gb2312"); gb! ! # UTF-8 -> GB2312
'\xd6\xd0\xb9\xfa\xc8\xcb'
>>> gb.encode("utf-8")! ! ! # encode 会把 gb 当做默认 UTF-8 编码,所以出错。
UnicodeDecodeError: 'utf8' codec can't decode byte 0xd6 in position 0: invalid
continuation byte
>>> gb.decode("gb2312")! ! ! # 可以将其转换成 UNICODE。
u'\u4e2d\u56fd\u4eba'
>>> gb.decode("gb2312").encode()!! # 然后再转换成 UTF-8
'\xe4\xb8\xad\xe5\x9b\xbd\xe4\xba\xba'
>>> unicode(gb, "gb2312")!! ! # GB2312 -> UNICODE
u'\u4e2d\u56fd\u4eba'
>>> u.encode()! ! ! ! # UNICODE -> UTF-8
'\xe4\xb8\xad\xe5\x9b\xbd\xe4\xba\xba'
>>> u.encode("gb2312")! ! ! # UNICODE -> GB2312
'\xd6\xd0\xb9\xfa\xc8\xcb'

标准库另有 codecs 模块⽤来处理更复杂的编码转换,⽐如⼤⼩端和 BOM

>>> from codecs import BOM_UTF32_LE
>>> s = "中国⼈"
>>> s
'\xe4\xb8\xad\xe5\x9b\xbd\xe4\xba\xba'
>>> s.encode("utf-32")
'\xff\xfe\x00\x00-N\x00\x00\xfdV\x00\x00\xbaN\x00\x00'
>>> BOM_UTF32_LE
'\xff\xfe\x00\x00'
>>> s.encode("utf-32").decode("utf-32")
u'\u4e2d\u56fd\u4eba'

格式化
Python 提供了两种字符串格式化⽅法,除了熟悉的 C 样式外,还有更强⼤的 format
%[(key)][flags][width][.precision]typecode

标记: 左对⻬, + 数字符号, # 进制前缀,或者⽤空格、 0 填充。

>>> "%(key)s=%(value)d" % dict(key = "a", value = 10) ! # key
'a=10'
>>> "[%-10s]" % "a"!! ! ! ! ! ! # 左对⻬
'[a ]'
>>> "%+d, %+d" % (-10, 10)!! ! ! ! ! # 数字符号
'-10, +10'
>>> "%010d" % 3! ! ! ! ! ! ! # 填充
'0000000003'
>>> "%.2f" % 0.1234!! ! ! ! ! ! # ⼩数位
'0.12'
>>> "%#x, %#X" % (100, 200)! ! ! ! ! # ⼗六进制、前缀、⼤⼩写。
'0x64, 0XC8'
>>> "%s, %r" % (m, m)! ! ! ! ! ! # s: str(); r: repr()
'test..., <__main__.M object at 0x103c4aa10>'

format ⽅法⽀持更多的数据类型,包括列表、字典、对象成员等。
{field!convertflag:formatspec}
格式化规范:
formatspec: [[fill]align][sign][#][0][width][.precision][typecode]
⽰例:

>>> "{key}={value}".format(key="a", value=10)!! # 使⽤命名参数。
'a=10'
>>> "{0},{1},{0}".format(1, 2)! ! ! ! # field 可多次使⽤。
'1,2,1'
>>> "{0:,}".format(1234567)! ! ! ! # 千分位符号
'1,234,567'
>>> "{0:,.2f}".format(12345.6789)! ! ! # 千分位,带⼩数位。
'12,345.68'
>>> "[{0:<10}], [{0:^10}], [{0:*>10}]".format("a")! # 左中右对⻬,可指定填充字符。
'[a ], [ a ], [*********a]'
>>> import sys
>>> "{0.platform}".format(sys)! ! ! ! # 成员
'darwin'
>>> "{0[a]}".format(dict(a=10, b=20))! ! ! # 字典
'10'
>>> "{0[5]}".format(range(10))! ! ! ! # 列表
'5'


另有 string.Template 模板可供使⽤。该模块还定义了各种常⻅的字符序列。

>>> from string import letters, digits, Template
>>> letters! ! ! ! ! ! ! ! ! # 字⺟表
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>> digits! ! ! ! ! ! ! ! ! # 数字表
'0123456789'
>>> Template("$name, $age").substitute(name = "User1", age = 20)! # 模板替换。
'User1, 20'
>>> Template("${name}, $age").safe_substitute(name = "User1") ! # 没找到值,不会抛出异常。
'User1, $age'


池化
Python 进程中,⽆数的对象拥有⼀堆类似 “__name__”“__doc__” 这样的名字,池化有助于减少对象数量和内存消耗, 提升性能。
intern() 函数可以把运⾏期动态⽣成的字符串池化。

>>> s = "".join(["a", "b", "c"])
>>> s is "abc"! ! ! # 显然动态⽣成的字符串 s 没有被池化。
False
>>> intern(s) is "abc"! ! # intern 会检查内部标记。
True
>>> intern(s) is intern(s)!! # 以后⽤ intern 从池中获取字符串对象,就可以复⽤了。
True

当池化的字符串不再有引⽤时,将被回收。

打赏