Rendering of reStructured text is not possible, please install Docutils.
.. contents::

:status: 草稿 ;HuangYi; 40%;

===================
Python内置数据类型
===================

数值类型
==========

运算符
-------
      
我决定在介绍数值类型之前还是先介绍一下 python 的这些个运算符,大部分都是很常见的,
只有很少的运算符可能在其他语言中不常见到。

由于 python 支持所谓运算符重载,有些类型会改变一些运算符的含义,
这些我们到时候再另行介绍,这里介绍的都是这些运算符最直观最基本的含义。

  +----------+---------------------------------------------------------------------------------------------+
  | 简单运算 | 加 ``+`` 、减 ``-`` 、乘 ``*`` 、除 ``/`` 、取模 ``%`` 、指数运算 ``**`` 、取相反数 ``-`` 。|
  +----------+---------------------------------------------------------------------------------------------+
  | 位运算   | 按位取反 ``~`` 、按位与 ``&`` 、按位或 ``|`` 、按位异或 ``^`` ,左移 ``<<`` 、右移 ``>>``   |
  +----------+---------------------------------------------------------------------------------------------+
  | 比较操作 |  ``<`` 、 ``>`` 、 ``==`` 、 ``>=`` 、 ``<=`` 、 ``<>`` 、 ``!=`` 。                        |
  +----------+---------------------------------------------------------------------------------------------+
  | 布尔操作 | ``not`` 、 ``and`` 、 ``or``                                                                |
  +----------+---------------------------------------------------------------------------------------------+

.. topic:: 简单运算
  
  加减乘除,小学就学过了,取模(就是做除法取余数)、指数运算、取相反数这些好像是初中学的。
  ::

    >>> 10/3
    3
    >>> 10%3
    1
    >>> 2**3
    8
    >>> a=1
    >>> -a
    -1

.. topic:: 位运算
  
  大家知道,计算机内部都是以二进制对数值进行存储,所谓位运算就是操作这些二进制位的。
  更详细的解释就恕我不能完整介绍,了解的同学自然一看就明白了,不了解的同学一时半会也还用不上。
  ::
    
    TODO: 一点实例

.. topic:: 比较操作

  对两个对象进行比较,返回一个布尔值,倒数第二的符号 ``<>`` 可能奇怪一点,它和最后一个 ``!=`` 一样,
  是不等于的意思。

.. topic:: 布尔运算

  这个不急,马上就要讲到了 ;-)


布尔
-------

``True`` 或者 ``False`` ,这就是布尔类型,干脆俐落。

.. sidebar:: 其他对象到布尔对象的转换规则

  ``None`` 、任何数值类型中的 ``0`` 、空字符串 ``''`` 、空元组 ``()`` 、空列表 ``[]`` 、空字典 ``{}``
  都被当作 ``False`` ,还有自定义的类型如果它实现了 ``__nonzero__()`` 或 ``__len__()`` 
  方法且方法返回 ``0`` 或 ``False`` 的,则其实例也被当作 ``False`` ,其他对象均为 ``True`` 。

在 python 中,任何对象都可以隐式地转换为布尔对象,这常常给大家一个错觉,认为布尔类型不存在,但实际上是布尔类型无处不在。

通过构造 ``bool`` 对象,你可以对这一点进行试验,看看哪些对象是 ``True`` ,哪些对象是 ``False`` :
::

  >>> bool(0)
  False
  >>> bool(1)
  True
  >>> bool('hello')
  True
  >>> class FooBar(object):
  ...     def __nonzero__(self):
  ...         return False
  ...
  >>> foobar = FooBar() # 创建 FooBar 类的实例,见 n 章 n 节 类与对象。
  >>> bool(foobar)
  False
  >>> 0 or 0L or 0.0 or 0j or '' or () or [] or {} or False
  False

如果你拥有基本的逻辑知识的话,应该不难看出,最后一句代码其实就证明了那些对象都是 ``False`` 。

布尔本是逻辑学数学中的概念,在那里布尔值之间就有三种基本的运算: ``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

  TODO:给一些有趣的 and or 实例。

另外,进行数值运算的时候布尔对象还能够隐式地转换成整数, ``True`` 是 ``1`` , ``False`` 是 ``0`` :
::

  >>> True+1
  2
  >>> False+1
  1
  >>> int(True)
  1
  >>> int(False)
  0


整数
------

我们生活在一个数学的世界里,数字对大家来说当不陌生,而整数便是数字中最基本的一种。

如果你没用过其他语言,那 python 的整数对于你来说应该是非常自然的;
如果你用过其他语言,那你很可能会发现 python 整数的一些独特之处。
首先 python 整数没有什么 short、long 等等的区分,
这还不算什么,python 整数最奇妙的地方莫过于他甚至没有大小的限制:
::

  >>> 1
  1
  >>> 9999999999999999999999
  9999999999999999999999L

.. topic:: 长整数

  实际上在内部 python 对整数的处理还是会分为普通整数和长整数,
  普通整数就是大家在其他语言中常见到整数。
  而超过这个范围的整数就自动当作长整数处理,
  而长整数可表示的范围就没有限制了。
  如果你还想刨根问底,那就只好去看 CPython 的实现了 ;-)

.. topic:: 小整数池

  为了提高性能,python 在启动时会对一定范围以内的小整数创建缓存,
  这样在后面创建这些小整数对象的时候,就不用重复的去申请内存,
  而是直接使用缓存中的小整数对象。
  这一点通过 ``id()`` 函数就可以看得出来:
  ::

    >>> a = 10
    >>> b = 10
    >>> id(a)
    11163620
    >>> id(b)
    11163620

浮点数
--------

所谓浮点数就是小数,如果你有 c 这样的静态语言经验,你完全可以把它当作是 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)

字符串
========

.. sidebar:: 字符串缓存

  python 虚拟机对所有 python 字符串进行缓存,所以任何两个内容相同的字符串,实际上都是同一个字符串。
  ::

    >>> a = 'python'
    >>> b = 'python'
    >>> id(a)
    11361984
    >>> id(b)
    11361984

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!' 

  分解开来看就是 ``('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!']

  另外,后面还会介绍另一种强大的构造字符串的方法: 字符串模板_ 。

.. topic:: 大小写转换

  ``upper`` 将字符串转换为大写, ``lower`` 转换成小写。
  比较奇妙的是 ``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`` 方法都可以在前面加个 ``r`` ,也就是 ``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:: 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')
  派&#65535;松
  >>> 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      |                   |
+--------+-------------------+

模板
``````````

容器类型
============

所谓容器,就是装东西的东西了。下面这些类型的作用都是用来“装”——或者说管理——其他对象的。

元组
----------

通过逗号分隔的一些对象,这就是元组了:
::

  >>> 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

字典
----------

集合
----------

数组(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)]]