ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
计算机其实就是可以做数学计算的机器,所以计算机程序当然可以处理各种数值。任何一种语言最重要的就是他的数据结构。 在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 ```