在了解Python的数据结构时,容器(container)、可迭代对象(iterable)、迭代器(iterator)、生成器(generator)、列表/集合/字典推导式(list,set,dict comprehension)众多概念参杂在一起,难免让初学者一头雾水,我将用一篇文章试图将这些概念以及它们之间的关系捋清楚
容器(container)
容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用 in , not in 关键字判断元素是否包含在容器中。通常这类数据结构把所有的元素存储在内存中(也有一些特列并不是所有的元素都放在内存)在Python中,常见的容器对象有:
list, deque, ....
set, frozensets, ....
dict, defaultdict, OrderedDict, Counter, ....
tuple, namedtuple, …
str
容器比较容易理解,因为你就可以把它看作是一个盒子、一栋房子、一个柜子,里面可以塞任何东西。从技术角度来说,当它可以用来询问某个元素是否包含在其中时,那么这个对象就可以认为是一个容器,比如 list,set,tuples都是容器对象:
尽管绝大多数容器都提供了某种方式来获取其中的每一个元素,但这并不是容器本身提供的能力,而是 可迭代对象 赋予了容器这种能力,当然并不是所有的容器都是可迭代的。
迭代器 Iterator:
可迭代对象:list tuple dict set frozenset str range() reversed() map() sorted() filter();
迭代器是访问可迭代对象的工具(对象);
什么是可迭代对象? (拥有iter方法的对象 为 可迭代对象)。
1、什么是迭代器:
迭代器是指用iter(obj)函数返回的对象(实例)
迭代器可以用next(it) 函数获取可迭代对象的数据;
满足以上两个条件:1.有iter方法,有next方法。
2、迭代器相关函数:
iter(iterable) 从可迭代对象中返回一个迭代器,iterable必须是一个能提供一个迭代器的对象;
next(iterator) 从迭代器iterator中获取下一个记录,如果无法获取下一条记录,则触发stopIteration异常通知;
说明:迭代器只能往前取值,不能后退;
示例:
L= [2, 3, 4, 5, 7]
it= iter(L) #让可迭代对象L提供一个迭代器
next(it) #2
next(it) #3
next(it) #4
next(it) #5
next(it) #7
next(it) #StopIteration 异常通知
#此示意用while语句和迭代器来访问列表
L = [2, 3, 5, 7]
it=iter(L)whileTrue:try:
x=next(it)exceptStopIteration:break
#for 循环内部三件事
# 1.调用可迭代对象的iter方法 返回一个迭代器对象;
# 2.不断调用迭代器对象的next方法;
# 3.处理StopIteration异常;
生成器 Generator:
1、什么是生成器:
生成器是能够动态提供数据的对象,生成器对象也是可迭代对象;
生成器有两种:
1.生成器函数
2.生成器表达式 eg: s=(x*2 for x in range(10000000))
2、生成器函数定义:
含有yield语句的函数是生成器函数,此函数被调用将返回一个生成器对象;
注:yield翻译为(产生或生成)
yield语句
语法:
yield 表达式
说明:
1.yield 用于def 函数中,目的是将此函数作为生成器函数使用;
2.yield 用来生成数据,供迭代器的next(it) 函数使用;
3、生成器函数说明:
1.生成器函数的调用将返回一个生成器对象,生成器对象是可迭代对象;
2.生成器函数调用return 会触发一个StopIteration异常;
#yield.py#此示例示意函数yield 语句的生成器函数定义方式和用法
defmyyield():'''此函数因含有yield语句,所以是生成器函数'''
print('即将生成1')yield 1 #等同于return 1
print('即将生成3')yield 3
print('即将生成5')yield 5
print('即将生成7')yield 7
print('即将生成结束')
gen= myyield() #gen绑定的是生成器,生成器是可迭代对象
it = iter(gen) #返回迭代器
x= next(it) #next()语句调用时才开始执行生成器函数的语句(如print),直到遇见yield语句停止,我待下次next()语句调用;
print(x)print(x)print(next(it))print(next(it))print(next(it))print(next(it))
1 #斐波那契数列
2 #0 1 1 2 3 5 8 13 21
3 deffib(max):4 n,before,after = 0, 0, 1
5 while n <6>
7 yieldbefore8 before,after = after,before+after #注意此处赋值是先进行右边相加 再赋值
9 n += 1
10
11 g=fib(8)12 print(g)13 print(next(g))14 print(next(g))15 print(next(g))16 print(next(g))17 print(next(g))
还可通过yield实现在单线程的情况下实现伪并发运算的效果
1 #_*_coding:utf-8_*_
2 __author__ = 'Alex Li'
3
4 importtime5 defconsumer(name):6 print("%s 准备吃包子啦!" %name)7 whileTrue:8 baozi = yield
9
10 print("包子[%s]来了,被[%s]吃了!" %(baozi,name))11
12
13 defproducer(name):14 c = consumer('A')15 c2 = consumer('B')16 c.__next__()17 c2.__next__()18 print("alex开始准备做包子啦!")19 for i in range(10):20 time.sleep(1)21 print("做了2个包子!")22 c.send(i)23 c2.send(i)24
25 producer("alex")
4、生成器表达式:
语法:
(表达式 for 变量 in 可迭代对象 [if 真值表达式])
说明:
if 子句可以省略
作用:
用推导式的形式生成一个新的生成器;
示例:
gen= (x ** 2 for x in range(1, 5))
it=iter(gen)
next(it)#1
next(it) #4
next(it) #9
next(it) #16
>>> (x ** 2 for x in range(1, 5)) #注意 生成器数据是现用,现生成
at 0x00000222A1C796D8>
>>>示例:
L= [2, 3, 5, 7]
Lst= [x + 1 for x in L] #列表推导式 或 列表生成式
it =iter(Lst)print('1',next(it)) #3
L[1] = 30
print(L)print('2',next(it)) #4
L= [2, 3, 5, 7]
Lst= (x + 1 for x in L) #生成器表达式,不存储数据
it =iter(Lst)print('3',next(it)) #3
L[1] = 30
print(L)print('4',next(it)) #31
迭代工具函数:
作用:
生成一个个性化的可迭代对象
函数名:
zip(iter1, iter2, iter3, .....) 返回一个zip对象,此对象用于生成一个元组,此元组中的元素分别由iter1,iter2可迭代对象中的元组构成(元组个数由最小的可迭代对象决定);
enumerate(iterable, start=0) 生成带索引的枚举对象,返回的迭代类型为索引-值对(index-value)对,默认索引从零开始,也可用start指定; 序号;
#示例:
numbers = [10086, 10000, 10010, 95588]
names= ['中国移动','中国电信','中国联通']for t inzip(numbers, names):print(t)for n, a in zip(numbers, names): #注 for 变量列表 in zip(...) 序列赋值
print(a, '的客服号码是', n)for t in zip(range(1000), numbers, names):print(t)for t inenumerate(names):print(t)for t in enumerate(names, 1000):print(t)
#练习#写一个程序,读入任意行文字,当输入空行时,结束输入,打印带有行号的输入结果#如:#请输入:hello # 输出如下:# 第1行:hello
defread_lines():
L=[]whileTrue:
s= input('请输入:')if nots:breakL.append(s)returnLdefprint_lines(L):'''打印带有行号的文字信息'''
for t in enumerate(lines, 1):print('第%d行:%s' %t)if __name__ == '__main__':
lines=read_lines()
print_lines(lines)
文件读取
defread_file(fpath):
BLOCK_SIZE= 1024with open(fpath,'rb') as f:whileTrue:
block=f.read(BLOCK_SIZE)ifblock:yieldblockelse:return
如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取。
My:生成器对象就是一种特殊的迭代器对象,满足迭代器协议,可以调用next;对生成器对象for 循环时,调用iter方法返回了生成器对象,然后再不断next迭代,而iter和next都是在yield内部实现的。
练习1:使用文件读取,找出文件中最长的行的?
max(len(x.strip()) for x in open('/hello/abc','r'))
View Code
练习2:
defadd(s, x):return s +xdefgen():for i in range(4):yieldi
base=gen()for n in [1, 10]:
base= (add(i, n) for i inbase)printlist(base)
解析'''核心语句就是:
for n in [1, 10]:
base = (add(i, n) for i in base)
在执行list(base)的时候,开始检索,然后生成器开始运算了。关键是,这个循环次数是2,也就是说,有两次生成器表达
式的过程。必须牢牢把握住这一点。
生成器返回去开始运算,n = 10而不是1没问题吧,这个在上面提到的文章中已经提到了,就是add(i, n)绑定的是n这个
变量,而不是它当时的数值。
然后首先是第一次生成器表达式的执行过程:base = (10 + 0, 10 + 1, 10 + 2, 10 +3),这是第一次循环的结
果(形象表示,其实已经计算出来了(10,11,12,3)),然后第二次,
base = (10 + 10, 11 + 10, 12 + 10, 13 + 10) ,终于得到结果了[20, 21, 22, 23].'''
send的工作方式:
deff():print("ok")
s=yield 7
print(s)yield 8f=f()print(f.send(None))print(next(f))#print(f.send(None))等同于print(next(f)),执行流程:打印ok,yield7,当再next进来时:将None赋值给s,然后返回8,可以通过断点来观察
序列:list, str, tuple, bytes, bytearray
字节串 bytes :(也叫字节序列)
1、字节串bytes
作用:存储以字节为单位的数据
说明:
字节串是不可变的字节序列
字节是0~255的整数(0x00~0xFF; 0b00000000~0b11111111)
创建空字节的字面值:
b = b''b绑定空字节串
b = b""b绑定空字节串
b = b''''''b绑定空字节串
b = b""""""b绑定空字节串
创建非空字节串的字面值:
b = b'ABCD'
b = b'\x41\x42'
2、字节串的构造函数 bytes:
bytes()生成一个字的字节串,等同于b'';
bytes(整形可迭代对象)#用可迭代对象初始化一个字节串
bytes(整数n)生成n个值为0的字符串
bytes(字符串,encoding='utf-8')用字符串的转换编码生成一个字节串
示例:
b=bytes()
b= bytes(range(0, 255))
b= bytes(10)
b= bytes('你好', 'utf-8')>>> bytes('hello','GBK') #b'hello'
>>> bytes('hello','utf-8') #b'hello'
国家标准码:
GBK
GB2312
GB18030
Unicode标准码:
Unicode(utf-8)
3、字节串的运算:a 97 A 65 0 48
+ += * *=
< <= > >= == !=
in / not in
示例>>> b = b'ABC' + b'123'
>>> len(b) #6
>>> b #b'ABC123'
>>> max(b) #67
>>> min(b) #49
>>> sum(b) #348
>>> b'abC' > b'abD' #False
>>> b'A' in b #True
>>> 49 in b #True
>>> b[::2] #b'AC2'
4、字节串的索引和切片:
5、字节串的函数:
len(x) max(x) min(x) sum(x) any(x) all(x)
6、字节串bytes 与 字符串str 的区别:
bytes 存储字节(0~255)
str 存储unicode字符(0~65535或更大)
str与bytes转换:
编码(encode)
str---------->bytes
b= s.encode(encoding='utf-8')
解码(decode)
bytes------------->str
s= b.decode(encoding='utf-8')
help(str.encode)>>> s = 'hello,你好'
>>> b = s.encode('utf-8')>>> b #b'hello,\xe4\xbd\xa0\xe5\xa5\xbd'
>>> s2 = b.decode('utf-8')>>> s2 #'hello,你好'
list--->tuple
set--->frozenset
bytearray----> bytes
字节数组 bytearray:
可变的字节序列;
创建函数 bytearray:
bytearray()创建字节数组
bytearray(可迭代对象)同bytes(可迭代对象)
bytearray(整数n)
bytearray(字符串,encoding='utf-8')
>>> ba =bytearray()>>> type(ba) #
>>> ba #bytearray(b'')
>>> bytearray(range(65,70)) #bytearray(b'ABCDE')
>>> bytearray(10) #含有10字节都为0的字节数组,最大值和最小值都为0;
bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
运算操作: + += * *=
比较运算:< <= > >= == !=
in / not in
函数: len(x) max(x) min(x) sum(x) any(x) all(x)
索引 index / 切片 slice:
字节数组支持索引和切片赋值,规则同列表的索引和切片赋值规则;
示例:>>> ba = bytearray(b'ABCDEF')>>>ba
bytearray(b'ABCDEF')>>> ba[::2] = [48, 49, 50]>>>ba
bytearray(b'0B1D2F')>>>字节数组的方法:
详见: help(bytearray)
文件 file: (RAM 随机存储器)
文件是用于数据存储的单位;
文件通常用来长期存储数据;
文件中的数据是以字节为单位进行顺序存储的;
1、文件的操作流程:
1. 打开文件
2. 读/写文件
3. 关闭文件
注:任何的操作系统,同一个应用程序同时打开文件的数量有最大数限制,所以在用完文件后需要关闭;
2、文件的打开函数open
open(file, mode='rt') 用于打开一个文件,返回此文件流对象,如果打开文件失败,则会触发OSError错误;
3、文件的关闭方法:
F.close()关闭文件,释放系统资源;
文件常用方法:
F.close() 关闭文件(关闭后文件不能再读写会发生ValueError错误)
F.readline() 读取一行数据(包括行尾换行符\n), 如果到达文件尾则返回空行
F.readlines(max_chars=-1) 返回每行字符串的列表,max_chars为最大字符(或字节)数,
F.writelines(lines) 每行字符串的列表
F.flush() 把写入文件对象的缓存内容写入到磁盘
F.read(size= -1) 从一个文件流中最多读取size个字符
F.write(text) 写一个字符串到文件流中,返回写入的字符数
二进制文件操作方法:
F.tell() 返回当前文件流的绝对位置
F.seek(offset, whence=0) 改变数据流的位置,返回新的绝对位置
F.readable() 判断这个文件是否可读,可读返回True,否则返回False
F.writable() 判断这个文件是否可写,可写返回True,否则返回False
F.seekable() 返回这个文件对象是否支持随机定位
F.truncate(pos= None) 剪掉 自pos位置之后的数据,返回新的文件长度(字节为单位)
4、文本文件操作模式:
模式字符:
't' (默认)
1. 默认文件中存储的数据为字符数据,以行为单位分隔,在python内部统一用'\n'作为换行符进行分隔;
2. 对文本文件的读写需要用字符串(str)进行读取和写入数据;
各操作系统的换行符:
linux 换行符:'\n'
whindows 换行符:'\r\n'
旧的macintosh 换行符:'\r'
新的Mac OS 换行符:'\r'
说明:
在文本文件模块下,各操作系统的换行符在读入python内部时办的为字符'\n';
defread_file_content():try:
f= open('info.txt')whileTrue:
s= f.readline() #'小张 20 100\n'
if nots:breaks2= s.strip() #去掉两端空白字符(#'小张 20 100')
name_age_score = s2.split() #['小张' '20' '100']
n, a, s = name_age_score #序列赋值
d = {'name': n, 'age': int(a), 'score': int(s)}
L.append(d)print(s)exceptOSError:print('打开文件失败')returnLif __name__ == '__main__':
docs=read_file_content()print(docs)
文件的迭代读取:
open返回的文件流对象是可迭代对象;
5、文本文件的写操作:
写文件模式有:
'w'
'x'
'a'
文件操作模式:
'r'以只读方式打开(默认)'w'以只写方式打开,删除原有文件内容(如果文件不存在,则创建该文件并以只写方式打开)'x' 创建一个新文件, 并以写模式打开这个文件,如果文件存在则会产生"FileExistsError"错误'a'以只写文件打开一个文件,如果有原文件则追加到文件末尾'b'用二进制模式打开't'文本文件模式打开 (默认)'+' 为更新内容打开一个磁盘文件 (可读可写)缺省模式是'rt'
'w+b'可以实现二进制随机读写,当打开文件时,文件内容将被清零'r+b'以二进制读和更新模式打开文件,打开文件时不会清空文件内容'r+' 以文本模式读和更新模式打开文件,打开文件时不会清空文件内容
6、二进制文件操作:
默认的文件中存储的都是以字节为单位的数据,通常有人为规则的格式,需要以字节为单位进行读写;
linux下#man ascii
ubuntu#xxd baidu.gif | head #ubuntu以十六进制方式查看图片(baidu.gif)
F.read() 的返回类型:
1.对于文本模式(’t')打开的文件,返回字符串str;
2.对于二进制模块('b')打开的文件,返回字节器bytes;
F.write(x)
1.对于文本模式,x必须为字符串;
2.对于二进制模块,x必须为字节串;
以十六进制方式查看 文件内容的命令:
# xxd 文件名 #以十六进制方式查看文件
#file_write_binary.py#示意以二进制方式写文件到’data.bin‘
try:
f= open('data.bin', 'wb')print('open 成功')#写入数据
b = b'\xe4\xb8\xad'f.write(b)
f.write(b'\x00\x00')
f.close()exceptOSError:print('file open fail')
str1= '15683359115'b= str1.encode('utf-8')
F.seek()方法:
F.seek(偏移量,相对位置)
偏移量:
大于0的数代表向文件尾方向移动;
小于0的数代表向文件头方向移动;
相对位置:
0代表从文件头开始偏移;
1代表从当前位置开始偏移;
2代表从文件尾开始偏移;
作用:
改变当前文件的读写位置;
注:seek 通常对 二进制模式打开的文件进行操作;
F.tell()方法:
作用:
返回当前文件读写位置;
小结:
文件操作的两种模式:
'b' 二进制模式
't' 文本模式
==检索指定目录,下所有后辍为Py的文件==
importosimportos.path#检索指定路径下,所有后辍为py的文件
ls =[]defgetAppointPathFile(path, ls):
filelist= os.listdir(path) #列出目录下所有文件包括子目录
try:for tmp infilelist:
pathTmp= os.path.join(path, tmp) #将路径与filelist组合
if True ==os.path.isdir(pathTmp):
getAppointPathFile(pathTmp, ls)#对子目录使用递归继续深入检索
elif pathTmp[pathTmp.rfind('.') + 1:].upper() == 'PY': #取文件后辍,变将后辍大写
ls.append(pathTmp)exceptPermissionError:pass
defmain():whileTrue:
path= input('请输入路径:').strip()if os.path.isdir(path) ==True:breakgetAppointPathFile(path, ls)for file inls:print(file)print(len(ls))
main()
回顾:
1.迭代器:it = iter(可迭代对象)
value = next(it) #从迭代器中获取值,当没有数据时触发StopIteration通知;
2.生成器:创建可迭代对象,提供动态生成数据对象;
生成器有两种:生成器函数和生成器表达式
生成器函数:
含有yield语句的函数是生成器函数
yield 表达式
yield作用: 是为next(it) 函数提供数据,生成器函数的执行流程和函数不同。
生成器表达式:(x ** 2 for x in range(10)) 注意与列表推导式区分开
3.迭代相关的函数:(也称生成器函数)
zip(iter1, iter2, iter3, .....) 返回一个zip对象,此对象用于生成一个元组,此元组中的元素分别由iter1,iter2可迭代对象中的元组构成(元组个数由最小的可迭代对象决定);
enumerate(iterable, start=0) 生成带索引的枚举对象,返回的迭代类型为索引-值对(index-value)对,默认索引从零开始,也可用start指定; 序号;
4.容器类 :字节串 bytes 字节数组 bytearray
5.str.encode(编码字符串) 转为bytes
bytes.decode(编码字符器) 转为str
1.文件 file 存储以字节为单位的数据;
文件的打开模式:二进制模式'b'文本模式't' 默认
文件的操作模式:读 'r' 写 'w', 'a'追加, 'x'文件存在报错,没有报错
F.close()
F.read(n=-1) /F.feadline() /F.readlines()
F.write(x) / F.writelines(列表)
F.seek(偏移量offset,起始位置whence)
F.tell() 获取当前读取位置
文件流对象是可迭代对象
压缩算法:zip unzip huffman
2.汉字编码:
GB18030(GBK(GB2312))
UNICODE32(UNICODE16) ---->UTF-8 (8-bit Unicode Tranfromation Fortat)
3.编码字符串
'gb18030' / 'gbk' / 'gb2312'
'utf-8'
'ascii'
4.编码注释:
#-*- coding: utf-8 -*-告诉解释器编码是什么utf-8
6>