列表生成式
在迭代器之前先要知道列表生成式,什么叫列表生成式,生成一个列表的式子
语法
a = [x for x in range(10)]
print(a)
执行结果
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [Finished in 0.1s]
列表生成器里面写式子
x*2的意思是先把列表的0-10的元素一个个拿出来,拿出来之后每个元素去进行计算*2,计算结果放到新的列表里面去顺序不变,这就叫列表生成式
a = [x*2 for x in range(10)] print(a)
执行结果
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
列表生成式可以调用函数
def f(n): return n **3 # 注意:f(x)必须要这样写,for后面是什么变量就得写什么变量,n只是一个形参 a = [f(x) for x in range(10)] print(a)
执行结果
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729] [Finished in 0.1s]
变量赋值的另一种方式,当把值赋给新的变量的时候,变量和值得相互匹配,否则无论是值多了还是变量少了,会报错变量和值不匹配
t = ["123", 8] a, b = t print(a) print(b) # 执行结果 123 8 [Finished in 0.1s]
生成器
通过列表生成式,我们可以直接创建一个列表,但是,受到内存限制,列表容量肯定是有限的,而且创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间,在Python中,这种一边循环一边计算的机制,称为生成器:generator
生成器是一个特殊的程序,可以被用作控制循环的迭代行为,python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器。
生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器
那么创建list和generator_ex,的区别是什么呢?从表面看就是[ ]和(),但是结果却不一样,一个打印出来是列表(因为是列表生成式),而第二个打印出来却是<generator object <genexpr> at 0x000002A4CBF9EBA0>,那么如何打印出来generator_ex的每一个元素呢?
<generator object <genexpr> at 0x000001DD58CE2D48> generator是一个生成器,generator object生成器对象,生成器拿到的仅仅是一个对象,没有值。
生成器和列表生成式的区别:列表生成式就是直接创建好的列表已经在内存了,而生成器创建的是生成器对象,不用的话数据永远不会生成,什么时候想用列表的元素了才会被调用出来。
生成器里面的元素不能想列表生成式那样可以任意拿里面的元素,只能拿完了前面的元素才能拿后面的元素
如果要一个个打印出来,可以通过next()函数获得generator的下一个返回值:
# <generator object <genexpr> at 0x000001DD58CE2D48> generator是一个生成器,generator object生成器对象 # 生成器拿到的仅仅是一个对象,没有值,生成器和列表生成式的区别: # 列表生成式就是直接创建好的列表已经在内存了,而生成器创建的是生成器对象, # 不用的话数据永远不会生成,什么时候想用列表的元素了才会被调用出来 s = (x*2 for x in range(10)) print(s) # 想要拿第一个元素,用生成器得里面的一个方法next(),用生成器对象去调用 # 生成器里面的元素不能想列表生成式那样可以任意拿里面的元素,只能拿完了前面的元素才能拿后面的元素 print(next(s)) # 等价于s.__next__() py2用法:s.next() print(next(s)) print(next(s)) print(next(s)) print(next(s)) print(next(s)) # 结果 <generator object <genexpr> at 0x000002541E662D48> 0 2 4 6 8 10 [Finished in 0.1s]
生成器本身就是可迭代对象(lterable),它具备可迭代对象的条件
for对s(生成器对象)做了一个next()的调用,调用next()去拿里面的值,第一次出来的next的值是零,
它把这个零赋给了i,再循环的时候再回来,再拿的值是2,把2这个值赋给了i,之前的零没有变量再进行引用了,就作为垃圾回收了,它的空间就出来了
哪怕是打印一百万个数,其实占内存的就只有这一个数,因为其他的只要没有被引用就会被python回收掉了,空间永远是*的
s = (x*2 for x in range(5)) print(s) for i in s: print(i) # 执行结果 <generator object <genexpr> at 0x00000271B8CD8248> 0 2 4 6 8 [Finished in 0.1s]
# 生成器一共有两种创建方式:
# 第一种:(x*2 for x in range(5))
# 第二种:yield # 通过yield这个关键字来实现
下面我们来看yield是怎么帮我们生成一个生成器对象的
器代表是一个函数的意思,现在我们要通过跟函数创建方式相同的方式来创建一个生成器对象
生成器本质上就是一个函数,只不过它跟函数稍稍有一点区别,正常的函数体里面有一个return返回值,生成器函数体里面是一个yield关键字用来创建的生成器对象
# 函数体里面只要出现了yield就是一个生成器对象,它不会再执行函数
# foo()是生成器对象,yield相当于一个容器 def foo(): print("ok") yield 1 # 怎么确定这个是一个普通函数还是一个生成器对象?只要yield就是一个生成器对象 # 下面我们来打打印一下这个对象来确定一下,它是否是生成器对象 print(foo()) # 执行结果 <generator object foo at 0x0000020F0D4D2D48> [Finished in 0.1s]
生成器通过next()进行,还是用next()方法获取生成器里面的元素,有几个yield就有几个元素,顺序还是从上往下执行
def foo(): print("ok") yield 1 print("ok2") yield 2 g=foo() print(g) next(g) # 执行结果 # 1类似于return返回结果,但是不会被执行 <generator object foo at 0x00000213ADB68248> ok [Finished in 0.1s]
我们现在正常情况下,只能通过next()进去,但是很明显再继续next()会报错的,报的错还是StopIteration,
意思是下面没有了,走到头了,yield结束了,遇到return就代表函数的结束,没有手动加的话,默认是有一个return的
默认return None这句话只是没有写而已,它是有的,这个函数的特殊点就是第一次next()返回了yield1迭代器,下次
再next()从yield1开始走,走到yield2又开始返回了,再next()就没有迭代的东西了,所以这时候它认为有两个yield
它只能给你迭代两次,所以它就会给你报错,这就是它的一个机制。不管是哪一种生成器对象,超过了迭代次数都会给你报错
def foo(): print("ok") yield 1 print("ok2") yield 2 报错 <generator object foo at 0x0000022B2FED2D48>Traceback (most recent call last): ok ok2 next(g) StopIteration [Finished in 0.1s with exit code 1]
能不能对迭代器对象进行一个循环呢?
def foo(): print("ok") yield 1 print("ok2") yield 2 g = foo() print(g) # <generator object foo at 0x0000022B2FED2D48> for i in foo(): print(i) 执行结果 <generator object foo at 0x000001803AB08248> ok 1 ok2 2 [Finished in 0.1s]
用循环遍历方式和用next()方法获取迭代器元素的区别:
用next()方法调用的只有ok,ok2没有1,2,next方法调用程序,当走到yield的时候,像普通函数一样把1返回给了迭代器对象,但是,并没有在任何地方打印这个返回值,
我们可以打印一下这个迭代器对象来看看,也就能说明为什么我们在for循环迭代器对象的为什么能够打印出1,2了
def foo(): print("ok") yield 1 print("ok2") yield 2 g=foo() print(g) # <generator object foo at 0x0000022B2FED2D48> print(next(g), next(g)) 执行结果 <generator object foo at 0x000001A0E0848248> ok ok2 1 2 [Finished in 0.1s]
for循环迭代器对象,内部实际上是这样做的:
调用foo()通过生成器对象去next(),第一次next首先出来的是print("ok")紧接著返回1赋给i,所以i打的是1,
第二次又next又进去从yield1开始走执行第二段代码打印print("ok2"),返回一个2,返回2之后被i拿到了,i拿到又打印2了,
所以在这个过程中只有1和2,那个print("ok")和print("ok2")是在执行foo()生成器函数对象里面打印的
def foo(): print("ok") yield 1 print("ok2") yield 2 g=foo() print(g) # 能不能对迭代器对象进行一个循环呢? for i in foo(): print(i) 执行结果 <generator object foo at 0x000002C903F68248> ok 1 ok2 2 [Finished in 0.1s]
可迭代对象
什么是可迭代对象?
从现象上看能进行for循环的都是可迭代对象,但本质上是内部有__iter__方法的才叫可迭代对象,生成器内部就有__iter__方法,所以它能进行for循环
列表、元组、字典、集合、内部都有__iter__方法,都是可迭代对象
用生成器对象写一个斐波拉契数列,,斐波拉契数列的特点是前两数相加之后等于第三个数,依次类推
def fib(max): n, before, after = 0, 0, 1 while n < max: # print(before) yield before before, after = after, before+after n += 1 print(fib(8))
# 注意:生成器的特点是要用的是要用的时候才会给你计算出来,所以要么8次next()方法,要么用for循环遍历
# g = fib(8)
# print(g)
# print(next(g))
# print(next(g))
# print(next(g))
# ....
for i in fib(8): print(i) 执行结果 <generator object fib at 0x000002094A932D48> 0 1 1 2 3 5 8 13 [Finished in 0.1s]
# yield的特点是一个断层的,调用函数第一次进去结束之后的状态是停留在第一个yield那里保存住的,
# 保存住之后,下一次再开始调用的时候就是从第一个yield那里开始进去的了,其实这个状态是帮我们保存住的
# 而我们的普通函数不是这样的,普通函数只要遇到return这个函数就压根结束了,再想执行除非你再去执行这个函数,这就是yield最大的一个作用
生成器的send方法
生成器的send()方法,与next()方法的区别在于,next直接进去函数里面执行了,下一次还可以next
send()跟next()其实是一样的,只不过send比next多一个往函数里面传值的功能,next就是直接去执行了,直接进去有什么执行什么
send是执行执行send也会进入函数体里面执行,只不过它会给你的yield前面的变量赋一个值,如果没有变量就不会有接接收的,但是不会报错
def bar(): print("ok1") count = yield 1 # 这里的顺序首先是yield 1返回,然后把这个1给count了 print(count) yield 2 b = bar() # 第一次进去的时候不能传值,只能传一个None。第一次send前如果没有next,只能传一个send(None) # send第一次进入函数体不知道把值传给谁,所有只能传None,第二次进去程序保在yield 1了,所以可以传值携带进去 # b.send(None) # b.send(None)==next(b) next(b) s = b.send("eee") print(s) 执行结果 ok1 eee 2 [Finished in 0.1s]
顺序是首先程序开始调用bar()内存里有函数体内容了,执行bar之后给我们创建了生成器对象了,
紧接着调用next(b),调用next(b)跟调用send(None)是一样的,因为如果你要sen值程序不知到你要发给谁
所以只能发个空,相当于什么都不发,那这个时候next(b)之后进入函数体了,进来之后开始执行print("ok1")
然后yield 1返回,然后第二次调用执行send("eee")传参进去才有值,执行send("eee")又到函数体里面去了
进到函数体之后从yield 1 开始走,再执行send("eee")才给count,这个时候count才等于eee,给了count的eee之后就可以打印了
有这个变量了所以能打印,打印完了又该退出了,退出了函数体之后这个yield 后面的2给谁调用就给谁接收,s接收了所以s就是2了
send是怎么工作的了,就跟next一样,只不过它可以进去给变量赋值,进去传值有什么用了?
因为有的需求是需要跟程序进行一个交互的,需要在调用它的时候给它一些参数之内的内容,这时候再send传进去给它一些指导
迭代器
生成器都是迭代器,迭代器不一定是生成器
迭代器包含有next方法的实现,在正确的范围内返回期待的数据以及超出范围后能够抛出StopIteration的错误停止迭代。
我们已经知道,可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list,tuple,dict,set,str等
一类是generator,包括生成器和带yield的generator function
这些可以直接作用于for 循环的对象统称为可迭代对象:Iterable
元组、列表、字典、集合、字符串这些都是可迭代对象,都有iter()方法
iter()方法就为我们做了一件事情,返回了一个迭代器对象
i = [1, 2, 3, 5] d = iter(i) # iter() == i.__iter__() __iter__是py2的语法 print(d) # <list_iterator object at 0x00000236DC273A88> iterrator(迭代器) 执行结果 <list_iterator object at 0x00000236DC273A88> [Finished in 0.1s] list,tuple,dict,string都是可迭代对象,而调用了这个iter()方法之后,返回的这个d就是一个iterrator,它就是一个迭代器
什么是迭代器?
迭代器满足两个条件:1.有iter方法。2.有next方法
i = [1, 2, 3, 5] # iter()方法就为我们做了一件事情,返回了一个迭代器对象 d = iter(i) # iter() == i.__iter__() __iter__是py2的语法 print(d) # <list_iterator object at 0x00000236DC273A88> iterrator(迭代器) print(next(d)) print(next(d)) print(next(d)) print(next(d)) print(next(d)) 执行结果 <list_iterator object at 0x00000188DECE3A88> 1 2 3 5 Traceback (most recent call last): File "E:\workspace4\", line 12, in <module> print(next(d)) StopIteration 生成器之所以能调用next方法,因为它本身就是一个迭代器,迭代更可以用这种方法了,只不过列表、元组、字典、字符串都可以转换成迭代器去调用
判断是否迭代器方法
from collections import Iterator,Iterable # collections(收集,集合), iterator(迭代器),iterable(迭代对象) i=[1,2,3,5] d=iter(i) # 创建迭代器 # print(d) print(isinstance(i,list)) # isinstance(存在)判断是否是列表 print(isinstance(i,Iterable)) # 判断是否是可迭代对象 print(isinstance(i,Iterator)) # 判断是否是迭代器 print(isinstance(d,Iterator)) # d才是迭代器 执行结果 True True False True [Finished in 0.1s]
time模块
模块都是为了实现一个需求一个功能,如果每一个功能都自己写代码,自己写函函数,那样会累死的,一些常用的功能,一些常用的内容,就像内置
函数就是python为我们写好了,我们直接去用就好了,我们就可以省出来时间就可以进行设计逻辑安排了,构思或者设计代码的结构,
所以说,无论是函数,还是内置函数,还是一些模块,都是python为我们提供好了的,需要哪些内容的时候,直接用就行了。
可能我们基本上用到的模块,它就给我们实现了仅有的功能,我们需要用到它的时候,也许我们的需求并不能完全给我们解决,这个时候
我们得自己加内容,但是没有关系,它已经给我们提供了很大得方便了,剩下得内容就自己加就行了,所以模块,内置函数这些都是为了我们方便开发的
在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。
为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在Python中,一个.py文件就称之为一个模块(Module)。
使用模块有什么好处?
最大的好处是大大提高了代码的可维护性。其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他地方引用。我们在编写程序的时候,也经常引用其他模块,包括Python内置的模块和来自第三方的模块。
使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,因此,我们自己在编写模块时,不必考虑名字会与其他模块冲突。但是也要注意,尽量不要与内置函数名字冲突。点这里查看Python的所有内置函数。
你也许还想到,如果不同的人编写的模块名相同怎么办?为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)。
model模块,调用模块是用import调用的
模块、内置函数这些都是为了我们方便开发的,调用模块通过import调用,调用了之后这个模块里面的方法就可以随便用
import time print(help(time)) # 查看时间模块的帮助文档 输出 Help on built-in module time: NAME time - This module provides various functions to manipulate time values. DESCRIPTION There are two standard representations of time. One is the number of seconds since the Epoch, in UTC (a.k.a. GMT). It may be an integer or a floating point number (to represent fractions of seconds). The Epoch is system-defined; on Unix, it is generally January 1st, 1970. The actual value can be retrieved by calling gmtime(0)。。。
时间模块的一些的方法
import time print(time.time()) # 打印时间戳,1970(unix诞生年)至此时此刻的时间戳,单位秒 输出 1614011612.8995292 time.sleep(3) print(time.clock()) # 计算cpu执行时间 输出 3.0407941 [Finished in 3.1s] print(time.localtime()) # 打印本地时间(结构化时间) 输出 time.struct_time(tm_year=2021, tm_mon=2, tm_mday=23, tm_hour=0, tm_min=34, tm_sec=43, tm_wday=1, tm_yday=54, tm_isdst=0) [Finished in 0.1s] 把结构化时间转成字符串时间打印 struct = time.localtime() print(time.strftime("%Y-%m-%d %H:%M:%S", struct)) 输出 2021-02-23 00:35:58 [Finished in 0.1s]
时间模块的三种形式:时间戳、结构化时间、格式化时间
print(time.mktime(time.localtime())) # 把本地结构化时间转换成时间戳 输出 1614017388.0 [Finished in 0.1s]
还有一个关于时间的模块叫datetime,这种是最友好的表达方式,time也可以做成这种情况,如果那种时间需要自己去构建格式的话就用time模块
import datetime print(datetime.datetime.now()) # 这种是最友好的表达方式 输出 2021-02-23 02:13:36.120177 [Finished in 0.1s]
random模块(随机数)
print(random.random()) # 0到1的小数随机数 输出 0.8598963924017481 [Finished in 0.1s] print(random.randint(1,8)) # 1到8的随机整数,包括8 输出 1 [Finished in 0.1s] print(random.choice("hello")) # 一个序列的随机选择 输出 h [Finished in 0.1s] print(random.sample(["1,2,3", 4, [1,2]], 2)) # 一个序列里面随机选两个 输出 ['1,2,3', 4] [Finished in 0.1s] print(random.randrange(1,3)) # 范围内取值不包括3,只有1到2 输出 2 [Finished in 0.1s]
用random写一个随机5为数字验证码
def v_code(): code = "" for i in range(5): add_num = random.randrange(10) code += str(add_num) print(code) v_code() 输出 12093 [Finished in 0.1s]
字母数字组合随机5位验证码验证码
chr()函数是输入一个整数【0,255】返回其对应的ascii符号
def v_code(): code = "" for i in range(5): add = random.choice([random.randrange(10), chr(random.randrange(65, 91))]) code += str(add) print(code) v_code() 输出 LYLZ4 [Finished in 0.1s]