计算机其实就是可以做数学计算的机器,所以计算机程序当然可以处理各种数值。任何一种语言最重要的就是他的数据结构。
在Python 里面常用的数据类型有
- 基本数据类型:包含整数、浮点数、布尔值,可以看成是单独的数据
- 容器数据类型:单独的数据放到一个容器里面就可以得到**容器类型**的数据,比如说字符串、元组、列表、字典、集合
> 可以使用 `dir(int)`这种类型的函数查看对象中可用的属性和方法
# 基本数据类型
## 整数
## 浮点数
整数加上一个小数点,就可以创建float
## 布尔值
对与数值变量,0,0.0都可以认为是空的,对于容器变量,里面没有元素就可以认为是空的。
## 空值
空值是 Python 里一个特殊的值,用 None 表示。None 不能理解为 0,因为 0 是有意义的,而 None 是一个特殊的空值。
# 容器数据类型
## 字符串
### 创建字符串
字符串是以单引号' 或双引号 " 括起来的任意文本,比如'abc',"xyz" 等等。
注意,'' 或 "" 本身只是一种表示方式,不是字符串的一部分,因此,字符串'abc' 只有 a,b,c 这 3 个字符。
如果' 本身也是一个字符,那就可以用 "" 括起,比如 `"I'm OK"`
如果字符串内部既包含' 又包含 " 怎么办?可以用转义字符 \ 来标识,比如
```python
'I\'m \"OK\"'
```
如果字符串里面有很多字符都需要转义,就需要加很多 \,为了简化,Python 还允许用 r'' 表示'' 内部的字符串默认不转义
|转义字符|描述|
|:--|:--|
|\(在行尾时)|续行符|
|\\|反斜杠符号|
|\',\"|引号|
|\n|换行|
|\r|回车|
|\oyy|八进制数,yy 代表的字符,例如:\o12 代表换行,其中 o 是字母,不是数字 0。|
|\xyy|十六进制数,yy 代表的字符,例如:\x0a 代表换行|
```python
print (r'\\\t\\')
```
如果有很多换行,可以使用 `'''...'''`表示多行内容
```python
print ('''line1
line2
line3''')
```
多行字符串'''...''' 还可以在前面加上 r 使用
### 字符串编码
我们知道计算机只能处理数字,如果要处理文本,需要将文本转换成数字。
最早的计算机在设计时采用 8 个比特(bit)作为一个字节(byte),所以,一个字节能表示的最大的整数就是 255(二进制 11111111 = 十进制 255),如果要表示更大的整数,就必须用更多的字节。
由于计算机是美国人发明的,因此,最早只有 127 个字符被编码到计算机里,也就是大小写英文字母、数字和一些符号,这个编码表被称为 ASCII 编码,比如大写字母 A 的编码是 65,小写字母 z 的编码是 122。
但是要处理中文显然一个字节是不够的,至少需要两个字节,而且还不能和 ASCII 编码冲突,所以,中国制定了 GB2312 编码,用来把中文编进去。
于是各国有各国的标准,不可避免的会出现冲突。
所以Unicode应运而生,可以将所有的语言统一到一套编码。
那么ASCII 编码和 Unicode 编码的主要区别在于:ASCII 编码是 1 个字节,而 Unicode 编码通常是 2 个字节。
不过新的问题在于,如果统一成 Unicode 编码,乱码问题从此消失了。但是,如果你写的文本基本上全部是英文的话,用 Unicode 编码比 ASCII 编码需要多一倍的存储空间,在存储和传输上就十分不划算。
又出现了把 Unicode 编码转化为 “可变长编码” 的 UTF-8 编码。
UTF-8 编码把一个 Unicode 字符根据不同的数字大小编码成 1-6 个字节,常用的英文字母被编码成 1 个字节,汉字通常是 3 个字节,只有很生僻的字符才会被编码成 4-6 个字节。如果你要传输的文本包含大量英文字符,用 UTF-8 编码就能节省空间:
|字符|ASCII|Unicode|UTF-8|
|:--|:--|:--|:--|
|A|1000001|00000000 01000001|1000001|
|中|x|01001110 00101101|11100100 10111000 10101101|
搞清楚了 ASCII、Unicode 和 UTF-8 的关系,我们就可以总结一下现在计算机系统通用的字符编码工作方式:
- 在计算机内存中,统一使用 Unicode 编码,当需要保存到硬盘或者需要传输的时候,就转换为 UTF-8 编码。
- 用记事本编辑的时候,从文件读取的 UTF-8 字符被转换为 Unicode 字符到内存里,编辑完成后,保存的时候再把 Unicode 转换为 UTF-8 保存到文件:
![UTOOLS1565000217270.png](http://yanxuan.nosdn.127.net/634116ad1d1c0eff5accf581e31be952.png)
浏览网页的时候,服务器会把动态生成的 Unicode 内容转换为 UTF-8 再传输到浏览器:
![UTOOLS1565000232673.png](http://yanxuan.nosdn.127.net/42723fdf97c51d9d5a936b247b6149e9.png)
### encode,decode
最新的 Python 3 版本中,字符串是以 Unicode 编码的。
也就是说,Python 的字符串支持多语言,例如:
```python
>>> print('包含中文的str')
```
由于 Python 的字符串类型是 str,在内存中以 Unicode 表示,一个字符对应若干个字节。
如果要在网络上传输,或者保存到磁盘上,就需要把 str 变为以**字节**为单位的 bytes。
Python 对 bytes 类型的数据用带 b 前缀的单引号或双引号表示,要注意区分'ABC' 和 b'ABC',前者是 str,后者虽然内容显示得和前者一样,但 bytes 的每个字符都只占用一个字节。
str可以通过 `encode()`编码为指定的 `bytes`
比如
```python
'ABC'.encode('ascii')
'中文'.encode("utf-8")
```
> 含有中文的 str 无法用 ASCII 编码,因为中文编码的范围超过了 ASCII 编码的范围,Python 会报错。
如果我们从网络或磁盘上读取了字节流,那么读到的数据就是 bytes。要把 bytes 变为 str,就需要用 decode() 方法
```python
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'
```
如果 bytes 中只有一小部分无效的字节,可以传入 errors='ignore' 忽略错误的字节:
当 Python 解释器读取源代码时,为了让它按 UTF-8 编码读取,我们通常在文件开头写上这两行:
```python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
```
### 格式化
一个常见的问题是如何输出格式化的字符串
```python
>>> 'Hi, %s, you have $%d.' % ('Michael', 1000000)
```
% 运算符就是用来格式化字符串的。在字符串内部,%s 表示用字符串替换,%d 表示用整数替换,有几个 %? 占位符,后面就跟几个变量或者值,顺序要对应好。如果只有一个 %?,括号可以省略。
如果你不太确定应该用什么,%s 永远起作用,它会把任何数据类型转换为字符串
有些时候,字符串里面的 % 是一个普通字符怎么办?这个时候就需要转义,用 %% 来表示一个 %
另一种格式化字符串的方法是使用字符串的 format() 方法,它会用传入的参数依次替换字符串内的占位符 {0}、{1}……,不过这种方式写起来比 % 要麻烦得多:
```python
>>> 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.1%'
```
### 索引和切片
Python 里面索引有三个特点
- 从 0 开始 ,不像 Matlab 从 1 开始。
- 切片通常写成 start:end 这种形式,包括「start 索引」对应的元素,但是不包括「end 索引」对应的元素。比如 s [2:4] 只获取字符串第 3 个到第 4 个元素。
- 索引值可正可负,正索引从 0 开始,从左往右;负索引从 -1 开始,从右往左。使用负数索引的时候,会从最后一个元素开始计数,最后一个元素的位置编号是-1
技巧:可以想象将元素分开的隔栏,显然 6 个元素需要 7 个隔栏,隔栏索引也是从 0 开始,这样再看到 start:end 就认为是隔栏索引,那么获取的元素就是「隔栏 start」和「隔栏 end」之间包含的元素。
- string [2:4] 就是「隔栏 2」和「隔栏 4」之间包含的元素,即 th
- string [-5:-2] 就是「隔栏 -5」和「隔栏 -2」之间包含的元素,即 yth
![UTOOLS1564498813003.png](http://yanxuan.nosdn.127.net/e71ae17190753798728c56b9d52a0783.png)
### 正则表达式
正则表达式 (regular expression) 主要用于识别字符串中符合某种模式的部分
```bash
input = """
'06/18/2019 13:00:00', 100, '1st';
'06/18/2019 13:30:00', 110, '2nd';
'06/18/2019 14:00:00', 120, '3rd'
"""
```
我们用下面这样的模式
```bash
pattern = re.compile("'[0-9/:\s]+'")
```
里面符号的意思如下:
- 最外面的两个单引号 ' 代表该模式以它们开始和结束
- 中括号 [] 用来概括该模式涵盖的所有类型的字节
- 0-9 代表数字类的字节
- / 代表正斜线
- : 代表分号
- \s 代表空格
- [] 外面的加号 + 代表 [] 里面的字节出现至少 1 次
然后就可以把所有符合pattern的日期表达式都找出来
```bash
pattern.findall(input)
["'06/18/2019 13:00:00'",
"'06/18/2019 13:30:00'",
"'06/18/2019 14:00:00'"]
```
接下来就可以把 / 换成 -,或者用 datetime 里面的 striptime () 把日期里年、月、日、小时、分钟和秒都获取出来。
### 字符串运算符
|操作符|描述|实例|
|:--|:--|:--|
|+|字符串连接|a + b 输出结果: HelloPython|
|*|重复输出字符串|a*2 输出结果:HelloHello|
|[ : ]|截取字符串中的一部分,遵循左闭右开原则,str [0,2] 是不包含第 3 个字符的。|a [1:4] 输出结果 ell|
|in,not in|成员运算符|H' in a 输出结果 True|
### 内建函数
#### len
要计算 str 包含多少个字符,可以用 len() 函数:
```python
>>> len('中文')
2
```
如果 len()里面传入的是 `bytes`,将计算字节数
```python
>>> len('中文'.encode('utf-8'))
6
# 中文将被编码为 b'\xe4\xb8\xad\xe6\x96\x87'
```
- capitalize ():大写句首的字母
- split ():把句子分成单词
- find (x):找到给定词 x 在句中的索引,找不到返回 -1
- replace (x, y):把句中 x 替代成 y
- strip (x):删除句首或句末含 x 的部分
比如说我们提取到字符类型的值之后,一般会使用 x.strip(" ")去除空格
|方法|描述|类型|实例|
|:--|:--|:--|:--|
|capitialize()|首字母大写|替换||
|expandtabs(tabsize=8)|将tab转为空格,默认空格数为8|替换||
|lower(),upper()|转换为小写|替换||
|replace(old, new [, max])|把 将字符串中的 str1 替换成 str2, 如果 max 指定,则替换不超过 max 次。|替换||
|count(str,beg=0,end=len(string))|返回str在string里面的次数,beg和end用于指定特定的范围|判断|str.count('run', 0, 10)|
|endswith(suffix,beg=0,end=(len(string))|检查字符串是否以 obj 结束|判断|Str.endswith(suffix, 0, 19)|
|isalnum(),isalpha(),isspace()|isalnum()表示至少有一个字符,并且所有字符都是字母或者数字,isalpha()表示至少有一个字符,并且所有字符都是字母|判断||
|isdigit(),isnumeric(),isdecimal()|只包含数字或者数字字符,则返回True|判断||
|islower(),isupper()|所有这些 (区分大小写的) 字符都是小写|判断||
|lstrip(),rstrip()|截掉字符串左边的空格或指定字符。|判断||
|startswith(substr, beg=0,end=len(string))|检查字符串是否是以指定子字符串 substr 开头,|判断||
|rfind(str, beg=0,end=len(string))|从右边开找|查找||
|find(str,beg=0,end=(len(string))|检测 str 是否包含在字符串中,若包含,则返回开始的索引值,否则返回-1|查找|str1.find(str2, 10)|
|index(str, beg=0, end=len(string))|跟 find () 方法一样,但是如果 str 不在字符串中会报一个异常.|查找||
|max(str)|返回字符串 str 中最大的字母。|查找||
|swapcase()|将字符串中大写转换为小写,小写转换为大写|转换||
|join(seq)|以指定字符串作为分隔符,将 seq 中所有的元素 合并|重组||
|split(str="", num=string.count(str))|num=string.count (str)) 以 str 为分隔符截取字符串,如果 num 有指定值,则仅截取 num+1 个子字符串|重组||
|splitlines([keepends])|按照行 ('\r', '\r\n', \n') 分隔,返回一个包含各行作为元素的列表,如果参数 keepends 为 False,不包含换行符,如果为 True,则保留换行符。|重组||
|bytes.decode(encoding="utf-8", errors="strict")|Python3 中没有 decode 方法,但我们可以使用 bytes 对象的 decode () 方法来解码给定的 bytes 对象,这个 bytes 对象可以由 str.encode () 来编码返回。|编码||
|encode(encoding='UTF-8',errors='strict')|以 encoding 指定的编码格式编码字符串|编码||
## 变量
变量在程序中就是用一个变量名表示了,变量名必须是大小写英文、数字和_的组合,且不能用数字开头
在 Python 中,等号 = 是赋值语句,可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量
这种变量本身类型不固定的语言称之为 **动态语言**,与之对应的是静态语言。
当我们写a = 'ABC'
Python的解释器做了两件事:
- 在内存中,创建 'ABC'字符串
- 在内存中创建了一个名为 `a`的变量,并指向 `'ABC'`
比如如下代码
```python
a = 'ABC'
b = a
a = 'XYZ'
print(b)
```
执行 a = 'ABC',解释器创建了字符串'ABC' 和变量 a,并把 a 指向'ABC':
![UTOOLS1564999810017.png](http://yanxuan.nosdn.127.net/24c103639d3ff307994f491c7abd74b2.png)
执行 b = a,解释器创建了变量 b,并把 b 指向 a 指向的字符串'ABC':
![UTOOLS1564999818331.png](http://yanxuan.nosdn.127.net/8e22164ccbc9f71691feee39015a58f8.png)
执行 a = 'XYZ',解释器创建了字符串 'XYZ',并把 a 的指向改为'XYZ',但 b 并没有更改:
![UTOOLS1564999825351.png](http://yanxuan.nosdn.127.net/6bf123287b90da6a04ca8a0a18b43024.png)
所以,最后打印变量 b 的结果自然是'ABC' 了。
## list
list是一种有序的集合,可以随时添加和删除其中的元素。
### 创建list
```python
classmates = ['Michael', 'Bob', 'Tracy']
```
list元素也可以是另外一个list
```python
s = ['python', 'java', ['asp', 'php'], 'scheme']
```
注意s只有4个元素,其中 `s[2]`又是一个list。
### 索引和切片
```python
classmates[-3]
```
L[0:3] 表示,从索引 0 开始取,直到索引 3 为止,但不包括索引 3。即索引 0,1,2,正好是 3 个元素。
如果第一个索引是 0,还可以省略
- 取前10个数: L[:10]
- 后10个数:L[-10:]
- 前11~20个数:L[10:20]
- 所有数每5个取一个:L[::5]
### 内置方法
- 追加: classmates.append('Adam')
- 插入到指定的位置: classmates.insert(1,'Jack')
- 删除list末尾的元素:classmates.pop()
- 更新或者替换元素: classmates[1] = 'Sarah'
### 迭代
我们可以通过 `for`循环来遍历list或者tuple
Python主要使用 `for ... in `来完成迭代,而Java是使用下标完成
所以Python的抽象程度高于java的for循环。
for循环不仅可以作用于list上,还可以用在其他可迭代的对象里面。
如果要实现类似于Java那样的下标循环,可以使用 `enumerate`把一个list变成索引-元素对
```python
for i , value in enumerate (['A','B','C']):
print (i , value)
```
### 列表生成式
如果要生成从1 到10的列表可以使用
```python
list(range(1, 11))
```
如果要生成 [1x1, 2x2, 3x3, ..., 10x10] 怎么做?
```python
[x * x for x in range(1, 11)]
```
还可以加上 if 判断,这样我们就可以筛选出仅偶数的平方:
```python
>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]
```
还可以使用两层循环,可以生成全排列:
```python
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
```
还可以通过一行代码列出当前目录下的所有文件和目录名
```python
import os
[d for d in os.listdir('.')]
```
把一个 list 中所有的字符串变成小写:
```python
>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
```
### 生成器
通过列表生成式倒是可以直接创建一个列表,但是如果列表特别的大,需要占用的存储空间自然非常多。
所以能不能让列表元素通过某种算法推算出来。
在 Python 中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,第一种方法就是把一个列表生成式的 [] 改成 (),就创建了一个 generator
```python
g = (x*x for x in range(10))
```
那么如何把下一个返回值打印出来
```python
next (g)
```
generator 保存的是算法,每次调用 next(g),就计算出 g 的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration 的错误。
其实正确的方法应该是通过 `for`循环,因为generator也是可迭代对象
```python
g = (x * x for x in range(10))
for n in g :
print (n)
```
比如Fibonacci数列,除第一个和第二个数外,任意一个数都可以由前两个数相加得到
```python
def fib(max):
n , a , b = 0 , 0 , 1
while n < max:
print (b)
a , b = b , a + b
n = n + 1
return 'done'
```
注意,赋值语句:
a, b = b, a + b
相当于:
```python
t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1
```
可以看出,fib 函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似 generator。
要把fib函数变成generator,只需要把 print(b)改为 yield b
```python
def fib (max):
n , a , b = 0 , 0 , 1
while n < max:
yield b
a , b = b , a + b
n = n + 1
return 'done'
```
如果一个函数定义中包含 yield 关键字,那么这个函数就不再是一个普通函数,而是一个 generator:
generator 和函数的执行流程不一样。函数是顺序执行,遇到 return 语句或者最后一行函数语句就返回。
而变成 generator 的函数,在每次调用 next() 的时候执行,遇到 yield 语句返回,再次执行时从上次返回的 yield 语句处继续执行。
举个简单的例子,定义一个 generator,依次返回数字 1,3,5:
```python
def odd():
print('step 1')
yield 1
print('step 2')
yield(3)
print('step 3')
yield(5)
```
调用该generator时,首先要生成一个generator对象,然后用next()获得下一个返回值
```python
o = odd()
next(o)
```
可以看到,odd 不是普通函数,而是 generator,在执行过程中,遇到 yield 就中断,下次又继续执行。执行 3 次 yield 后,已经没有 yield 可以执行了,所以,第 4 次调用 next(o) 就报错。
同样的,把函数改成 generator 后,我们基本上从来不会用 next() 来获取下一个返回值,而是直接使用 for 循环来迭代
## tuple
另外一种有序列表叫元组:tuple
tuple与list非常相似,但是tuple一旦初始化不能修改
```python
>>> classmates = ('Michael', 'Bob', 'Tracy')
```
不过tuple不能改变,所以没有 append,insert这样的方法
不可变的tuple有什么意义呢?
因为tuple不可变,所以代码更安全。
需要注意的是,当定义一个tuple的时候,其元素就必须确定下来。
如果要定义只有一个元素的tuple需要
```python
t = (1,)
```
因为括号 () 既可以表示 tuple,又可以表示数学公式中的
如果tuple中有一个元素是列表,这个列表其实是可以赋值的,。
```python
>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])
```
如下是tuple包含的三个元素:
![UTOOLS1565188565434.png](http://yanxuan.nosdn.127.net/c74b880b6277625c1e609b558e327885.png)
如果把 A和B修改为X和Y,tuple变为
![UTOOLS1565188592643.png](http://yanxuan.nosdn.127.net/2ee6edc6e2e23ad50e7f7d52586c1b0f.png)
表面上看, tuple的元素确实变了,但是变的其实不是tuple的元素,而是list的元素。
所谓tuple不变,指的是tuple的每个元素,指向永远的变。但是指向的这个list本身是可变的。
## dict
dict使用 key -value这种键值对进行存储,查找速度极快。
为什么dict查找速度这么快?
假设我们正在查字典,如果要查某一个汉字,第一种方法是从第一页往后面翻,直到找到,这就是list查找元素的方法。
第二种方法是建立一个索引表,查这个字对应的页码,然后直接翻到相应的页数。
这样无论是找哪一个字,速度都非常快,而且还不会随着字典大小增加而增加。
dict就是采用的第二种方法,现在给定一个名字,就可以在内部算出这个名字对应的页码,也就是存放数值的内存地址。速度自然非常块。
同样,在放入数据的时候,也需要根据key算出value存放的位置。这样才能根据key直接拿到value
### 创建dict
可以使用两 种方法创建dict
- 初始化的时候指定
```python
d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
```
- 通过key放入
```python
>>> d['Adam'] = 67
```
如果 key 不存在,dict 就会报错,为了避免这个问题,可以使用 `in`来进行判断
```python
'Thomas' in d
```
或者通过 dict 提供的 get() 方法
```python
d.get("Thomas")
```
如果不存在会返回None
> 注意:返回 None 的时候 Python 的交互环境不显示结果。
### 删除key
使用 `pop(key)`可以删除key,对应的value也会删除掉
### 注意
值得注意的是,dict内部存放的顺序与key放入的顺序没有关系。
### 与list对比
和list相对比,dict具有如下特点
- 插入和查找的速度极快,而且不会随着key增加而变慢
- 需要占用大量的内存,比较浪费
所以说dict是用空间来换时间的方法。
需要牢记一条就是dict的key必须是**不可变的对象**
因为dict需要根据key来算value存的地方,如果每次key不同,算出来的地址自然就乱了。
在 Python 中,字符串、整数等都是不可变的,
### 迭代
#### 迭代key
```python
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> for key in d:
... print(key)
...
a
c
b
```
因为 dict 的存储不是按照 list 的方式顺序排列,所以,迭代出的结果顺序很可能不一样。
#### 迭代 value
默认情况下,dict 迭代的是 key。如果要迭代 value,可以用 for value in d.values(),如果要同时迭代 key 和 value,可以用 for k, v in d.items()。
## set
set 和 dict 类似,也是一组 key 的集合,但不存储 value。由于 key 不能重复,所以,在 set 中,没有重复的 key。
### 创建
要创建一个set,需要提供一个list作为输入集合
```python
s = set([1, 2, 3])
```
注意,传入的参数 [1, 2, 3] 是一个 list,而显示的 {1, 2, 3} 只是告诉你这个 set 内部有 1,2,3 这 3 个元素,显示的顺序也不表示 set 是有序的。。
重复元素在 set 中自动被过滤:
```js
>>> s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3}
```
### 添加删除元素
通过 add(key) 方法可以添加元素到 set 中
通过 remove(key) 方法可以删除元素:
set 可以看成数学意义上的无序和无重复元素的集合,因此,两个 set 可以做数学意义上的交集、并集等操作:
```js
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}
```
## 不可变对象
上面我们讲了,str 是不变对象,而 list 是可变对象。
对于可变对象,比如 list,对 list 进行操作,list 内部的内容是会变化的
而对于不可变对象,比如 str,对 str 进行操作呢?虽然字符串有个 replace() 方法,也确实变出了'Abc',但变量 a 最后仍是'abc',应该怎么理解呢?
```js
>>> a = 'abc'
>>> b = a.replace('a', 'A')
>>> b
'Abc'
>>> a
'abc'
```
对象 a 的内容是'abc',但其实是指,a 本身是一个变量,它指向的对象的内容才是'abc'
当我们调用 a.replace('a', 'A') 时,实际上调用方法 replace 是作用在字符串对象'abc' 上的,而这个方法虽然名字叫 replace,但却没有改变字符串'abc' 的内容。
相反,replace 方法创建了一个新字符串'Abc' 并返回,如果我们用变量 b 指向该新字符串,就容易理解了,变量 a 仍指向原有的字符串'abc',但变量 b 却指向新字符串'Abc' 了:
![UTOOLS1565226020788.png](http://yanxuan.nosdn.127.net/ffaed6a12f015b4495888a7262d4c0b4.png)
> 对于不变对象来说,调用对象自身的任意方法,**也不会**改变该对象自身的内容。相反,这些方法会**创建新的对象**并返回,这样,就保证了不可变对象本身永远是不可变的。
# 补充
## 可迭代对象
如何判断一个对象是可迭代对象呢?方法是通过 collections 模块的 Iterable 类型判断:
```python
>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False
```
如果要对 list 实现类似 Java 那样的下标循环怎么办?
Python 内置的 enumerate 函数可以把一个 list 变成索引 - 元素对,这样就可以在 for 循环中同时迭代索引和元素本身:
```python
>>> for i, value in enumerate(['A', 'B', 'C']):
... print(i, value)
...
0 A
1 B
2 C
```
上面的 for 循环里,同时引用了两个变量,在 Python 里是很常见的,比如下面的代码:
```python
>>> for x, y in [(1, 1), (2, 4), (3, 9)]:
... print(x, y)
...
1 1
2 4
3 9
```
## 迭代器
可以直接作用于 for 循环的数据类型有以下几种:
一类是集合数据类型,如 list、tuple、dict、set、str 等;
一类是 generator,包括生成器和带 yield 的 generator function。
这些可以直接作用于 for 循环的对象统称为可迭代对象:Iterable。
可以使用 isinstance() 判断一个对象是否是 Iterable 对象:
```python
from collections import Iterable
isinstance ([], Iterable)
```
而生成器不但可以作用于 for 循环,还可以被 next() 函数不断调用并返回下一个值,直到最后抛出 StopIteration 错误表示无法继续返回下一个值了。
> 可以被 next() 函数调用并不断返回下一个值的对象称为迭代器:Iterator。
可以使用 isinstance() 判断一个对象是否是 Iterator 对象:
```python
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False
```
list、dict、str 虽然是 Iterable,却不是 Iterator。
生成器都是 Iterator对象
把 list、dict、str 等 Iterable 变成 Iterator 可以使用 iter() 函数
```python
>>> isinstance(iter([]), Iterator)
True
```
为什么 list、dict、str 等数据类型不是 Iterator?
这是因为 Python 的 Iterator 对象表示的是一个数据流,Iterator 对象可以被 next() 函数调用并不断返回下一个数据,直到没有数据时抛出 StopIteration 错误。
可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过 next() 函数实现按需计算下一个数据,所以 Iterator 的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator 甚至可以表示一个无限大的数据流,例如全体自然数。而使用 list 是永远不可能存储全体自然数的。
凡是可作用于 for 循环的对象都是 Iterable 类型;
凡是可作用于 next() 函数的对象都是 Iterator 类型,它们表示一个惰性计算的序列;
集合数据类型如 list、dict、str 等是 Iterable 但不是 Iterator,不过可以通过 iter() 函数获得一个 Iterator 对象。
Python 的 for 循环本质上就是通过不断调用 next() 函数实现的,例如:
```python
for x in [1, 2, 3, 4, 5]:
pass
```
实际上完全等价于:
```python
# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break
```