Size: 320
Comment:
|
Size: 41888
Comment:
|
Deletions are marked like this. | Additions are marked like this. |
Line 1: | Line 1: |
||'''status'''|| 草稿 || HuangYi || 0%|| [[TableOfContents]] = 内置数据类型 = == 数值类型 == === int === === float === == 字符串 == === 常见字符串操作 === === Unicode 对象 === === 字符串模板 === == 集合类型 == === Tuple === === List === === Dict === === Set === |
##language:zh #format rst .. contents:: :status: 草稿 ;HuangYi; 60%; =================== Python内置数据类型 =================== 数值类型 ========== 运算符 ------- 终于还是决定在最前面介绍一下 python 的这些个运算符先,基本上都是些很常见的符号, 只要有基本的计算机和数学常识,所以理解起来应当不会有什么困难。 由于 python 支持所谓的运算符重载,某些类型为了方便可能会改变一些运算符本来的含义, 比如加号,对于整数来说它就是数学中的加法,而对于字符串来说确实字符串之间的连接。 这些特殊的含义先放到一边,等后面遇到了我们再另行介绍。 这里先介绍的都是这些运算符最直观、最基本的含义。 +----------+-------------------------------------------------------------------------------------------+ | 数值运算 | 加 ``+`` 、减 ``-`` 、乘 ``*`` 、除 ``/`` 、取模 ``%`` 、指数运算 ``**`` 、取相反数 ``-`` | +----------+-------------------------------------------------------------------------------------------+ | 位运算 | 按位取反 ``~`` 、按位与 ``&`` 、按位或 ``|`` 、按位异或 ``^`` ,左移 ``<<`` 、右移 ``>>`` | +----------+-------------------------------------------------------------------------------------------+ | 比较操作 | 小于 ``<`` 、大于 ``>`` 、等于 ``==`` 、大于等于 ``>=`` 、 | | | 小于等于 ``<=`` 、不等于 ``<>`` 、不等于 ``!=`` | +----------+-------------------------------------------------------------------------------------------+ | 布尔操作 | ``not`` 、 ``and`` 、 ``or`` | +----------+-------------------------------------------------------------------------------------------+ .. topic:: 数值运算 加减乘除,小学就学过了,取模(就是做除法取余数)、指数运算、取相反数难点,但还在初中生那个层次晃悠 ;-) :: >>> 10/3 3 >>> 10%3 1 >>> 2**3 8 >>> a=1 >>> -a -1 .. topic:: 位运算 你总该知道计算机内部都是以二进制对数据进行存储的吧!所谓位运算就是用来操作这些二进制位的。 更详细的解释就恕我不能完整介绍,了解的同学自然一看就明白了,不了解的同学估计一时半会也还用不上, 所以你也不用担心 ;-) :: >>> 2>>1 # 0010 -> 0001 1 >>> 2<<1 # 0010 -> 0100 4 >>> 5 | 10 # 0101 | 1010 -> 1111 15 >>> 5 & 10 # 0101 & 1010 -> 0000 0 .. topic:: 比较操作 对两个对象进行比较,返回一个布尔值。 要注意的是 ``=`` 号已经被用来进行名字绑定(也就是赋值)了,所以我们这里使用 ``==`` 符号来做相等的判断。 :: >>> 1 < 2 True >>> 'b'>'a' True >>> 1!=2 True (这几个例子好弱智啊!= =") .. topic:: 布尔运算 这个先不急,马上就要讲到了 ;-) 布尔 ------- ``True`` 或者 ``False`` ,这就是布尔类型,干脆俐落。 .. sidebar:: 其他对象到布尔对象的转换规则 ``None`` 、任何数值类型中的 ``0`` 、空字符串 ``''`` 、空元组 ``()`` 、空列表 ``[]`` 、空字典 ``{}`` 都被当作 ``False`` ,还有自定义的类型如果它实现了 ``__nonzero__()`` 或 ``__len__()`` 方法且方法返回 ``0`` 或 ``False`` 的,则其实例也被当作 ``False`` ,其他对象均为 ``True`` 。 在 python 中,任何对象都可以隐式地转换为布尔对象,所谓隐式的意思就是自动进行转换, 所以那些对布尔对象的操作,你都可以传个任意的对象过去,python 会按照一定规则自动地将 这个对象转换成合适的 ``True`` 或者 ``False`` 。 这一点常常让人觉得布尔对象神龙见首不见尾,似乎无处不在却又很少见到其真正的本尊。 通过构造 ``bool`` 对象,你可以对其他对象到布尔对象的转换规则进行验证, 看看究竟哪些对象是 ``True`` ,哪些对象是 ``False`` : :: >>> bool(0) False >>> bool(1) True >>> bool('hello') True >>> class FooBar(object): # 这里涉及到 class ,参考第 n 章 第 n 节 类与对象,或者直接跳过。 ... def __nonzero__(self): ... return False ... >>> foobar = FooBar() # 创建 FooBar 类的实例。 >>> bool(foobar) False >>> 0 or 0L or 0.0 or 0j or '' or () or [] or {} or False False 如果你拥有基本的逻辑知识的话,看到上面最后一句代码,应该就能确定那些参与 ``or`` 操作的对象都是 ``False`` 了, 因为 or 的规则是只要有一个对象为真,整个表达式就为真了。 任何涉及布尔的地方总免不了 ``not`` 、 ``and`` 、 ``or`` 这三个运算。 python 的布尔类型自然也不例外。 不过在这里他们虽然叫还是叫做布尔运算,但像刚才说的,python 中任何对象都可以隐式地转换为布尔对象, 这使得实际上任何对象之间都可以进行这种所谓的“布尔”运算,于是 python 干脆对这三种运算的语义又做了细微的调整, 在保留布尔操作本身语义的同时,大大增强了他们的灵活性与实际用途。 python 中这三种运算的规则定义如下: .. sidebar:: and or 组合 通过 and 和 or 的组合你可以获得许多奇妙的效果, 比如 ``condition and a or b`` 实际上就等价于 c 语言中的 ``condition? a:b`` , 意思就是说如果条件 ``condition`` 满足那么就 ``a`` ,否则就 ``b`` 。 有意思吧 ;-) 至于为什么就当做作业留给聪明的你慢慢思考吧(见练习题1)。 .. topic:: not a 如果 ``a`` 为 ``True`` 则返回 ``False`` ,为 ``False`` 则返回 ``True`` .. topic:: a and b 如果 a 为 ``True`` 则返回 b,否则返回 a .. topic:: a or b 如果 a 为 ``False`` 则返回 b,否则返回 a 示例::: >>> not 1 False >>> products = None >>> products = products or [default_product1, ...] # 伪码,如果 products 为 ``None`` 或者为空的话,给它提供一个默认值。 # 这就是 python 布尔操作符超越传统布尔操作符的应用之一了。 TODO:提供多一些有趣有用的 and or 实例。 另外,进行数值运算的时候布尔对象也能够隐式地转换成整数, ``True`` 是 ``1`` , ``False`` 是 ``0`` : :: >>> True+1 2 >>> False+1 1 >>> int(True) 1 >>> int(False) 0 整数 ------ 如果你还有受过其他语言的“污染”的话,python 的整数就是数学中的整数而已,没什么好说的。 对于受过某些其他语言的熏陶的朋友,也许需要注意的一点就是: python 的整数没有什么 short、long 的区分,它甚至连大小的限制都没有! :: >>> 1 1 >>> 9999999999999999999999 9999999999999999999999L >>> 99999999999999999 * 9999999999999999999 999999999999999989900000000000000001L .. topic:: 长整数 实际上在内部 python 对整数的处理还是会分为普通整数和长整数, 普通整数就是大家在其他语言中常见到整数,长度为机器位长, 通常都是 32 位(不过再过几年可能就要改口说是通常 64 位了)。 而超过这个范围的整数就自动当作长整数处理, 而长整数表示范围可就几乎完全——只要内存吃得消——没有限制了。 如果你还想刨根问底说怎么做到没有限制,那我只好叫你去看 CPython 源码了 ;-) .. topic:: 小整数池 为了提高性能,python 在启动时会对一定范围以内的小整数创建缓存, 这样在后面创建这些小整数对象的时候,就不用重复的去申请内存, 而是直接使用缓存中的小整数对象。 这一点通过 ``id()`` 函数就可以看得出来: :: >>> a = 10 >>> b = 10 >>> id(a) 11163620 >>> id(b) 11163620 >>> a = 999999999999999999 >>> id(a) 11314456 >>> b = 999999999999999999 >>> id(b) 11314960 # 显然 b 的地址与 a 不同。 浮点数 -------- python 的浮点数其实就是数学中的小数, c 语言中的 ``double`` 。 在数值运算中,整数与浮点数运算的结果是浮点数,这就是所谓的“提升规则”, 也就是“小”类型会被提升为“大”类型参与计算,许多语言都是这样的。 从这个角度去看,也就不难理解为什么布尔对象参与数值运算时会被隐式地转换成整数了 ;-) :: >>> 3/2 1 >>> 3/2.0 1.5 复数 ------- 复数~~,貌似终于升级到高中的层次了 = =" 如果你不知复数为何物,大可直接跳过,损失不大。 既然你懂复数的话,你应该会发现 python 的复数其实非常直观,和数学中学到的没有什么不同。 :: >>> 1+1j (1+1j) >>> 1+1j +3 (4+1j) >>> 1+1j +3j (1+4j) >>> (1+1j)*5 (5+5j) >>> (1+1j)*(2+2j) 4j >>> (1+1j)/(2+2j) (0.5+0j) >>> (1+1.0j)/(2+2j) (0.5+0j) >>> (1+1j)**(2+2j) # 指数运行,见 数值运算_ (-0.26565399884924118+0.31981811385613623j) 字符串 ======== python 字符串既可以用单引号表示也可以用双引号表示, 甚至还可以用三引号——哦不对,是三个引号——来表示。 这样如果字符串里本身包含双引号,你就可以用单引号来表示: :: >>> 'My name is "python"' 'My name is "python"' 而如果字符串里本身包含单引号呢,你又可以用双引号用表示: :: >>> "My name is 'python'" "My name is 'python'" 真是太方便了! 三个引号的字符串就更方便了,中间甚至还可以换行! :: >>> '''My ... name ... is ... "python" ... ! ... ''' 'My\nname\nis\n"python"\n!\n' >>> """My ... name ... is ... 'python' ... ! ... """ "My\nname\nis\n'python'\n!\n" 字符串转义 ------------ 那字符串里要是既有单引号又有双引号怎么办?答案就是字符串转义: :: >>> 'My \'name\' is "python"!' 'My \'name\' is "python"!' 所谓字符串转义就是 ... TODO: 字符串转义的含义、作用 等。 下面这个表列出所有转义符及其简要说明,要是觉得这点简要的解释不过瘾的话, 直接跑到 python shell 下面去试验一下,马上就清楚了: :: >>> print '\a' >>> print 'aa\bbb' abb >>> print 'a\tb\nab' a b ab >>> TODO: 更多有趣例子 +-------------+------------------------------------------------+ | 转义符 | 含义 | +=============+================================================+ | ``\换行`` | 忽略后面的换行符 | +-------------+------------------------------------------------+ | ``\\`` | 字符 ``\`` | +-------------+------------------------------------------------+ | ``\'`` | 单引号 ``'`` | +-------------+------------------------------------------------+ | ``\"`` | 双引号 ``"`` | +-------------+------------------------------------------------+ | ``\a`` | 发出声音:滴 | +-------------+------------------------------------------------+ | ``\b`` | 退格符 | +-------------+------------------------------------------------+ | ``\f`` | | +-------------+------------------------------------------------+ | ``\n`` | 换行符 | +-------------+------------------------------------------------+ | ``\r`` | 回车符 | +-------------+------------------------------------------------+ | ``\t`` | 水平 TAB 符 | +-------------+------------------------------------------------+ | ``\v`` | 竖直 TAB 符 | +-------------+------------------------------------------------+ | ``\ooo`` | 输出 8 进制数字(最多3个) ``oo`` 所代表的字符 | +-------------+------------------------------------------------+ | ``\xhh`` | 输出 16 进制数字(最多2个) ``hh`` 所代表的字符| +-------------+------------------------------------------------+ | ``\N{name}``| | +-------------+------------------------------------------------+ | ``\uxxx`` | | +-------------+------------------------------------------------+ | ``\Uxxx`` | | +-------------+------------------------------------------------+ 序列操作 -------------- 序列类型(Sequence Types)其实是一个抽象接口, 内置类型中实现了这一接口的有字符串、Unicode 对象、元组、列表、 buffer、xrange。既然先讲到字符串,那就在这里就把这个概念说明一下先, 在后面向大家介绍其他序列类型时就直接参考这里了。 所有的序列类型都支持一些共同的操作,这里拿字符串来举例子,其他序列类型大家到时候一看就明白了。 .. topic:: ``in`` :: >>> 'python' in 'I love python!' True >>> 'c' not in 'I love python!' False .. topic:: 连接 ``+`` 将多个序列对象连接起来。 :: >>> 'I '+'love '+'python!' 'I love python!' .. topic:: ``*`` 拷贝 n 份(准确得说是浅拷贝,见第n章第n节) :: >>> print 'I love python!\n'*3 I love python! I love python! I love python! .. topic:: 索引 第一个是 ``0`` ,正数表示从左向右数第几个,负数是从右向左数,不过第一个的左边没有,就绕到最右边去了。 :: >>> 'python'[0] 'p' >>> 'python'[3] 'h' >>> 'python'[-1] 'n' >>> 'python'[-3] 'h' .. sidebar:: 惯用法 ``sequence[:]`` ,也就是使用 ``start`` 、 ``end`` 和 ``step`` 的默认值对序列对象切片, 实际上就是对序列对象的一个浅拷贝,而这显然比实际的拷贝操作方便多了。 .. topic:: 切片 取序列中一个片段。 原型是 ``sequence[start:end:step]`` ``start`` 表示起始位置, ``end`` 表示结束位置, ``step`` 表示每经过多少取一个值。 三个值均可忽略。 ``step`` 默认值为 ``1`` ,表示没有间隔; ``start`` 默认值为 ``0`` ,也就是序列最开始的位置, ``end`` 默认为序列的长度,也就是最末尾的后面一位。 :: >>> 'python'[0:6:2] # 完整版本 'pto' >>> 'python'[0:6] # 忽略 step 'python' >>> 'python'[:3] # 忽略 start 和 step 'pyt' >>> 'python'[3:] # 忽略 end 和 step 'hon' >>> 'python'[:] # 全部忽略 'python' 另外对于序列类型中的可变类型(比如列表),还可以直接利用索引和切片对元素进行修改和删除,可惜字符串不是, 如果想看,直接跳到 列表_ 那一节去就行了。 常用字符串操作 ---------------- 上面这一节讲的其实已经是属于常用字符串操作了,不过那些是所有序列对象都共同拥有的东西, 而这一节要介绍的是专门为字符串提供的操作。 :: >>> dir(str) ['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__ ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__g t__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__ ', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', ' __rmul__', '__setattr__', '__str__', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'index', 'isalnum', 'isalpha', 'isdi git', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lst rip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit' , 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', ' translate', 'upper', 'zfill'] 其实里面许多的方法,都是看到名字就能联想到其功能的。 稍微复杂点的方法,只要在 python shell 里面做点实验也都能猜个八九不离十。 如果还剩下些什么疑问呢,就到这里来找答案吧。 .. topic:: 连接与分割 上面介绍序列类型时,我们已经使用了 ``+`` 号来做字符串的连接操作, 在某些情况下这当然是不错的,然而在多数情况下我们其实都不推荐这种做法, 因为大量的这种连接操作会大大影响效率。比如说这个例子::: >>> 'I '+'love '+'python!' 'I love python!' .. sidebar:: 字符串缓存 python 虚拟机内部会对所有 python 字符串进行缓存,所以任何两个内容相同的字符串, 实际上都指向同一个字符串。 典型的空间换时间的做法,节省了许多为分配相同字符串浪费的cpu时间, 同时也浪费了一些内存空间。 所以做大量字符串连接的时候,千万要慎重! 因为那些中间产生的无用字符串对象都会被虚拟机缓存,很可能会始终占据着你的内存。 :: >>> a = 'python' >>> b = 'python' >>> id(a) 11361984 >>> id(b) 11361984 分解开来看就是 ``('I '+'love ') + 'python!'`` ,第一次连接操作就会产生一个中间对象 ``'I love '`` ,而这个对象从结果来看完全是没有用的。 大量的连接操作,就会产生大量无用的中间对象。 浪费了分配内存所花费的时间也浪费了内存。 所以,两个字符串的连接用 ``+`` 是很方便的,不过两个以上的字符串连接我们还是更推荐下面这个方法: :: >>> ' '.join(['I', 'love', 'python!']) 'I love python!' 你看,也不是很麻烦嘛 ;-) 你还可以用其他字符串来进行连接: :: >>> '--'.join(['I', 'love', 'python!']) 'I--love--python!' 也许你已经注意到了里面的中括号,中括号是用来构造列表的(参考 列表_ )。 ``split`` 是 ``join`` 的逆操作,原型是 ``split( [sep [,maxsplit]])`` , 它可以把一个字符串分割成多个字符串的列表: :: >>> 'I love python!'.split(' ') ['I', 'love', 'python!'] >>> 'I--love--python!'.split('--') ['I', 'love', 'python!'] 如果你不传递或者传递 ``None`` 给 ``sep`` 参数,那么 ``split`` 会启用 一个比较特殊的字符串分割策略,多说无益,先看代码: :: >>> 'I love python!'.split(' ') # 使用空格分割 ['I', 'love', '', '', '', '', 'python!'] >>> 'I love python!'.split() # 默认分割策略 ['I', 'love', 'python!'] 看出区别了吧,它会把连续的空白当作分割符,其作用就不用我明说了吧 ;-) ``split`` 方法还接受另一个可选的参数: ``maxsplit`` ,意思就是最大分割次数, 这样分割出来的列表最长也就是 ``maxsplit + 1`` 。 :: >>> 'I love python!'.split(' ', 1) ['I', 'love python!'] ``split`` 还有一个姐妹版: ``rsplit`` ,作用基本一样,就是限制分割次数时, 分割的顺序变成从右到左。 :: >>> 'I love python!'.rsplit(' ', 1) ['i love', 'python'] 另外,后面还会介绍另一种强大的构造字符串的方法: 字符串模板_ 。 .. topic:: 大小写转换 ``upper`` 将字符串转换为大写, ``lower`` 转换成小写。 TODO: ``swapcase`` 比较奇妙的是 ``title`` ,它将每个单词的首字母转成大写,其他转成小写。 :: >>> 'Python'.upper() 'PYTHON' >>> 'Python'.lower() 'python' >>> 'the python book'.title() 'The Python Book' .. topic:: 字符串测试 :: >>> 'python'.islower() # 是否都是小写 True >>> 'PYTHON'.isupper() # 是否都是大写 True >>> 'The Python Book'.istitle() # 是否 ... (参考上面对 title 方法的解释) True >>> 'python'.isalpha() # 是否都是字母, isalnum 方法作用相同 True >>> '42'.isdigit() # 是否都是数字 True >>> ' '.isspace() # 是否都是空格 True >>> ''.islower() or ''.isupper() or ''.istitle() or ''.isalpha() or ''.isdigit() or ''.isspace() False 最后一句证明了这些测试对空字符串都不成立。 .. topic:: 查找 ``find`` 方法返回子串在字符串中出现的位置,原型是 ``find( sub[, start[, end]])`` , 可选的 ``start`` 、 ``end`` 参数用来限制查找范围,如果找不到则返回 ``-1`` 。 ``index`` 方法和 ``find`` 方法一样,唯一区别就是找不到的时候会抛出 ``ValueError`` 异常(见某章某节 异常)而不是返回 ``-1`` 。 ``find`` 和 ``index`` 方法也都有个“姐妹版”,也就是 ``rfind`` 和 ``rindex`` ,功能类似, 只不过查找的方向变成从右向左。 :: >>> 'I love python!'.find('love') 2 >>> 'I love python!'.find('c') -1 >>> 'I love python!'.index('c') Traceback (most recent call last): File "<stdin>", line 1, in ? ValueError: substring not found >>> 'python is pythonic!'.find('python') 0 >>> 'python is pythonic!'.rfind('python') 10 .. topic:: ``startswith`` , ``endswith`` 判断字符串是否以某个子字符串开始或结束。 原型: ``startswith( prefix[, start[, end]])`` , ``endswith( suffix[, start[, end]])`` ``prefix`` / ``suffix`` 就是这个子字符串,可选参数 ``start`` 、 ``end`` 指定源字符串中哪一部分参与判断, 默认值分别为 ``0`` 和字符串的长度,也就是整个字符串参与判断的意思。 :: >>> 'python'.startswith('pyt') True >>> 'python'.endswith('hon') True >>> 'python'.startswith('th', 2) True >>> 'python'.endswith('th', 0, -2) True .. topic:: replace TODO .. topic:: ljust rjust center TODO .. topic:: translate TODO .. topic:: zfill TODO .. topic:: TODO TODO 字符串与字节流 ---------------- Unicode 字符串 ---------------- Unicode 是一个很重要的话题,也是一个现代程序员所必备的知识之一。那还是在 n 年以前 ... 话说老美刚整出计算机的那会,老美还在说英文(当然,现在也还在说英文),大家知道,英文 abc 总共也没几个字符, 就算加上一些稀奇古怪的!@#$%^&这样的字符, 也就那么些了,最后把各种奇怪字符都加在一起算了一下,大概 127 个, 而一个字节能表达 256 种字符呢,用一个字节表示一个字符都还绰绰有余。 当时谁都觉得用一个字节表示一个字符真好。这种编码方式叫做 ASCII 。 后来计算机便传入了中国和许多其他国家,很快就遇到了一个大问题,汉语言文字博大精深,又岂是小小一个字节能表达得了的? 于是有人发明了用两个字节表达汉字的编码方法,叫做 gbk 。这种现象同样也在其他非英语国家上演着。 大家都用着各自不同的互相冲突的编码方式,这给交流带来极大不便,此时的世界亟需一个统一的标准。 于是 Unicode 便应运而生了!Unicode 定义了一个大表,里面包含了全世界所有已知的字符,然后给这些字符编号,每个字符对应一个数字, 也就是所谓的代码点(code-point)。 需要注意的是 Unicode 本身是不在乎字符在计算机上是如何存储的,一个字节还是两个字节还是三个字节与 Unicode 无关, 你可以采取各种存储策略,甚至你可以直接把字符在 Unicode 大表中的编号——也就是代码点——来当作字符在计算机中的表示, 而规定如何存储 Unicode 字符的规范就叫做编码! 现在世界上的编码成百上千,以前的 gbk 在现在 Unicode 新环境下也仍然存在,不过它只能处理 Unicode 字符的一部分了。 如果希望自己的程序能够跨越国界的话,最好还是使用一种能够处理所有 Unicode 字符的全能编码。 最流行的全能编码应该是 utf-8 了,它使用一种变长的存储方式,对传统的 ASCII 字符还是使用一个字节来存储, 这样那些英文国家的程序可以完全不受影响。当然这样的话它就要使用更多的字节来存储其他非英文字符了。 在 python 中定义一个 Unicode 字符串非常简单,在普通字符串前面加一个 ``u`` 即可, 还可以使用转义符 ``\u`` 直接使用代码点来定义 Unicode 字符串: :: >>> u'派松' u'\u6d3e\u677e' >>> print u'\u4e2d\u56fd' 中国 >>> print u'派松\u4e2d\u56fd' 派松中国 .. sidebar:: 系统默认编码 :: >>> import sys >>> sys.getdefaultencoding() 'ascii' 使用 ``encode`` (编码) ``decode`` (解码) 方法,就可以在普通字符串和 Unicode 字符串之间进行转换, 两个方法的原型分别为: ``encode( [encoding[,errors]])`` 和 ``decode( [encoding[,errors]])`` 这两个方法都接受两个可选参数,第一个是编码名称,默认是系统默认编码, 第二个参数是个字符串,用来指定错误处理方式,可以使用的值有: +-------------------------+----------------------------------------------------------------------------+ | 取值 | 错误处理方式。 | +=========================+============================================================================+ | ``'strict'`` | 抛出异常 ``UnicodeError`` ,这是默认行为。 | +-------------------------+----------------------------------------------------------------------------+ | ``'ignore'`` | 忽略错误字符,继续处理其他文本。 | +-------------------------+----------------------------------------------------------------------------+ | ``'replace'`` | 用一个合适的字符替代出错字符,解码时使用标准 Unicode 替代字符 ``'\uFFFD'`` | | | ,编码时使用 ``'?'`` 。 | +-------------------------+----------------------------------------------------------------------------+ | ``'xmlcharrefreplace'`` | 使用合适的 XML character reference ?? 替代出错字符,仅在编码时有用 | +-------------------------+----------------------------------------------------------------------------+ | ``'backslashreplace'`` | 使用转义字符串替代出错字符,仅在编码时有用。 | +-------------------------+----------------------------------------------------------------------------+ :: >>> print u'派\uffff松'.encode('gbk', 'strict') Traceback (most recent call last): File "<stdin>", line 1, in ? UnicodeEncodeError: 'gbk' codec can't encode character u'\uffff' in position 1: illegal multibyte sequence >>> print u'派\uffff松'.encode('gbk', 'ignore') 派松 >>> print u'派\uffff松'.encode('gbk', 'replace') 派?松 >>> '派\xff\xff松'.decode('gbk', 'replace') u'\u6d3e\ufffd\u677e' >>> print u'派\uffff松'.encode('gbk', 'xmlcharrefreplace') 派松 >>> print u'派\uffff松'.encode('gbk', 'backslashreplace') 派\uffff松 直接在普通字符串中使用中文时,使用的编码方式取决于源代码文件本身所使用的编码, 在中文平台上一般默认都是 gbk ,中文平台的 console 中一般也都是 gbk。 下面我们来体验几种不同编码之间的差异: :: >>> u'派松' # 无编码 u'\u6d3e\u677e' >>> '派松' # 默认的 gbk 编码 '\xc5\xc9\xcb\xc9' >>> u'派松'.encode('utf-8') # 使用 encode 从 Unicode 字符串转换成 普通字符串 '\xe6\xb4\xbe\xe6\x9d\xbe' >>> '派松'.decode('gbk') # 使用 decode 从普通字符串转换成 Unicode 字符串 u'\u6d3e\u677e' >>> len(u'派松') 2 >>> len('派松') 4 >>> len(u'派松'.encode('utf-8')) 6 我们说过 Unicode 本身不定义字符在计算机中的表现方式,所以当我们需要将 Unicode 字符串保存到文件, 或是在网络中传输,或是从 console 中 ``print`` 出来时,都需要以某种方式编码 Unicode 字符串先。 不过很多操作面对 Unicode 字符串时都能够智能地选择某种默认编码进行处理, 比如在一般的中文平台上, ``print`` 默认便使用 gbk 来进行输出: :: >>> print u'\uFFFF' Traceback (most recent call last): File "<stdin>", line 1, in ? UnicodeEncodeError: 'gbk' codec can't encode character u'\uffff' in position 0: illegal multibyte sequence 还有要注意的一点就是在源代码文件中定义包含非 ASCII 字符的 Unicode 字符串时, 最好是文件的顶部(第一行或第二行)用注释的方式定义一下此文件本身所使用的编码, 因为 python 虚拟机在解析的时候需要从编码后的字符串解码得到 Unicode 字符串, 所以如果不知道源文件本身使用的编码,虚拟机就使用默认的 ASCII 进行处理, 这样就很可能出现乱码甚至是抛出异常。 .. sidebar:: 指定源文件编码 究竟如何指定源文件使用的编码才是符合 python 规范的呢? 其实 python 在这一点上是充分考虑了各大主流编辑器的具体情况的。 python 规定了这样一个正则表达式: ``coding[:=]\s*([-\w.]+)`` , 所有满足这个正则表达式的写法都可以被 python 虚拟机识别。 懂正则表达式的同学应该不难看出左边的两种写法显然都是满足规范的。 而对于不了解正则表达式的同学就不用考虑这么多了, 从左边的两种主流方式选一种就行了。 对于大部分编辑器都应该这么写: :: #!/usr/bin/python # -*- coding: utf-8 -*- python = u'派松' 使用 vim 的同学可以这样写: :: #!/usr/bin/python # vim: set fileencoding=utf-8 : python = u'派松' 上面两种写法都可以同时被 python 虚拟机和相应的编辑器识别 (当然其中的 ``utf-8`` 可以换成任何合法的编码名称)。 字符串模板 -------------- 格式化 ````````````````` 上面已经介绍过几种构造字符串的方法:不适合大量使用的 ``+`` 操作和字符串的 ``join`` 方法。 他们可以把几个小字符串拼接成一个大字符串。 但是它们并不能很好得处理其他类型的对象的字符串表示,换句话说就是“格式化”。 先给大家看几个例子,应该很快就会明白所谓“格式化”到底是什么意思了。 :: TODO: 字符串格式化入门例子 +--------+-------------------+ | 格式码 | 含义 | +========+===================+ | d | | +--------+-------------------+ | i | | +--------+-------------------+ | o | | +--------+-------------------+ | u | | +--------+-------------------+ | x | | +--------+-------------------+ | q | | +--------+-------------------+ 模板 `````````` TODO 容器类型 ============ 所谓容器,就是装东西的东西了。下面这些类型的作用都是用来“装”——或者说管理——其他对象的。 元组 ---------- 通过逗号分隔的一些对象,这就是元组了: :: >>> 1,2,3 (1, 2, 3) 不过通常我们都用一对小括号扩着,主要是为了避免歧义: :: >>> (1,2,3) (1, 2, 3) 千万要注意的是,如果元组中只有一个对象,一定记得要在对象后面加一个逗号,因为否则的话, python 会把它当作一个对象,就算加了括号也没用! :: >>> 1 1 >>> 1, (1,) >>> (1) 1 >>> (1,) (1,) 首先元组属于序列类型,自然就拥有所有的序列操作: :: >>> (1,2,3)+(5,6,7) (1, 2, 3, 5, 6, 7) >>> (1,2)*3 (1, 2, 1, 2, 1, 2) >>> 2 in (1,2) True >>> (1,2,3,4,5,6,7)[1:-1:2] (2, 4, 6) 元组还是不可变对象,所以对其中的元素不能修改: :: >>> (1,2,3)[0] = 0 Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: object does not support item assignment 另外,元组和下面马上要介绍的列表都还支持一个非常有趣的特性,就是所谓的“并行绑定”: (上面代码中等号左边的元组都可以替换成列表,效果一样) :: >>> (a, b) = (1, (2, 3)) >>> print a, b 1 (2, 3) >>> (a, (b, c)) = (1, (2, 3)) >>> print a, b, c 1 2 3 >>> (a, b, c) = 'pyt' >>> print a, b, c p y t 相信聪明的你已经看出来了,当绑定一个元组/列表的时候,它会自动去绑定元组/列表中每一个元素。 这个过程甚至还是可以递归的! 而等号右边可以是任何支持索引的数据类型,比如大家了解的序列类型就可以。 列表 ---------- 使用中括号括起来的一组对象,对象之间以逗号分隔,就是列表了。 列表的中括号可不像元组的小括号那样可以省略,否则就会有歧义。 首先列表属于序列类型: :: >>> [1,2,3]+[5,6,7] [1, 2, 3, 5, 6, 7] >>> [1,2]*3 [1, 2, 1, 2, 1, 2] >>> 2 in [1,2] True >>> [1,2,3,4,5,6,7][1:-1:2] [2, 4, 6] 列表还是可变数据类型,我们可以通过索引和切片随意修改和删除其中的元素: :: >>> a = [1,2] >>> a[0] = 0 >>> a [0, 2] >>> a = [1,2,3,4,5,6,7] >>> a[1:-1:2] [2, 4, 6] >>> a[1:-1:2] = [0]*3 >>> a [1, 0, 3, 0, 5, 0, 7] >>> del a[1:-1:2] >>> a [1, 3, 5, 7] 此外,列表还提供了许多强大的操作。 .. topic:: 简单操作 :: >>> a = [1, 2, 3] >>> a.append(4) # 在列表末尾追加一个对象 >>> a [1, 2, 3, 4] >>> a.extend([5,6,7]) # 用另一个列表来扩展这个列表 >>> a [1, 2, 3, 4, 5, 6, 7] >>> a.insert(0, 2) # 在索引为 0 的位置上插入对象 2 >>> a [2, 1, 2, 3, 4, 5, 6, 7] >>> a.count(2) # 计算对象出现的次数 2 >>> a.index(2) # 返回对象第一次出现的位置 0 >>> a.index(2, 3, -1) # 后面两个参数分别为 ``start`` 和 ``end`` ,表示在列表中查找的范围, # 找不到就抛出异常 Traceback (most recent call last): File "<stdin>", line 1, in ? ValueError: list.index(x): x not in list >>> a.pop() # 返回列表中最后一个对象,并把该对象从列表中删除 7 >>> a [1, 1, 2, 3, 4, 5, 6] >>> a.pop(-2) # 返回列表中位置为 -2 (也就是倒数第二个) 的对象,并把该对象从列表中删除 5 >>> a [1, 1, 2, 3, 4, 6] >>> a.remove(1) # 删除列表中位置为 1 的对象 >>> a [1, 2, 3, 4, 6] >>> a.reverse() # 反转列表 >>> a [6, 4, 3, 2, 1] .. topic:: 排序 TODO 字典 ---------- 其他语言中它也常被叫做哈希表或是关联数组,在这里我们叫它字典。 语法是两个大括号,中间括着一个或多个以逗号分隔的 ``key:value`` 对,``key`` 、 ``value`` 都是普通 python 对象, 看下代码你就全明白了::: >>> numbers = {'one':1, 'two':2, 'three':3} >>> numbers['one'] 1 组合其他数据结构::: >>> article = { ... 'title':'About Python', ... 'author':'someone', ... 'content':'...', ... 'comments':[ ... {'title':'the article is great!', ... 'author':'annoynouse', ... 'content':'...'}, ... {'title':'the article is good!', ... 'author':'someone', ... 'content':'...'}, ... ] ... } >>> article['title'] 'About Python' >>> article['comments'] [{'content': '...', 'author': 'annoynouse', 'title': 'the article is great!'}, { 'content': '...', 'author': 'someone', 'title': 'the article is good!'}] >>> article['comments'][0]['author'] 'annoynouse' 说白了,它其实就是一个 key、value 之间的映射,而它的好处就是,通过 key 来对 value 的查找非常快。 比如说你要管理许多员工信息,你常常需要通过员工的名字来找到相应的员工信息对象,那么你就可以建立这样一个映射, 也就是字典,来加速这一查找的过程::: >>> usermap = {'wukong':user1, 'bajie':user2, 'tangceng':user3} >>> usermap['wukong'] <__main__.User object at 0x00C269D0> 而如果不用字典做,你就需要对一个一个 ``User`` 对象的名字进行比较,看它是否是 ``'wukong'`` ,是则罢了, 否则的话还要继续比较下去。显然不如字典来得高效快捷。 要做字典的 key 也是要满足一定条件的,这个条件就是该对象必须是不可修改的(具体原因留作作业给大家思考), 除了元组以为的不可变对象都能够满足要求,对于元组来说,虽然它是不可变对象,但由于它其中可以包含可变对象, 所以并不是所有元组都是不可修改,只有那些只包含不可变对象的元组才能满足做 key 的要求。 了解内幕的朋友会知道,字典内部其实会对 key 对象进行一个哈希操作,得到一个整数,以此作为 value 对象实际存储的位置, 这也就是字典能够快速查找的根本原因。python 有一个内置函数 ``hash``,它会调用对象的一个叫做 ``__hash__`` 的方法, 来返回对象的哈希值。这个 ``__hash__`` 方法便是真正执行这个“哈希”操作的地方,当然并非所有对象都能正常完成这个哈希操作, 原因我们刚刚说过了::: >>> hash(1) 1 >>> hash('python') 1142331976 >>> 'python'.__hash__() 1142331976 >>> hash((1,2,3)) -378539185 >>> hash([1,2,3]) Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: list objects are unhashable >>> hash((1,2,[1,2,3])) Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: list objects are unhashable 当然你自己的 ``class`` 如果需要也是完全可以重写掉默认的 ``__hash__`` 函数,返回合适的整数的。 集合 ---------- 集合的概念应该很多人都不会陌生,集合中元素不能重复,集合之间可以进行并、交、补等操作。 在 python 中,对应的就是集合( ``set`` )数据类型。 不过集合类型可没有上面那些类型那样的语法支持,没办法,小括号中括号大括号都被用光了 ;-( :: >>> set1 = set() >>> set1.add(1) >>> set1.add(2) >>> set1 set([1, 2]) >>> 1 in set1 True >>> 4 not in set1 True >>> set1 = set([1,2]) >>> set2 = set([1,2,3]) >>> set1 <= set2 # set1 是否是 set2 的子集,set1.issubset(set2) True >>> set2 >= set1 # set2 是否是 set1 的超集,set2.issuperset(set1) True >>> set1 = set([1,2,3]) >>> set2 = set([2,3,4]) >>> set1 | set2 # 并 set1.union(set2) set([1, 2, 3, 4]) >>> set1 & set2 # 交 set1.intersection(set2) set([2, 3]) >>> set1 - set2 # 差 set1.difference(set2) set([1]) >>> set1 ^ set2 # 异或 set1.symmetric_difference(set2) set([1, 4]) 集合还有一个特性就是它能保证其中的元素不重复::: >>> set([1,2,2,3,3,4,4]) set([1, 2, 3, 4]) 数组(array) -------------- TODO: 是否该加上 array 类型? 小结 ====== 练习 ====== * 解释 ``condition and a or b`` 与 ``condition?a:b`` 的等价关系。 * 还没学过 c 语言?OK,可以告诉你, ``condition?a:b`` 的意思就是: :: if condition: return a else: return b * 使用 ``range`` 快速构造等差数列。 * 提示:在 python shell 中使用 ``help(range)`` 查看 ``range`` 函数的文档。 * 利用元组并行赋值的特性如何快速交换两个变量的值? * 使用列表模拟队列和堆栈操作。 * 快速出去列表中重复元素。 * 提示:集合中元素不重复。 .. macro:: [[PageComment2(nosmiley=1, notify=1)]] |
.. contents:: :status: 草稿 ;HuangYi; 60%; =================== Python内置数据类型 =================== 数值类型 ========== 运算符 ------- 终于还是决定在最前面介绍一下 python 的这些个运算符先,基本上都是些很常见的符号, 只要有基本的计算机和数学常识,所以理解起来应当不会有什么困难。 由于 python 支持所谓的运算符重载,某些类型为了方便可能会改变一些运算符本来的含义, 比如加号,对于整数来说它就是数学中的加法,而对于字符串来说确实字符串之间的连接。 这些特殊的含义先放到一边,等后面遇到了我们再另行介绍。 这里先介绍的都是这些运算符最直观、最基本的含义。 +----------+-------------------------------------------------------------------------------------------+ | 数值运算 | 加 ``+`` 、减 ``-`` 、乘 ``*`` 、除 ``/`` 、取模 ``%`` 、指数运算 ``**`` 、取相反数 ``-`` | +----------+-------------------------------------------------------------------------------------------+ | 位运算 | 按位取反 ``~`` 、按位与 ``&`` 、按位或 ``|`` 、按位异或 ``^`` ,左移 ``<<`` 、右移 ``>>`` | +----------+-------------------------------------------------------------------------------------------+ | 比较操作 | 小于 ``<`` 、大于 ``>`` 、等于 ``==`` 、大于等于 ``>=`` 、 | | | 小于等于 ``<=`` 、不等于 ``<>`` 、不等于 ``!=`` | +----------+-------------------------------------------------------------------------------------------+ | 布尔操作 | ``not`` 、 ``and`` 、 ``or`` | +----------+-------------------------------------------------------------------------------------------+ .. topic:: 数值运算 加减乘除,小学就学过了,取模(就是做除法取余数)、指数运算、取相反数难点,但还在初中生那个层次晃悠 ;-) :: >>> 10/3 3 >>> 10%3 1 >>> 2**3 8 >>> a=1 >>> -a -1 .. topic:: 位运算 你总该知道计算机内部都是以二进制对数据进行存储的吧!所谓位运算就是用来操作这些二进制位的。 更详细的解释就恕我不能完整介绍,了解的同学自然一看就明白了,不了解的同学估计一时半会也还用不上, 所以你也不用担心 ;-) :: >>> 2>>1 # 0010 -> 0001 1 >>> 2<<1 # 0010 -> 0100 4 >>> 5 | 10 # 0101 | 1010 -> 1111 15 >>> 5 & 10 # 0101 & 1010 -> 0000 0 .. topic:: 比较操作 对两个对象进行比较,返回一个布尔值。 要注意的是 ``=`` 号已经被用来进行名字绑定(也就是赋值)了,所以我们这里使用 ``==`` 符号来做相等的判断。 :: >>> 1 < 2 True >>> 'b'>'a' True >>> 1!=2 True (这几个例子好弱智啊!= =") .. topic:: 布尔运算 这个先不急,马上就要讲到了 ;-) 布尔 ------- ``True`` 或者 ``False`` ,这就是布尔类型,干脆俐落。 .. sidebar:: 其他对象到布尔对象的转换规则 ``None`` 、任何数值类型中的 ``0`` 、空字符串 ``''`` 、空元组 ``()`` 、空列表 ``[]`` 、空字典 ``{}`` 都被当作 ``False`` ,还有自定义的类型如果它实现了 ``__nonzero__()`` 或 ``__len__()`` 方法且方法返回 ``0`` 或 ``False`` 的,则其实例也被当作 ``False`` ,其他对象均为 ``True`` 。 在 python 中,任何对象都可以隐式地转换为布尔对象,所谓隐式的意思就是自动进行转换, 所以那些对布尔对象的操作,你都可以传个任意的对象过去,python 会按照一定规则自动地将 这个对象转换成合适的 ``True`` 或者 ``False`` 。 这一点常常让人觉得布尔对象神龙见首不见尾,似乎无处不在却又很少见到其真正的本尊。 通过构造 ``bool`` 对象,你可以对其他对象到布尔对象的转换规则进行验证, 看看究竟哪些对象是 ``True`` ,哪些对象是 ``False`` : :: >>> bool(0) False >>> bool(1) True >>> bool('hello') True >>> class FooBar(object): # 这里涉及到 class ,参考第 n 章 第 n 节 类与对象,或者直接跳过。 ... def __nonzero__(self): ... return False ... >>> foobar = FooBar() # 创建 FooBar 类的实例。 >>> bool(foobar) False >>> 0 or 0L or 0.0 or 0j or '' or () or [] or {} or False False 如果你拥有基本的逻辑知识的话,看到上面最后一句代码,应该就能确定那些参与 ``or`` 操作的对象都是 ``False`` 了, 因为 or 的规则是只要有一个对象为真,整个表达式就为真了。 任何涉及布尔的地方总免不了 ``not`` 、 ``and`` 、 ``or`` 这三个运算。 python 的布尔类型自然也不例外。 不过在这里他们虽然叫还是叫做布尔运算,但像刚才说的,python 中任何对象都可以隐式地转换为布尔对象, 这使得实际上任何对象之间都可以进行这种所谓的“布尔”运算,于是 python 干脆对这三种运算的语义又做了细微的调整, 在保留布尔操作本身语义的同时,大大增强了他们的灵活性与实际用途。 python 中这三种运算的规则定义如下: .. sidebar:: and or 组合 通过 and 和 or 的组合你可以获得许多奇妙的效果, 比如 ``condition and a or b`` 实际上就等价于 c 语言中的 ``condition? a:b`` , 意思就是说如果条件 ``condition`` 满足那么就 ``a`` ,否则就 ``b`` 。 有意思吧 ;-) 至于为什么就当做作业留给聪明的你慢慢思考吧(见练习题1)。 .. topic:: not a 如果 ``a`` 为 ``True`` 则返回 ``False`` ,为 ``False`` 则返回 ``True`` .. topic:: a and b 如果 a 为 ``True`` 则返回 b,否则返回 a .. topic:: a or b 如果 a 为 ``False`` 则返回 b,否则返回 a 示例::: >>> not 1 False >>> products = None >>> products = products or [default_product1, ...] # 伪码,如果 products 为 ``None`` 或者为空的话,给它提供一个默认值。 # 这就是 python 布尔操作符超越传统布尔操作符的应用之一了。 TODO:提供多一些有趣有用的 and or 实例。 另外,进行数值运算的时候布尔对象也能够隐式地转换成整数, ``True`` 是 ``1`` , ``False`` 是 ``0`` : :: >>> True+1 2 >>> False+1 1 >>> int(True) 1 >>> int(False) 0 整数 ------ 如果你还有受过其他语言的“污染”的话,python 的整数就是数学中的整数而已,没什么好说的。 对于受过某些其他语言的熏陶的朋友,也许需要注意的一点就是: python 的整数没有什么 short、long 的区分,它甚至连大小的限制都没有! :: >>> 1 1 >>> 9999999999999999999999 9999999999999999999999L >>> 99999999999999999 * 9999999999999999999 999999999999999989900000000000000001L .. topic:: 长整数 实际上在内部 python 对整数的处理还是会分为普通整数和长整数, 普通整数就是大家在其他语言中常见到整数,长度为机器位长, 通常都是 32 位(不过再过几年可能就要改口说是通常 64 位了)。 而超过这个范围的整数就自动当作长整数处理, 而长整数表示范围可就几乎完全——只要内存吃得消——没有限制了。 如果你还想刨根问底说怎么做到没有限制,那我只好叫你去看 CPython 源码了 ;-) .. topic:: 小整数池 为了提高性能,python 在启动时会对一定范围以内的小整数创建缓存, 这样在后面创建这些小整数对象的时候,就不用重复的去申请内存, 而是直接使用缓存中的小整数对象。 这一点通过 ``id()`` 函数就可以看得出来: :: >>> a = 10 >>> b = 10 >>> id(a) 11163620 >>> id(b) 11163620 >>> a = 999999999999999999 >>> id(a) 11314456 >>> b = 999999999999999999 >>> id(b) 11314960 # 显然 b 的地址与 a 不同。 浮点数 -------- python 的浮点数其实就是数学中的小数, c 语言中的 ``double`` 。 在数值运算中,整数与浮点数运算的结果是浮点数,这就是所谓的“提升规则”, 也就是“小”类型会被提升为“大”类型参与计算,许多语言都是这样的。 从这个角度去看,也就不难理解为什么布尔对象参与数值运算时会被隐式地转换成整数了 ;-) :: >>> 3/2 1 >>> 3/2.0 1.5 复数 ------- 复数~~,貌似终于升级到高中的层次了 = =" 如果你不知复数为何物,大可直接跳过,损失不大。 既然你懂复数的话,你应该会发现 python 的复数其实非常直观,和数学中学到的没有什么不同。 :: >>> 1+1j (1+1j) >>> 1+1j +3 (4+1j) >>> 1+1j +3j (1+4j) >>> (1+1j)*5 (5+5j) >>> (1+1j)*(2+2j) 4j >>> (1+1j)/(2+2j) (0.5+0j) >>> (1+1.0j)/(2+2j) (0.5+0j) >>> (1+1j)**(2+2j) # 指数运行,见 数值运算_ (-0.26565399884924118+0.31981811385613623j) 字符串 ======== python 字符串既可以用单引号表示也可以用双引号表示, 甚至还可以用三引号——哦不对,是三个引号——来表示。 这样如果字符串里本身包含双引号,你就可以用单引号来表示: :: >>> 'My name is "python"' 'My name is "python"' 而如果字符串里本身包含单引号呢,你又可以用双引号用表示: :: >>> "My name is 'python'" "My name is 'python'" 真是太方便了! 三个引号的字符串就更方便了,中间甚至还可以换行! :: >>> '''My ... name ... is ... "python" ... ! ... ''' 'My\nname\nis\n"python"\n!\n' >>> """My ... name ... is ... 'python' ... ! ... """ "My\nname\nis\n'python'\n!\n" 字符串转义 ------------ 那字符串里要是既有单引号又有双引号怎么办?答案就是字符串转义: :: >>> 'My \'name\' is "python"!' 'My \'name\' is "python"!' 所谓字符串转义就是 ... TODO: 字符串转义的含义、作用 等。 下面这个表列出所有转义符及其简要说明,要是觉得这点简要的解释不过瘾的话, 直接跑到 python shell 下面去试验一下,马上就清楚了: :: >>> print '\a' >>> print 'aa\bbb' abb >>> print 'a\tb\nab' a b ab >>> TODO: 更多有趣例子 +-------------+------------------------------------------------+ | 转义符 | 含义 | +=============+================================================+ | ``\换行`` | 忽略后面的换行符 | +-------------+------------------------------------------------+ | ``\\`` | 字符 ``\`` | +-------------+------------------------------------------------+ | ``\'`` | 单引号 ``'`` | +-------------+------------------------------------------------+ | ``\"`` | 双引号 ``"`` | +-------------+------------------------------------------------+ | ``\a`` | 发出声音:滴 | +-------------+------------------------------------------------+ | ``\b`` | 退格符 | +-------------+------------------------------------------------+ | ``\f`` | | +-------------+------------------------------------------------+ | ``\n`` | 换行符 | +-------------+------------------------------------------------+ | ``\r`` | 回车符 | +-------------+------------------------------------------------+ | ``\t`` | 水平 TAB 符 | +-------------+------------------------------------------------+ | ``\v`` | 竖直 TAB 符 | +-------------+------------------------------------------------+ | ``\ooo`` | 输出 8 进制数字(最多3个) ``oo`` 所代表的字符 | +-------------+------------------------------------------------+ | ``\xhh`` | 输出 16 进制数字(最多2个) ``hh`` 所代表的字符| +-------------+------------------------------------------------+ | ``\N{name}``| | +-------------+------------------------------------------------+ | ``\uxxx`` | | +-------------+------------------------------------------------+ | ``\Uxxx`` | | +-------------+------------------------------------------------+ 序列操作 -------------- 序列类型(Sequence Types)其实是一个抽象接口, 内置类型中实现了这一接口的有字符串、Unicode 对象、元组、列表、 buffer、xrange。既然先讲到字符串,那就在这里就把这个概念说明一下先, 在后面向大家介绍其他序列类型时就直接参考这里了。 所有的序列类型都支持一些共同的操作,这里拿字符串来举例子,其他序列类型大家到时候一看就明白了。 .. topic:: ``in`` :: >>> 'python' in 'I love python!' True >>> 'c' not in 'I love python!' False .. topic:: 连接 ``+`` 将多个序列对象连接起来。 :: >>> 'I '+'love '+'python!' 'I love python!' .. topic:: ``*`` 拷贝 n 份(准确得说是浅拷贝,见第n章第n节) :: >>> print 'I love python!\n'*3 I love python! I love python! I love python! .. topic:: 索引 第一个是 ``0`` ,正数表示从左向右数第几个,负数是从右向左数,不过第一个的左边没有,就绕到最右边去了。 :: >>> 'python'[0] 'p' >>> 'python'[3] 'h' >>> 'python'[-1] 'n' >>> 'python'[-3] 'h' .. sidebar:: 惯用法 ``sequence[:]`` ,也就是使用 ``start`` 、 ``end`` 和 ``step`` 的默认值对序列对象切片, 实际上就是对序列对象的一个浅拷贝,而这显然比实际的拷贝操作方便多了。 .. topic:: 切片 取序列中一个片段。 原型是 ``sequence[start:end:step]`` ``start`` 表示起始位置, ``end`` 表示结束位置, ``step`` 表示每经过多少取一个值。 三个值均可忽略。 ``step`` 默认值为 ``1`` ,表示没有间隔; ``start`` 默认值为 ``0`` ,也就是序列最开始的位置, ``end`` 默认为序列的长度,也就是最末尾的后面一位。 :: >>> 'python'[0:6:2] # 完整版本 'pto' >>> 'python'[0:6] # 忽略 step 'python' >>> 'python'[:3] # 忽略 start 和 step 'pyt' >>> 'python'[3:] # 忽略 end 和 step 'hon' >>> 'python'[:] # 全部忽略 'python' 另外对于序列类型中的可变类型(比如列表),还可以直接利用索引和切片对元素进行修改和删除,可惜字符串不是, 如果想看,直接跳到 列表_ 那一节去就行了。 常用字符串操作 ---------------- 上面这一节讲的其实已经是属于常用字符串操作了,不过那些是所有序列对象都共同拥有的东西, 而这一节要介绍的是专门为字符串提供的操作。 :: >>> dir(str) ['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__ ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__g t__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__ ', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', ' __rmul__', '__setattr__', '__str__', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'index', 'isalnum', 'isalpha', 'isdi git', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lst rip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit' , 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', ' translate', 'upper', 'zfill'] 其实里面许多的方法,都是看到名字就能联想到其功能的。 稍微复杂点的方法,只要在 python shell 里面做点实验也都能猜个八九不离十。 如果还剩下些什么疑问呢,就到这里来找答案吧。 .. topic:: 连接与分割 上面介绍序列类型时,我们已经使用了 ``+`` 号来做字符串的连接操作, 在某些情况下这当然是不错的,然而在多数情况下我们其实都不推荐这种做法, 因为大量的这种连接操作会大大影响效率。比如说这个例子::: >>> 'I '+'love '+'python!' 'I love python!' .. sidebar:: 字符串缓存 python 虚拟机内部会对所有 python 字符串进行缓存,所以任何两个内容相同的字符串, 实际上都指向同一个字符串。 典型的空间换时间的做法,节省了许多为分配相同字符串浪费的cpu时间, 同时也浪费了一些内存空间。 所以做大量字符串连接的时候,千万要慎重! 因为那些中间产生的无用字符串对象都会被虚拟机缓存,很可能会始终占据着你的内存。 :: >>> a = 'python' >>> b = 'python' >>> id(a) 11361984 >>> id(b) 11361984 分解开来看就是 ``('I '+'love ') + 'python!'`` ,第一次连接操作就会产生一个中间对象 ``'I love '`` ,而这个对象从结果来看完全是没有用的。 大量的连接操作,就会产生大量无用的中间对象。 浪费了分配内存所花费的时间也浪费了内存。 所以,两个字符串的连接用 ``+`` 是很方便的,不过两个以上的字符串连接我们还是更推荐下面这个方法: :: >>> ' '.join(['I', 'love', 'python!']) 'I love python!' 你看,也不是很麻烦嘛 ;-) 你还可以用其他字符串来进行连接: :: >>> '--'.join(['I', 'love', 'python!']) 'I--love--python!' 也许你已经注意到了里面的中括号,中括号是用来构造列表的(参考 列表_ )。 ``split`` 是 ``join`` 的逆操作,原型是 ``split( [sep [,maxsplit]])`` , 它可以把一个字符串分割成多个字符串的列表: :: >>> 'I love python!'.split(' ') ['I', 'love', 'python!'] >>> 'I--love--python!'.split('--') ['I', 'love', 'python!'] 如果你不传递或者传递 ``None`` 给 ``sep`` 参数,那么 ``split`` 会启用 一个比较特殊的字符串分割策略,多说无益,先看代码: :: >>> 'I love python!'.split(' ') # 使用空格分割 ['I', 'love', '', '', '', '', 'python!'] >>> 'I love python!'.split() # 默认分割策略 ['I', 'love', 'python!'] 看出区别了吧,它会把连续的空白当作分割符,其作用就不用我明说了吧 ;-) ``split`` 方法还接受另一个可选的参数: ``maxsplit`` ,意思就是最大分割次数, 这样分割出来的列表最长也就是 ``maxsplit + 1`` 。 :: >>> 'I love python!'.split(' ', 1) ['I', 'love python!'] ``split`` 还有一个姐妹版: ``rsplit`` ,作用基本一样,就是限制分割次数时, 分割的顺序变成从右到左。 :: >>> 'I love python!'.rsplit(' ', 1) ['i love', 'python'] 另外,后面还会介绍另一种强大的构造字符串的方法: 字符串模板_ 。 .. topic:: 大小写转换 ``upper`` 将字符串转换为大写, ``lower`` 转换成小写。 TODO: ``swapcase`` 比较奇妙的是 ``title`` ,它将每个单词的首字母转成大写,其他转成小写。 :: >>> 'Python'.upper() 'PYTHON' >>> 'Python'.lower() 'python' >>> 'the python book'.title() 'The Python Book' .. topic:: 字符串测试 :: >>> 'python'.islower() # 是否都是小写 True >>> 'PYTHON'.isupper() # 是否都是大写 True >>> 'The Python Book'.istitle() # 是否 ... (参考上面对 title 方法的解释) True >>> 'python'.isalpha() # 是否都是字母, isalnum 方法作用相同 True >>> '42'.isdigit() # 是否都是数字 True >>> ' '.isspace() # 是否都是空格 True >>> ''.islower() or ''.isupper() or ''.istitle() or ''.isalpha() or ''.isdigit() or ''.isspace() False 最后一句证明了这些测试对空字符串都不成立。 .. topic:: 查找 ``find`` 方法返回子串在字符串中出现的位置,原型是 ``find( sub[, start[, end]])`` , 可选的 ``start`` 、 ``end`` 参数用来限制查找范围,如果找不到则返回 ``-1`` 。 ``index`` 方法和 ``find`` 方法一样,唯一区别就是找不到的时候会抛出 ``ValueError`` 异常(见某章某节 异常)而不是返回 ``-1`` 。 ``find`` 和 ``index`` 方法也都有个“姐妹版”,也就是 ``rfind`` 和 ``rindex`` ,功能类似, 只不过查找的方向变成从右向左。 :: >>> 'I love python!'.find('love') 2 >>> 'I love python!'.find('c') -1 >>> 'I love python!'.index('c') Traceback (most recent call last): File "<stdin>", line 1, in ? ValueError: substring not found >>> 'python is pythonic!'.find('python') 0 >>> 'python is pythonic!'.rfind('python') 10 .. topic:: ``startswith`` , ``endswith`` 判断字符串是否以某个子字符串开始或结束。 原型: ``startswith( prefix[, start[, end]])`` , ``endswith( suffix[, start[, end]])`` ``prefix`` / ``suffix`` 就是这个子字符串,可选参数 ``start`` 、 ``end`` 指定源字符串中哪一部分参与判断, 默认值分别为 ``0`` 和字符串的长度,也就是整个字符串参与判断的意思。 :: >>> 'python'.startswith('pyt') True >>> 'python'.endswith('hon') True >>> 'python'.startswith('th', 2) True >>> 'python'.endswith('th', 0, -2) True .. topic:: replace TODO .. topic:: ljust rjust center TODO .. topic:: translate TODO .. topic:: zfill TODO .. topic:: TODO TODO 字符串与字节流 ---------------- Unicode 字符串 ---------------- Unicode 是一个很重要的话题,也是一个现代程序员所必备的知识之一。那还是在 n 年以前 ... 话说老美刚整出计算机的那会,老美还在说英文(当然,现在也还在说英文),大家知道,英文 abc 总共也没几个字符, 就算加上一些稀奇古怪的!@#$%^&这样的字符, 也就那么些了,最后把各种奇怪字符都加在一起算了一下,大概 127 个, 而一个字节能表达 256 种字符呢,用一个字节表示一个字符都还绰绰有余。 当时谁都觉得用一个字节表示一个字符真好。这种编码方式叫做 ASCII 。 后来计算机便传入了中国和许多其他国家,很快就遇到了一个大问题,汉语言文字博大精深,又岂是小小一个字节能表达得了的? 于是有人发明了用两个字节表达汉字的编码方法,叫做 gbk 。这种现象同样也在其他非英语国家上演着。 大家都用着各自不同的互相冲突的编码方式,这给交流带来极大不便,此时的世界亟需一个统一的标准。 于是 Unicode 便应运而生了!Unicode 定义了一个大表,里面包含了全世界所有已知的字符,然后给这些字符编号,每个字符对应一个数字, 也就是所谓的代码点(code-point)。 需要注意的是 Unicode 本身是不在乎字符在计算机上是如何存储的,一个字节还是两个字节还是三个字节与 Unicode 无关, 你可以采取各种存储策略,甚至你可以直接把字符在 Unicode 大表中的编号——也就是代码点——来当作字符在计算机中的表示, 而规定如何存储 Unicode 字符的规范就叫做编码! 现在世界上的编码成百上千,以前的 gbk 在现在 Unicode 新环境下也仍然存在,不过它只能处理 Unicode 字符的一部分了。 如果希望自己的程序能够跨越国界的话,最好还是使用一种能够处理所有 Unicode 字符的全能编码。 最流行的全能编码应该是 utf-8 了,它使用一种变长的存储方式,对传统的 ASCII 字符还是使用一个字节来存储, 这样那些英文国家的程序可以完全不受影响。当然这样的话它就要使用更多的字节来存储其他非英文字符了。 在 python 中定义一个 Unicode 字符串非常简单,在普通字符串前面加一个 ``u`` 即可, 还可以使用转义符 ``\u`` 直接使用代码点来定义 Unicode 字符串: :: >>> u'派松' u'\u6d3e\u677e' >>> print u'\u4e2d\u56fd' 中国 >>> print u'派松\u4e2d\u56fd' 派松中国 .. sidebar:: 系统默认编码 :: >>> import sys >>> sys.getdefaultencoding() 'ascii' 使用 ``encode`` (编码) ``decode`` (解码) 方法,就可以在普通字符串和 Unicode 字符串之间进行转换, 两个方法的原型分别为: ``encode( [encoding[,errors]])`` 和 ``decode( [encoding[,errors]])`` 这两个方法都接受两个可选参数,第一个是编码名称,默认是系统默认编码, 第二个参数是个字符串,用来指定错误处理方式,可以使用的值有: +-------------------------+----------------------------------------------------------------------------+ | 取值 | 错误处理方式。 | +=========================+============================================================================+ | ``'strict'`` | 抛出异常 ``UnicodeError`` ,这是默认行为。 | +-------------------------+----------------------------------------------------------------------------+ | ``'ignore'`` | 忽略错误字符,继续处理其他文本。 | +-------------------------+----------------------------------------------------------------------------+ | ``'replace'`` | 用一个合适的字符替代出错字符,解码时使用标准 Unicode 替代字符 ``'\uFFFD'`` | | | ,编码时使用 ``'?'`` 。 | +-------------------------+----------------------------------------------------------------------------+ | ``'xmlcharrefreplace'`` | 使用合适的 XML character reference ?? 替代出错字符,仅在编码时有用 | +-------------------------+----------------------------------------------------------------------------+ | ``'backslashreplace'`` | 使用转义字符串替代出错字符,仅在编码时有用。 | +-------------------------+----------------------------------------------------------------------------+ :: >>> print u'派\uffff松'.encode('gbk', 'strict') Traceback (most recent call last): File "<stdin>", line 1, in ? UnicodeEncodeError: 'gbk' codec can't encode character u'\uffff' in position 1: illegal multibyte sequence >>> print u'派\uffff松'.encode('gbk', 'ignore') 派松 >>> print u'派\uffff松'.encode('gbk', 'replace') 派?松 >>> '派\xff\xff松'.decode('gbk', 'replace') u'\u6d3e\ufffd\u677e' >>> print u'派\uffff松'.encode('gbk', 'xmlcharrefreplace') 派松 >>> print u'派\uffff松'.encode('gbk', 'backslashreplace') 派\uffff松 直接在普通字符串中使用中文时,使用的编码方式取决于源代码文件本身所使用的编码, 在中文平台上一般默认都是 gbk ,中文平台的 console 中一般也都是 gbk。 下面我们来体验几种不同编码之间的差异: :: >>> u'派松' # 无编码 u'\u6d3e\u677e' >>> '派松' # 默认的 gbk 编码 '\xc5\xc9\xcb\xc9' >>> u'派松'.encode('utf-8') # 使用 encode 从 Unicode 字符串转换成 普通字符串 '\xe6\xb4\xbe\xe6\x9d\xbe' >>> '派松'.decode('gbk') # 使用 decode 从普通字符串转换成 Unicode 字符串 u'\u6d3e\u677e' >>> len(u'派松') 2 >>> len('派松') 4 >>> len(u'派松'.encode('utf-8')) 6 我们说过 Unicode 本身不定义字符在计算机中的表现方式,所以当我们需要将 Unicode 字符串保存到文件, 或是在网络中传输,或是从 console 中 ``print`` 出来时,都需要以某种方式编码 Unicode 字符串先。 不过很多操作面对 Unicode 字符串时都能够智能地选择某种默认编码进行处理, 比如在一般的中文平台上, ``print`` 默认便使用 gbk 来进行输出: :: >>> print u'\uFFFF' Traceback (most recent call last): File "<stdin>", line 1, in ? UnicodeEncodeError: 'gbk' codec can't encode character u'\uffff' in position 0: illegal multibyte sequence 还有要注意的一点就是在源代码文件中定义包含非 ASCII 字符的 Unicode 字符串时, 最好是文件的顶部(第一行或第二行)用注释的方式定义一下此文件本身所使用的编码, 因为 python 虚拟机在解析的时候需要从编码后的字符串解码得到 Unicode 字符串, 所以如果不知道源文件本身使用的编码,虚拟机就使用默认的 ASCII 进行处理, 这样就很可能出现乱码甚至是抛出异常。 .. sidebar:: 指定源文件编码 究竟如何指定源文件使用的编码才是符合 python 规范的呢? 其实 python 在这一点上是充分考虑了各大主流编辑器的具体情况的。 python 规定了这样一个正则表达式: ``coding[:=]\s*([-\w.]+)`` , 所有满足这个正则表达式的写法都可以被 python 虚拟机识别。 懂正则表达式的同学应该不难看出左边的两种写法显然都是满足规范的。 而对于不了解正则表达式的同学就不用考虑这么多了, 从左边的两种主流方式选一种就行了。 对于大部分编辑器都应该这么写: :: #!/usr/bin/python # -*- coding: utf-8 -*- python = u'派松' 使用 vim 的同学可以这样写: :: #!/usr/bin/python # vim: set fileencoding=utf-8 : python = u'派松' 上面两种写法都可以同时被 python 虚拟机和相应的编辑器识别 (当然其中的 ``utf-8`` 可以换成任何合法的编码名称)。 字符串模板 -------------- 格式化 ````````````````` 上面已经介绍过几种构造字符串的方法:不适合大量使用的 ``+`` 操作和字符串的 ``join`` 方法。 他们可以把几个小字符串拼接成一个大字符串。 但是它们并不能很好得处理其他类型的对象的字符串表示,换句话说就是“格式化”。 先给大家看几个例子,应该很快就会明白所谓“格式化”到底是什么意思了。 :: TODO: 字符串格式化入门例子 +--------+-------------------+ | 格式码 | 含义 | +========+===================+ | d | | +--------+-------------------+ | i | | +--------+-------------------+ | o | | +--------+-------------------+ | u | | +--------+-------------------+ | x | | +--------+-------------------+ | q | | +--------+-------------------+ 模板 `````````` TODO 容器类型 ============ 所谓容器,就是装东西的东西了。下面这些类型的作用都是用来“装”——或者说管理——其他对象的。 元组 ---------- 通过逗号分隔的一些对象,这就是元组了: :: >>> 1,2,3 (1, 2, 3) 不过通常我们都用一对小括号扩着,主要是为了避免歧义: :: >>> (1,2,3) (1, 2, 3) 千万要注意的是,如果元组中只有一个对象,一定记得要在对象后面加一个逗号,因为否则的话, python 会把它当作一个对象,就算加了括号也没用! :: >>> 1 1 >>> 1, (1,) >>> (1) 1 >>> (1,) (1,) 首先元组属于序列类型,自然就拥有所有的序列操作: :: >>> (1,2,3)+(5,6,7) (1, 2, 3, 5, 6, 7) >>> (1,2)*3 (1, 2, 1, 2, 1, 2) >>> 2 in (1,2) True >>> (1,2,3,4,5,6,7)[1:-1:2] (2, 4, 6) 元组还是不可变对象,所以对其中的元素不能修改: :: >>> (1,2,3)[0] = 0 Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: object does not support item assignment 另外,元组和下面马上要介绍的列表都还支持一个非常有趣的特性,就是所谓的“并行绑定”: (上面代码中等号左边的元组都可以替换成列表,效果一样) :: >>> (a, b) = (1, (2, 3)) >>> print a, b 1 (2, 3) >>> (a, (b, c)) = (1, (2, 3)) >>> print a, b, c 1 2 3 >>> (a, b, c) = 'pyt' >>> print a, b, c p y t 相信聪明的你已经看出来了,当绑定一个元组/列表的时候,它会自动去绑定元组/列表中每一个元素。 这个过程甚至还是可以递归的! 而等号右边可以是任何支持索引的数据类型,比如大家了解的序列类型就可以。 列表 ---------- 使用中括号括起来的一组对象,对象之间以逗号分隔,就是列表了。 列表的中括号可不像元组的小括号那样可以省略,否则就会有歧义。 首先列表属于序列类型: :: >>> [1,2,3]+[5,6,7] [1, 2, 3, 5, 6, 7] >>> [1,2]*3 [1, 2, 1, 2, 1, 2] >>> 2 in [1,2] True >>> [1,2,3,4,5,6,7][1:-1:2] [2, 4, 6] 列表还是可变数据类型,我们可以通过索引和切片随意修改和删除其中的元素: :: >>> a = [1,2] >>> a[0] = 0 >>> a [0, 2] >>> a = [1,2,3,4,5,6,7] >>> a[1:-1:2] [2, 4, 6] >>> a[1:-1:2] = [0]*3 >>> a [1, 0, 3, 0, 5, 0, 7] >>> del a[1:-1:2] >>> a [1, 3, 5, 7] 此外,列表还提供了许多强大的操作。 .. topic:: 简单操作 :: >>> a = [1, 2, 3] >>> a.append(4) # 在列表末尾追加一个对象 >>> a [1, 2, 3, 4] >>> a.extend([5,6,7]) # 用另一个列表来扩展这个列表 >>> a [1, 2, 3, 4, 5, 6, 7] >>> a.insert(0, 2) # 在索引为 0 的位置上插入对象 2 >>> a [2, 1, 2, 3, 4, 5, 6, 7] >>> a.count(2) # 计算对象出现的次数 2 >>> a.index(2) # 返回对象第一次出现的位置 0 >>> a.index(2, 3, -1) # 后面两个参数分别为 ``start`` 和 ``end`` ,表示在列表中查找的范围, # 找不到就抛出异常 Traceback (most recent call last): File "<stdin>", line 1, in ? ValueError: list.index(x): x not in list >>> a.pop() # 返回列表中最后一个对象,并把该对象从列表中删除 7 >>> a [1, 1, 2, 3, 4, 5, 6] >>> a.pop(-2) # 返回列表中位置为 -2 (也就是倒数第二个) 的对象,并把该对象从列表中删除 5 >>> a [1, 1, 2, 3, 4, 6] >>> a.remove(1) # 删除列表中位置为 1 的对象 >>> a [1, 2, 3, 4, 6] >>> a.reverse() # 反转列表 >>> a [6, 4, 3, 2, 1] .. topic:: 排序 TODO 字典 ---------- 其他语言中它也常被叫做哈希表或是关联数组,在这里我们叫它字典。 语法是两个大括号,中间括着一个或多个以逗号分隔的 ``key:value`` 对,``key`` 、 ``value`` 都是普通 python 对象, 看下代码你就全明白了::: >>> numbers = {'one':1, 'two':2, 'three':3} >>> numbers['one'] 1 组合其他数据结构::: >>> article = { ... 'title':'About Python', ... 'author':'someone', ... 'content':'...', ... 'comments':[ ... {'title':'the article is great!', ... 'author':'annoynouse', ... 'content':'...'}, ... {'title':'the article is good!', ... 'author':'someone', ... 'content':'...'}, ... ] ... } >>> article['title'] 'About Python' >>> article['comments'] [{'content': '...', 'author': 'annoynouse', 'title': 'the article is great!'}, { 'content': '...', 'author': 'someone', 'title': 'the article is good!'}] >>> article['comments'][0]['author'] 'annoynouse' 说白了,它其实就是一个 key、value 之间的映射,而它的好处就是,通过 key 来对 value 的查找非常快。 比如说你要管理许多员工信息,你常常需要通过员工的名字来找到相应的员工信息对象,那么你就可以建立这样一个映射, 也就是字典,来加速这一查找的过程::: >>> usermap = {'wukong':user1, 'bajie':user2, 'tangceng':user3} >>> usermap['wukong'] <__main__.User object at 0x00C269D0> 而如果不用字典做,你就需要对一个一个 ``User`` 对象的名字进行比较,看它是否是 ``'wukong'`` ,是则罢了, 否则的话还要继续比较下去。显然不如字典来得高效快捷。 要做字典的 key 也是要满足一定条件的,这个条件就是该对象必须是不可修改的(具体原因留作作业给大家思考), 除了元组以为的不可变对象都能够满足要求,对于元组来说,虽然它是不可变对象,但由于它其中可以包含可变对象, 所以并不是所有元组都是不可修改,只有那些只包含不可变对象的元组才能满足做 key 的要求。 了解内幕的朋友会知道,字典内部其实会对 key 对象进行一个哈希操作,得到一个整数,以此作为 value 对象实际存储的位置, 这也就是字典能够快速查找的根本原因。python 有一个内置函数 ``hash``,它会调用对象的一个叫做 ``__hash__`` 的方法, 来返回对象的哈希值。这个 ``__hash__`` 方法便是真正执行这个“哈希”操作的地方,当然并非所有对象都能正常完成这个哈希操作, 原因我们刚刚说过了::: >>> hash(1) 1 >>> hash('python') 1142331976 >>> 'python'.__hash__() 1142331976 >>> hash((1,2,3)) -378539185 >>> hash([1,2,3]) Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: list objects are unhashable >>> hash((1,2,[1,2,3])) Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: list objects are unhashable 当然你自己的 ``class`` 如果需要也是完全可以重写掉默认的 ``__hash__`` 函数,返回合适的整数的。 集合 ---------- 集合的概念应该很多人都不会陌生,集合中元素不能重复,集合之间可以进行并、交、补等操作。 在 python 中,对应的就是集合( ``set`` )数据类型。 不过集合类型可没有上面那些类型那样的语法支持,没办法,小括号中括号大括号都被用光了 ;-( :: >>> set1 = set() >>> set1.add(1) >>> set1.add(2) >>> set1 set([1, 2]) >>> 1 in set1 True >>> 4 not in set1 True >>> set1 = set([1,2]) >>> set2 = set([1,2,3]) >>> set1 <= set2 # set1 是否是 set2 的子集,set1.issubset(set2) True >>> set2 >= set1 # set2 是否是 set1 的超集,set2.issuperset(set1) True >>> set1 = set([1,2,3]) >>> set2 = set([2,3,4]) >>> set1 | set2 # 并 set1.union(set2) set([1, 2, 3, 4]) >>> set1 & set2 # 交 set1.intersection(set2) set([2, 3]) >>> set1 - set2 # 差 set1.difference(set2) set([1]) >>> set1 ^ set2 # 异或 set1.symmetric_difference(set2) set([1, 4]) 集合还有一个特性就是它能保证其中的元素不重复::: >>> set([1,2,2,3,3,4,4]) set([1, 2, 3, 4]) 数组(array) -------------- TODO: 是否该加上 array 类型? 小结 ====== 练习 ====== * 解释 ``condition and a or b`` 与 ``condition?a:b`` 的等价关系。 * 还没学过 c 语言?OK,可以告诉你, ``condition?a:b`` 的意思就是: :: if condition: return a else: return b * 使用 ``range`` 快速构造等差数列。 * 提示:在 python shell 中使用 ``help(range)`` 查看 ``range`` 函数的文档。 * 利用元组并行赋值的特性如何快速交换两个变量的值? * 使用列表模拟队列和堆栈操作。 * 快速出去列表中重复元素。 * 提示:集合中元素不重复。 .. macro:: [[PageComment2(nosmiley=1, notify=1)]]