python study 1

一简介

Python 是著名的 “龟叔”Guido van Rossum在 1989 年开发的。

Python 是跨平台的，它可以运行在 Windows、Mac 和各种 Linux/Unix 系统上。在 Windows 上写 Python 程序，放到 Linux 上也是能够运行的

优点
1 Python 就为我们提供了非常完善的基础代码库，覆盖了网络、文件、GUI、数据库、文本等大量内容，被形象地称作 “内置电池（batteries included）”。用 Python 开发，许多功能不必从零编写，直接使用现成的即可。

2 除了内置的库外，Python 还有大量的第三方库，也就是别人开发的，供你直接使用的东西。

缺点
1 运行速度慢，和 C 程序相比非常慢，因为 Python 是解释型语言，你的代码在执行时会一行一行地翻译成 CPU 能理解的机器码，这个翻译过程非常耗时，所以很慢。而 C 程序是运行前直接编译成 CPU 能执行的机器码，所以非常快。
2 代码不能加密。如果要发布你的 Python 程序，实际上就是发布源代码，这一点跟 C 语言不同，C 语言不用发布源代码，只需要把编译后的机器码（也就是你在 Windows 上常见的 xxx.exe 文件）发布出去。要从机器码反推出 C 代码是不可能的，所以，凡是编译型的语言，都没有这个问题，而解释型的语言，则必须把源码发布出去。

python 解释器
当我们编写 Python 代码时，我们得到的是一个包含 Python 代码的以.py 为扩展名的文本文件。要运行代码，就需要 Python 解释器去执行.py 文件。
python 解释器：
CPython：从 Python 官方网站下载并安装好 Python 3.x 后，我们就直接获得了一个官方版本的解释器：CPython。这个解释器是用 C 语言开发的，所以叫 CPython。在命令行下运行 python 就是启动 CPython 解释器。

IPython：是基于 CPython 之上的一个交互式解释器，也就是说，IPython 只是在交互方式上有所增强，但是执行 Python 代码的功能和 CPython 是完全一样的

PyPy：另一个 Python 解释器，它的目标是执行速度。PyPy 采用 JIT 技术，对 Python 代码进行动态编译（注意不是解释），所以可以显著提高 Python 代码的执行速度。绝大部分 Python 代码都可以在 PyPy 下运行，但是 PyPy 和 CPython 有一些是不同的，这就导致相同的 Python 代码在两种解释器下执行可能会有不同的结果。如果你的代码要放到 PyPy 下执行，就需要了解 PyPy 和 CPython 的不同点。

Jython：运行在 Java 平台上的 Python 解释器，可以直接把 Python 代码编译成 Java 字节码执行。

IronPython：IronPython 和 Jython 类似，只不过 IronPython 是运行在微软.Net 平台上的 Python 解释器，可以直接把 Python 代码编译成.Net 的字节码。

Python 的解释器很多，但使用最广泛的还是CPython。如果要和 Java 或.Net 平台交互，最好的办法不是用 Jython 或 IronPython，而是通过网络调用来交互，确保各程序之间的独立性。

命令行模式和 Python 交互模式
在命令行模式下，可以执行 python 进入 Python 交互式环境，也可以执行 python hello.py 运行一个.py 文件。
执行一个.py 文件只能在命令行模式执行。敲一个命令 python hello.py

Python 交互式环境会把每一行 Python 代码的结果自动打印出来，但是，直接运行 Python 代码却不会。

Python 交互模式的代码是输入一行，执行一行，而命令行模式下直接运行.py 文件是一次性执行该文件内的所有代码。可见，Python 交互模式主要是为了调试 Python 代码用的，也便于初学者学习，它不是正式运行 Python 代码的环境！

文本编辑器
Visual Studio Code：微软出品、跨平台。
注意，不要用 Word 和 Windows 自带的记事本。Word 保存的不是纯文本文件，而记事本会自作聪明地在文件开始的地方加上几个特殊字符（UTF-8 BOM），结果会导致程序运行出现莫名其妙的错误。
文件名只能是英文字母、数字和下划线的组合

直接运行 py 文件
能不能像.exe 文件那样直接运行.py 文件呢？在 Windows 上是不行的，但是，在 Mac 和 Linux 上是可以的，方法是在.py 文件的第一行加上一个特殊的注释：

Copy
#!/usr/bin/env python3

print('hello, world')

然后，通过命令给 hello.py 以执行权限：
$ chmod a+x hello.py

就可以直接运行 hello.py 了

用 Python 开发程序，可以一边在文本编辑器里写代码，一边开一个交互式命令窗口，在写代码的过程中，把部分代码粘到命令行去验证，事半功倍！

输入和输出
输出：
用 print () 在括号中加上字符串，就可以向屏幕上输出指定的文字
print () 函数也可以接受多个字符串，用逗号 “,” 隔开，就可以连成一串输出：

Copy
print('The quick brown fox', 'jumps over', 'the lazy dog')

print () 会依次打印每个字符串，遇到逗号 “,” 会输出一个空格，因此，输出的字符串是这样拼起来的：

输入：
input ()，可以让用户输入字符串，并存放到一个变量里。比如输入用户的名字：

Copy
>>>name = input()
Michael

当你输入 name = input () 并按下回车后，Python 交互式命令行就在等待你的输入了。这时，你可以输入任意字符，然后按回车后完成输入。

输入完成后，不会有任何提示，Python 交互式命令行又回到 >>> 状态了。那我们刚才输入的内容到哪去了？答案是存放到 name 变量里了。可以直接输入 name 查看变量内容：

Copy
>>> name
'Michael'

在计算机程序中，变量不仅可以为整数或浮点数，还可以是字符串，因此，name 作为一个变量就是一个字符串。

要打印出 name 变量的内容，除了直接写 name 然后按回车外，还可以用 print () 函数：

Copy
>>> print(name)
Michael

input () 可以让你显示一个字符串来提示用户

Copy
name = input('please enter your name: ')
print('hello,', name)

输入是 Input，输出是 Output，因此，我们把输入输出统称为 Input/Output，或者简写为 IO。

二 python 基础

任何一种编程语言都有自己的一套语法，编译器或者解释器就是负责把符合语法的程序代码转换成 CPU 能够执行的机器码，然后执行。

Python 的语法比较简单，采用_缩进_方式

Copy
# print absolute value of an integer:
a = 100
if a >= 0:
print(a)
else:
print(-a)

以 #开头的语句是注释，注释是给人看的，可以是任意内容，解释器会忽略掉注释。其他每一行都是一个语句，当语句以冒号：结尾时，缩进的语句视为代码块。

缩进有利有弊。好处是强迫你写出格式化的代码，但没有规定缩进是几个空格还是 Tab。按照约定俗成的惯例，应该始终坚持使用4 个空格的缩进。

缩进的坏处就是 “复制－粘贴” 功能失效了，这是不方便的地方。当你重构代码时，粘贴过去的代码必须重新检查缩进是否正确。此外，IDE 很难像格式化 Java 代码那样格式化 Python 代码。

注意，Python 程序是大小写敏感的，如果写错了大小写，程序会报错。

数据类型和变量

计算机能处理的远不止数值，还可以处理文本、图形、音频、视频、网页等各种各样的数据，不同的数据，需要定义不同的数据类型。在 Python 中，能够直接处理的数据类型有以下几种：

2.1 整数
Python 可以处理任意大小的整数，当然包括负整数，在程序中的表示方法和数学上的写法一模一样，例如：1，100，-8080，0 等等。

计算机由于使用二进制，所以，有时候用十六进制表示整数比较方便，十六进制用 0x 前缀和 0-9，a-f 表示，例如：0xff00，0xa5b4c3d2，等等。

对于很大的数，例如 10000000000，很难数清楚 0 的个数。Python 允许在数字中间以_分隔，因此，写成 10_000_000_000 和 10000000000 是完全一样的。十六进制数也可以写成 0xa1b2_c3d4。

2.2 浮点数
浮点数也就是小数，之所以称为浮点数，是因为按照科学记数法表示时，一个浮点数的小数点位置是可变的，比如，1.23x109 和 12.3x108 是完全相等的。浮点数可以用数学写法，如 1.23，3.14，-9.01，等等。但是对于很大或很小的浮点数，就必须用科学计数法表示，把 10 用 e 替代，1.23x109 就是 1.23e9，或者 12.3e8，0.000012 可以写成 1.2e-5，等等。

整数和浮点数在计算机内部存储的方式是不同的，整数运算永远是精确的（除法难道也是精确的？是的！），而浮点数运算则可能会有四舍五入的误差。

2.3 字符串
字符串是以单引号' 或双引号"括起来的任意文本，比如 'abc'，"xyz"等等。请注意，'' 或"" 本身只是一种表示方式，不是字符串的一部分，因此，字符串 'abc' 只有 a，b，c 这 3 个字符。如果 ' 本身也是一个字符，那就可以用 ""括起来，比如"I'm OK" 包含的字符是 I，'，m，空格，O，K 这 6 个字符。

如果字符串内部既包含 ' 又包含 " 怎么办？可以用转义字符\ 来标识，比如：

Copy
'I\'m \"OK\"!'

表示的字符串内容是：
I'm "OK"!

转义字符 \ 可以转义很多字符，比如 \n 表示换行，\t 表示制表符，字符 \ 本身也要转义，所以 \\ 表示的字符就是 \

如果字符串里面有很多字符都需要转义，就需要加很多 \，为了简化，Python 还允许用 r'' 表示 '' 内部的字符串默认不转义

Copy
>>> print('\\\t\\')
\ \
>>> print(r'\\\t\\')
\\\t\\

如果字符串内部有很多换行，用 \n 写在一行里不好阅读，为了简化，Python 允许用 '''...''' 的格式表示多行内容

Copy
>>> print('''line1
... line2
... line3''')
line1
line2
line3

上面是在交互式命令行内输入，注意在输入多行内容时，提示符由 >>> 变为...，提示你可以接着上一行输入，注意... 是提示符，不是代码的一部分。当输入完结束符 ``` 和括号) 后，执行该语句并打印结果。

如果写成程序并存为.py 文件，就是：

Copy
print('''line1
line2
line3''')

多行字符串 '''...''' 还可以在前面加上 r 使用

Copy
print(r'''hello,\n
world''')

Copy
hello,\n
world

2.4 布尔值
布尔值和布尔代数的表示完全一致，一个布尔值只有True、False两种值，要么是 True，要么是 False，在 Python 中，可以直接用 True、False 表示布尔值（请注意大小写），也可以通过布尔运算计算出来：

Copy
>>> True
True
>>> False
False
>>> 3 > 2
True
>>> 3 > 5
False

布尔值可以用and、or 和 not运算。

and 运算是与运算，只有所有都为 True，and 运算结果才是 True

or 运算是或运算，只要其中有一个为 True，or 运算结果就是 True

not 运算是非运算，它是一个单目运算符，把 True 变成 False，False 变成 True

not 运算是非运算，它是一个单目运算符，把 True 变成 False，False 变成 True

2.5 空值
空值是 Python 里一个特殊的值，用None表示。None 不能理解为 0，因为 0 是有意义的，而 None 是一个特殊的空值。

此外，Python 还提供了列表、字典等多种数据类型，还允许创建自定义数据类型。

变量
变量在程序中就是用一个变量名表示了，变量名必须是大小写英文、数字和_的组合，且不能用数字开头。

在 Python 中，等号 = 是赋值语句，可以把任意数据类型赋值给变量，同一个变量可以反复赋值，而且可以是不同类型的变量

这种变量本身类型不固定的语言称之为动态语言，与之对应的是静态语言。静态语言在定义变量时必须指定变量类型，如果赋值的时候类型不匹配，就会报错。例如 Java 是静态语言，赋值语句如下（// 表示注释）：

Copy
int a = 123; // a是整数类型变量
a = "ABC"; // 错误：不能把字符串赋给整型变量

理解变量在计算机内存中的表示也非常重要。

Copy
a = 'ABC'

Python 解释器干了两件事情：

在内存中创建了一个 'ABC' 的字符串；

在内存中创建了一个名为 a 的变量，并把它指向 'ABC'。

Copy
a = 'ABC'
b = a
a = 'XYZ'
print(b)

执行 a = 'ABC'，解释器创建了字符串 'ABC' 和变量 a，并把 a 指向 'ABC'：

执行 b = a，解释器创建了变量 b，并把 b 指向 a 指向的字符串 'ABC'：

执行 a = 'XYZ'，解释器创建了字符串 'XYZ'，并把 a 的指向改为 'XYZ'，但 b 并没有更改：

所以，最后打印变量 b 的结果自然是 'ABC' 了。

常量
所谓常量就是不能变的变量，比如常用的数学常数 π 就是一个常量。在 Python 中，通常用全部大写的变量名表示常量：

Copy
PI = 3.14159265359

但事实上 PI 仍然是一个变量，Python 根本没有任何机制保证 PI 不会被改变，所以，用全部大写的变量名表示常量只是一个习惯上的用法，如果你一定要改变变量 PI 的值，也没人能拦住你。

解释一下整数的除法为什么也是精确的。在 Python 中，有两种除法，一种除法是 /：

Copy
>>> 10 / 3
3.3333333333333335

/ 除法计算结果是浮点数，即使是两个整数恰好整除，结果也是浮点数：

Copy
>>> 9 / 3
3.0

还有一种除法是 //，称为地板除，两个整数的除法仍然是整数：

Copy
>>> 10 // 3
3

你没有看错，整数的地板除 // 永远是整数，即使除不尽。要做精确的除法，使用 / 就可以。

因为 // 除法只取结果的整数部分，所以 Python 还提供一个余数运算 %，可以得到两个整数相除的余数：

Copy
>>> 10 % 3
1

无论整数做 // 除法还是取余数，结果永远是整数，所以，整数运算结果永远是精确的。

Python 支持多种数据类型，在计算机内部，可以把任何数据都看成一个 “对象”，而变量就是在程序中用来指向这些数据对象的，对变量赋值就是把数据和变量给关联起来。

对变量赋值 x = y 是把变量 x 指向真正的对象，该对象是变量 y 所指向的。随后对变量 y 的赋值不影响变量 x 的指向。

注意：Python 的整数没有大小限制，而某些语言的整数根据其存储长度是有大小限制的，例如 Java 对 32 位整数的范围限制在 - 2147483648-2147483647。

Python 的浮点数也没有大小限制，但是超出一定范围就直接表示为 inf（无限大）。

list 和 tuple

list
Python 内置的一种数据类型列表。list 是一种有序的集合，可以随时添加和删除其中的元素。

比如，列出班里所有同学的名字，就可以用一个 list 表示：

Copy
>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> classmates
['Michael', 'Bob', 'Tracy']

变量 classmates 就是一个 list。用 len () 函数可以获得 list 元素的个数：

Copy
>>> len(classmates)
3

用索引来访问 list 中每一个位置的元素，记得索引是从0开始

Copy
>>> classmates[0]
'Michael'
>>> classmates[1]
'Bob'
>>> classmates[2]
'Tracy'
>>> classmates[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range

当索引超出了范围时，Python 会报一个IndexError错误，所以，要确保索引不要越界，记得最后一个元素的索引是len(classmates) - 1。

如果要取最后一个元素，除了计算索引位置外，还可以用 - 1 做索引，直接获取最后一个元素：

Copy
>>> classmates[-1]
'Tracy'

list 是一个可变的有序表，所以，可以往 list 中追加元素到末尾：
list.append('')

Copy

>>> classmates.append('Adam')
>>> classmates
['Michael', 'Bob', 'Tracy', 'Adam']

list.insert(num,'')
也可以把元素插入到指定的位置，比如索引号为 1 的位置：

Copy
>>> classmates.insert(1, 'Jack')
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']

list.pop()
要删除 list 末尾的元素，用 pop () 方法：

Copy
>>> classmates.pop()
'Adam'
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy']

list.pop(i)
要删除指定位置的元素，用 pop (i) 方法，其中 i 是索引位置：

Copy
>>> classmates.pop(1)
'Jack'
>>> classmates
['Michael', 'Bob', 'Tracy']

要把某个元素替换成别的元素，可以直接赋值给对应的索引位置：

Copy
>>> classmates[1] = 'Sarah'
>>> classmates
['Michael', 'Sarah', 'Tracy']

list 里面的元素的数据类型也可以不同，比如：

Copy
>>> L = ['Apple', 123, True]

list 元素也可以是另一个 list，比如：

Copy
>>> s = ['python', 'java', ['asp', 'php'], 'scheme']
>>> len(s)
4

要注意 s 只有 4 个元素，其中 s [2] 又是一个 list，如果拆开写就更容易理解了：

Copy

>>> p = ['asp', 'php']
>>> s = ['python', 'java', p, 'scheme']

要拿到 'php' 可以写 p [1] 或者 s [2][1]，因此 s 可以看成是一个二维数组，类似的还有三维、四维…… 数组，不过很少用到。

如果一个 list 中一个元素也没有，就是一个空的 list，它的长度为 0：

Copy
>>> L = []
>>> len(L)
0

tuple
另一种有序列表叫元组。tuple 和 list 非常类似，但是tuple 一旦初始化就不能修改，比如同样是列出同学的名字：

Copy
>>> classmates = ('Michael', 'Bob', 'Tracy')

现在，classmates 这个 tuple 不能变了，它也没有 append ()，insert () 这样的方法。其他获取元素的方法和 list 是一样的，你可以正常地使用 classmates [0]，classmates [-1]，但不能赋值成另外的元素。

不可变的 tuple 有什么意义？因为 tuple 不可变，所以代码更安全。如果可能，能用 tuple 代替 list 就尽量用 tuple。

tuple 的陷阱：当你定义一个 tuple 时，在定义的时候，tuple 的元素就必须被确定下来，比如：

Copy
>>> t = (1, 2)
>>> t
(1, 2)

如果要定义一个空的 tuple，可以写成 ()：

Copy
>>> t = ()
>>> t
()

但是，要定义一个只有 1 个元素的 tuple，如果你这么定义：

Copy

>>> t = (1)
>>> t
1

定义的不是 tuple，是 1 这个数！这是因为括号 () 既可以表示 tuple，又可以表示数学公式中的小括号，这就产生了歧义，因此，Python 规定，这种情况下，按小括号进行计算，计算结果自然是 1。

所以，只有 1 个元素的 tuple 定义时必须加一个逗号, 来消除歧义：

Copy
>>> t = (1,)
>>> t
(1,)

Python 在显示只有 1 个元素的 tuple 时，也会加一个逗号，，以免你误解成数学计算意义上的括号。

最后来看一个 "可变的”tuple：

Copy
>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])

这个 tuple 定义的时候有 3 个元素，分别是 'a'，'b' 和一个 list。不是说 tuple 一旦定义后就不可变了吗？怎么后来又变了？

我们先看看定义的时候 tuple 包含的 3 个元素：

当我们把 list 的元素 'A' 和 'B' 修改为 'X' 和 'Y' 后，tuple 变为：

表面上看，tuple 的元素确实变了，但其实变的不是 tuple 的元素，而是 list 的元素。tuple 一开始指向的 list 并没有改成别的 list，所以，tuple 所谓的 “不变” 是说，tuple 的每个元素，指向永远不变。即指向 'a'，就不能改成指向 'b'，指向一个 list，就不能改成指向其他对象，但指向的这个 list 本身是可变的！

理解了 “指向不变” 后，要创建一个内容也不变的 tuple 怎么做？那就必须保证 tuple 的每一个元素本身也不能变。

list 和 tuple 是 Python 内置的有序集合，一个可变，一个不可变。

条件判断

据 Python 的缩进规则，如果 if 语句判断是 True，就把缩进的两行 print 语句执行。如果 if 判断是 False，去把 else 执行.
注意不要少写了冒号:

Copy
age = 3
if age >= 18:
print('your age is', age)
print('adult')
else:
print('your age is', age)
print('teenager')

elif 是 else if 的缩写，完全可以有多个 elif
if 语句执行特点：是从上往下判断，如果在某个判断上是 True，把该判断对应的语句执行后，就忽略掉剩下的 elif 和 else

Copy
if <条件判断1>:
<执行1>
elif <条件判断2>:
<执行2>
elif <条件判断3>:
<执行3>
else:
<执行4>

if 判断条件还可以简写，比如写：

Copy
if x:
print('True')

只要 x 是非零数值、非空字符串、非空 list 等，就判断为 True，否则为 False。

再议 input
TypeError

Copy
birth = input('birth: ')
if birth < 2000:
print('00前')
else:
print('00后')

输入 1982，结果报错：

Copy
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: str() > int()

这是因为 input () 返回的数据类型是 str，str 不能直接和整数比较，必须先把 str 转换成整数。Python 提供了int () 函数来完成这件事情

int () 函数发现一个字符串并不是合法的数字时就会报错，程序就退出了。

Copy
s = input('birth: ')
birth = int(s)
if birth < 2000:
print('00前')
else:
print('00后')

再次运行，就可以得到正确地结果。但是，如果输入 abc 呢？又会得到一个错误信息：
ValueError

Copy
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'abc'

循环

for...in 循环
Python 的循环有两种，一种是 for...in 循环，依次把 list 或 tuple 中的每个元素迭代出来

Copy
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)

执行这段代码，会依次打印 names 的每一个元素：

Copy
Michael
Bob
Tracy

所以 for x in ... 循环就是把每个元素代入变量 x，然后执行缩进块的语句。

range () 函数，可以生成一个整数序列，再通过 list () 函数可以转换为 list。比如 range (5) 生成的序列是从 0 开始小于 5 的整数：

Copy
>>> list(range(5))
[0, 1, 2, 3, 4]

while 循环
第二种循环是 while 循环，只要条件满足，就不断循环，条件不满足时退出循环

break
在循环中，break 语句可以提前退出循环

Copy
n = 1
while n <= 100:
if n > 10: # 当n = 11时，条件满足，执行break语句
break # break语句会结束当前循环
print(n)
n = n + 1
print('END')

执行上面的代码可以看到，打印出 1~10 后，紧接着打印 END，程序结束。

continue
在循环过程中，也可以通过 continue 语句，跳过当前的这次循环，直接开始下一次循环

Copy
n = 0
while n < 10:
n = n + 1
if n % 2 == 0: # 如果n是偶数，执行continue语句
continue # continue语句会直接继续下一轮循环，后续的print()语句不会执行
print(n)

break 语句可以在循环过程中直接退出循环，而 continue 语句可以提前结束本轮循环，并直接开始下一轮循环。这两个语句通常都必须配合 if 语句使用。

要特别注意，不要滥用 break 和 continue 语句。

“死循环”，也就是永远循环下去。这时可以用 Ctrl+C 退出程序，或者强制结束 Python 进程

dict 和 set

dict
Python 内置了字典：dict 的支持，dict 全称 dictionary，在其他语言中也称为 map，使用键 - 值（key-value）存储，具有极快的查找速度。

用 dict 实现，只需要一个 “名字”-“成绩” 的对照表，直接根据名字查找成绩，无论这个表有多大，查找速度都不会变慢。

Copy
>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
>>> d['Michael']
95

为什么 dict 查找速度这么快？因为 dict 的实现原理和查字典是一样的。假设字典包含了 1 万个汉字，我们要查某一个字，一个办法是把字典从第一页往后翻，直到找到我们想要的字为止，这种方法就是在 list 中查找元素的方法，list 越大，查找越慢。

第二种方法是先在字典的索引表里（比如部首表）查这个字对应的页码，然后直接翻到该页，找到这个字。无论找哪个字，这种查找速度都非常快，不会随着字典大小的增加而变慢。

dict 就是第二种实现方式，给定一个名字，比如 'Michael'，dict 在内部就可以直接计算出 Michael 对应的存放成绩的 “页码”，也就是 95 这个数字存放的内存地址，直接取出来，所以速度非常快。

你可以猜到，这种 key-value 存储方式，在放进去的时候，必须根据 key 算出 value 的存放位置，这样，取的时候才能根据 key 直接拿到 value。

把数据放入 dict 的方法，除了初始化时指定外，还可以通过 key 放入：

Copy
>>> d['Adam'] = 67
>>> d['Adam']
67

由于一个 key 只能对应一个 value，所以，多次对一个 key 放入 value，后面的值会把前面的值冲掉：

Copy
>>> d['Jack'] = 90
>>> d['Jack']
90
>>> d['Jack'] = 88
>>> d['Jack']
88

如果 key 不存在，dict 就会报错：KeyError

Copy
>>> d['Thomas']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'Thomas'

要避免 key 不存在的错误，有两种办法
一是通过in判断 key 是否存在：

Copy
>>> 'Thomas' in d
False

二是通过 dict 提供的get () 方法，如果 key 不存在，可以返回None，或者自己指定的 value：

Copy

>>> d.get('Thomas')
>>> d.get('Thomas', -1)
-1

注意：返回 None 的时候 Python 的交互环境不显示结果。

要删除一个 key，用pop (key) 方法，对应的 value 也会从 dict 中删除：

Copy
>>> d.pop('Bob')
75
>>> d
{'Michael': 95, 'Tracy': 85}

请务必注意，dict 内部存放的顺序和 key 放入的顺序是没有关系的。

和 list 比较，dict 有以下几个特点：
1 查找和插入的速度极快，不会随着 key 的增加而变慢；
2 需要占用大量的内存，内存浪费多。

而 list 相反：
1 查找和插入的时间随着元素的增加而增加；
2 占用空间小，浪费内存很少。
所以，dict 是用空间来换取时间的一种方法。

dict 可以用在需要高速查找的很多地方，在 Python 代码中几乎无处不在，正确使用 dict 非常重要，需要牢记的第一条就是dict 的 key 必须是不可变对象。

这是因为dict 根据 key 来计算 value 的存储位置，如果每次计算相同的 key 得出的结果不同，那 dict 内部就完全混乱了。这个通过 key 计算位置的算法称为哈希算法（Hash）

要保证 hash 的正确性，作为 key 的对象就不能变。在 Python 中，字符串、整数等都是不可变的，因此，可以放心地作为 key。而 list 是可变的，就不能作为 key：

Copy
>>> key = [1, 2, 3]
>>> d[key] = 'a list'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

set
set 和 dict 类似，也是一组 key 的集合，但不存储 value。由于 key 不能重复，所以，在 set 中，没有重复的 key。

要创建一个 set，需要提供一个 list 作为输入集合：

Copy
>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}

注意，传入的参数 [1, 2, 3] 是一个 list，而显示的 {1, 2, 3} 只是告诉你这个 set 内部有 1，2，3 这 3 个元素，显示的顺序也不表示 set 是有序的。。

重复元素在 set 中自动被过滤：

Copy
>>> s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3}

通过add (key) 方法可以添加元素到 set 中，可以重复添加，但不会有效果：

Copy
>>> s.add(4)
>>> s
{1, 2, 3, 4}
>>> s.add(4)
>>> s
{1, 2, 3, 4}

通过remove (key) 方法可以删除元素：

Copy
>>> s.remove(4)
>>> s
{1, 2, 3}

set 可以看成数学意义上的无序和无重复元素的集合，因此，两个 set 可以做数学意义上的交集、并集等操作：

Copy
>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}

set 和 dict 的唯一区别仅在于没有存储对应的 value，但是，set 的原理和 dict 一样，所以，同样不可以放入可变对象，因为无法判断两个可变对象是否相等，也就无法保证 set 内部 “不会有重复元素”。

再议不可变对象

对于可变对象，比如 list，对 list 进行操作，list 内部的内容是会变化的，比如：

Copy
>>> a = ['c', 'b', 'a']
>>> a.sort()
>>> a
['a', 'b', 'c']

而对于不可变对象，比如 str，对 str 进行操作呢：

Copy
>>> a = 'abc'
>>> a.replace('a', 'A')
'Abc'
>>> a
'abc'

虽然字符串有个 replace () 方法，也确实变出了 'Abc'，但变量 a 最后仍是 'abc'，应该怎么理解呢？

我们先把代码改成下面这样：

Copy
>>> a = 'abc'
>>> b = a.replace('a', 'A')
>>> b
'Abc'
>>> a
'abc'

要始终牢记的是，a 是变量，而 'abc' 才是字符串对象！有些时候，我们经常说，对象 a 的内容是 'abc'，但其实是指，a 本身是一个变量，它指向的对象的内容才是 'abc'：

a-------->'abc'

当我们调用 a.replace ('a', 'A') 时，实际上调用方法 replace 是作用在字符串对象 'abc' 上的，而这个方法虽然名字叫 replace，但却没有改变字符串 'abc' 的内容。相反，replace 方法创建了一个新字符串 'Abc' 并返回，如果我们用变量 b 指向该新字符串，就容易理解了，变量 a 仍指向原有的字符串 'abc'，但变量 b 却指向新字符串 'Abc' 了：
a-------->'abc'
b-------->'Abc'

所以，对于不变对象来说，调用对象自身的任意方法，也不会改变该对象自身的内容。相反，这些方法会创建新的对象并返回，这样，就保证了不可变对象本身永远是不可变的。

使用 key-value 存储结构的 dict 在 Python 中非常有用，选择不可变对象作为 key 很重要，最常用的 key 是字符串。