python开发基础篇:二
1:装饰器基础
基础版本的装饰器,装饰器很重要,必问
1:装饰器形成的过程 : 最简单的装饰器 ->有返回值的-> 有一个参数-> 万能参数->装饰器自己带参数的
2:装饰器的作用
3:程序开发原则 :开放封闭原则(还有依赖倒置原则)
4:装饰器语法糖 :@
5:装饰器的固定模式
# 一个简单的需求:计算一段代码运行时间 python对时间进行操作的内置模块:time模块 1:time.time() # 返回值获取当前时间,1612345340.9485893 返回一个时间戳,1970-1-1到现在经过的时间 2:time.sleep() # 让程序在执行到这个位置的时候停一会儿,代码阻塞在这里,停留在这里 os模式是和操作系统做交互来使用:如删除操作系统上的文件remove,修改文件名称rename # 1:计算函数运行时间的装饰器timmer函数源代码和讲解 def func(): # func函数是被装饰器装饰的函数 time.sleep(0.01) print('老板好同事好大家好') return '新年好'
import time def timmer(f): # 装饰器函数 def inner(): start = time.time() ret = f() # 被装饰的函数(传递进来的是被装饰的原函数) end = time.time() print(end - start) return ret return inner # 这里返回的是内部函数的名称不要加括号 def timmer(f): 这个timmer里面的inner函数是个闭包 1:内部函数调用了外部参数变量f 2:外部传进来的f即使是参数,也依然是timmer这个函数的局部作用域里的名字空间 ----所以inner函数是个闭包 最后调用func函数的时候实际上调用的是装饰器timmer函数内部的inner函数 func=timmer(func) #func = inner func() 上面的装饰器初级版本代码流程讲解: 1:def func(): 定义函数func, 2:def timmer(f): 定义函数timmer 3:func=timmer(func) 1:先执行=号后面的部分:timmer(func)调用timmer函数 把func传到f,在timmer的局部内存空间,f参数变量指向func内存地址 2:timmer函数里定义一个inner函数,然后 return inner 返回inner变量, 3:执行=号前面赋值部分,返回的inner返回给func变量了,func一开始是指向初始func函数以前的内存地址的 现在func被赋值成timmer内部定义的inner函数的内存地址了 4:func() 执行func()的时候实际执行的inner() 5:inner() inner函数直接执行 f() 中的f指的是已经传来的一开始定义的func地址,因为一开始func被传参给f在timmer函数的局部名称空间里已经存储了
装饰器的作用 不想修改函数的调用方式 但是还想在原来的函数前后添加功能 如上例子(原来的函数是func函数,timmer就是一个装饰器函数,只是对一个函数 有一些装饰作用)
装饰器的本质:闭包函数 代码开发原则: 封版 开放封闭原则 1:开放 : 对扩展是开放的 2:封闭 : 对修改是封闭的 开发一个项目写一个程序,版本1.0,2.0,3.0,更新的过程中可以加一些功能,但是不能修改原来有的一些功能 会影响后期的调用, 可以在现有的功能上加新功能,py3---加了很多大并发 修改源代码:叫程序的重构 ----这样很严重,代码一年到几年才重构一次
# 2:装饰器语法糖@符号 让装饰器代码变得更简单 @timmer import time def timmer(f): # 装饰器函数 def inner(): start = time.time() ret = f() # 被装饰的函数 end = time.time() print(end - start) return ret return inner @timmer # 语法糖 :@装饰器函数名 @timmer用法等同func=timmer(func) def func(): # 被装饰的函数上面加上@装饰器函数名 相当于写了func=timmer(func) 这行代码 time.sleep(0.01) print('老板好同事好大家好') return '新年好' ret = func() print(ret)
# 3:带返回值的装饰器(被装饰函数有返回值),需要装饰器里面的inner函数变量接收func函数执行后的返回值然后在inner里面return出去 import time def timmer(f): # 装饰器函数 def inner(): start = time.time() ret = f() # 变量ret接收被装饰的函数f的返回值 end = time.time() print(end - start) return ret # 把func初始函数的返回值返回出去 return inner @timmer def func(): time.sleep(0.01) print('老板好同事好大家好') return '新年好' ret = func() print(ret)
# 内嵌函数演示 # case1 def outer(): def inner(): return 'inner' inner() ret = outer() # 这里执行outer,outer函数没有返回值,只有inner函数有返回值 print(ret) # 打印:None # 代码运行逻辑 1:outer函数放内存里 2:ret = outer()调用的时候outer的时候, 1:outer里面先定义一个inner 2:inner()执行调用inner函数,inner函数执行完有返回值,返回值返回到inner()这里。但是没有变量去接受 outer函数就结束了, outer函数的返回值为None
# 4:装饰带参数函数的装饰器,被装饰的函数可能带一个参数,可能带多个参数 # 在装饰器里面的inner函数里面加上*args和**kwargs import time def timmer(f): # 装饰器函数 def inner(*args, **kwargs): # *args和**kwargs 代表inner函数可以接受任何参数(因为外部最终调用func函数就是等同调用这里的inner函数,最终参数就是传递给了inner函数) # (1,2) /(1) # 按照位置传参的和关键值传递的都可以:inner(1,2,3) inner(a=1,b=2) start = time.time() ret = f(*args, **kwargs) # f(1,2) f是被装饰的函数 *args解开args这个元组给f函数传参,**kwargs解开字典给f函数传参 end = time.time() print(end - start) return ret return inner @timmer # 语法糖 @装饰器函数名 def func(a, b): # 被装饰的函数 time.sleep(0.01) print('老板好同事好大家好', a, b) return '新年好' @timmer # 语法糖 @装饰器函数名 def func1(a): # 被装饰的函数带参数 time.sleep(0.01) print('老板好同事好大家好', a) return '新年好' def func2(*args): l = [] for i in args: l.append(i) return l def func3(**kwargs): l = [] for i in kwargs: l.append([i, kwargs[i]]) return l ret = func(1, 2) # inner() 这里func(1,2)传递的参数1,2实际上是传给inner函数 print(ret) ret = func(1, b=2) # inner() print(ret) resp = func2([1, 2, 3, 4, 5, 6, 7, 8]) print(resp) resp = func3(a=1, b=2) print(resp)
# 5:装饰器代码模板 import time def wrapper(f): # wrapper是装饰器函数,f是被装饰的函数 wrapper就是装饰的意思 def inner(*args, **kwargs): # *args,**kwargs 动态参数 '''在被装饰函数之前要做的事''' ret = f(*args, **kwargs) # 执行调用被装饰的函数 '''在被装饰函数之后要做的事''' return ret return inner @wrapper # 语法糖 @装饰器函数名 def func(a, b): # 被装饰的函数 time.sleep(0.01) print('老板好同事好大家好', a, b) return '新年好' # 装饰器步骤如下 # 步骤一 def wrapper(): def inner(): pass return inner # 不要加括号,加括号就变成了调用 # 步骤二 def wrapper(func):
# 每次外部func=wrapper(func)调用wrapper函数的时候,
# wrapper这个函数就创建局部空间,并且创建一个func变量接受被装饰的函数,
# 下面qqxing = wrapper(qqxing)或者@wrapper就是把qqxing这个函数传递给wrapper函数局部空间的func
# 然后让qqxing这个变量重新赋值到wrapper内部的inner函数 def inner(*args, **kwargs): ret = func(*args, **kwargs) # 被装饰的函数func return ret return inner @wrapper # qqxing = wrapper(qqxing) def qqxing(): print(123) ret = qqxing() # inner qqxing()=inner()
# *args的使用 def outer(*args): print(args) # (1, 2, 3, 4) 打印一个元组 print(*args) # 1 2 3 4 打印一个个参数,被打散了 # args是个元组,*args是打散后的一个个参数,函数调用可以使用*args打散args这个序列变成一个个参数传递,print这里也是一个函数调用, # print函数接受一个*args这么一个参数,才把args这个序列里每个参数打印出来 # def outer(*args):定义函数后面运行的时候使用*args是把外部调用outer函数时候传递的多个位置参数组合成一个元组赋值给args # outer(*[1,2,3,4]):函数调用的时候使用*iter是把iter这个序列拆解成一个个的元素传参给outer函数调用 def inner(*zzz): # 这里的*zzz把传来的各个参数1,2,3,4聚合成(1,2,3,4)这样的元组并且赋值给zzz变量 print('inner : ', zzz) inner(*args) # 这里*args是把args这个序列打散一个个传参 等同inner(1,2,3,4,...)进行传参 outer(1, 2, 3, 4) # outer(1, 2, 3, 4)等同outer(*[1,2,3,4])等同outer(*(1,2,3,4)) # 动态参数:一松一散,先outer(1,2,3,4)或者outer(*(1,2,3,4)) 打散序列成多个参数进行传递 # 到了运行函数outer(*args)就会把多个参数聚合成一个元组,----装饰器类似一个媒体 # 函数运行的时候接受参数就组合成元组,---接收的时候*args是聚合 # 调用函数的时候*args就是打散元组 ---调用的时候*args就是打散 # 装饰器类似媒介
# functools模块wraps装饰器的使用 1:装饰器调用后被装饰函数的函数名和注释都会指向装饰器里面的内置inner函数 全局角度说没有holiday被装饰函数了,初始的holiday函数传参给func了,func=holiday holiday已经在装饰器函数wrapper局部名字空间了,没有在全局名字空间了 def wrapper(func): def inner(*args, **kwargs): print('在被装饰的函数执行之前做的事') ret = func(*args, **kwargs) print('在被装饰的函数执行之后做的事') return ret return inner
@wrapper def holiday(day): '''这是一个放假通知''' print('全体放假%s天' % day) return '好开心' print(holiday.__name__) # 获取holiday函数的名称 打印:inner print(holiday.__doc__) # 获取holiday函数的注释 打印:None # 因为现在使用了装饰器,holiday函数实际指向inner函数的内存地址了
# 所有这里打印holiday函数的__name__函数名称和__doc__函数注释实际上打印的是wrapper装饰器里面inner函数的名称和注释
# 2:调用装饰器后,如果想被装饰函数holiday外面还能正常使用holiday所有的内置方法----这时候需要用到wraps方法 from functools import wraps # wraps装饰器的意思 def wrapper(func): # func = holiday @wraps(func) # 给inner内部函数写个装饰器, # 不改变inner的原有的功能,加了其他功能,inner函数的信息 # 指向holiday def inner(*args, **kwargs): print('在被装饰的函数执行之前做的事') ret = func(*args, **kwargs) print('在被装饰的函数执行之后做的事') return ret return inner
@wrapper # holiday = wrapper(holiday) def holiday(day): '''这是一个放假通知''' print('全体放假%s天' % day) return '好开心' print(holiday.__name__) # holiday print(holiday.__doc__) # 这是一个放假通知
# 上面的@wraps(func)调用wraps这个装饰器可以使用inner = wraps(func)(inner)来代替
内部逻辑就是调用装饰器的时候,装饰器wrapper内部空间的func参数指向初始的holiday函数
调用装饰器inner = wraps(func)(inner)后,把func函数(指向初始的holiday函数)的相关参数信息
提取出来,用来装饰inner函数,让inner函数的函数信息和初始的holiday相同
所以后面的打印被装饰函数holiday的__name__函数名和__doc__函数注释信息和初始的holiday相同
@wraps(func)让它func(func是传参进来的holiday)函数基础属性不变,
加了@wraps(func)让func(func是传参进来的holiday)的基础属性还是指向初始的func(初始的holiday),但是功能还是inner函数的
# 6:装饰器带参数(三层装饰器)模板 def wraper(age): def bar(func): def inner(*args, **kwargs): func(*args, **kwargs) print("这是新加的功能") print("年龄是:", age) return inner return bar @wraper(18) # @wraper(18)等同foo=wraper(18)(foo) # 1:foo=wraper(18) 返回bar # 2:bar(foo)返回inner # 所以foo最终还是指向内部的inner函数 def foo(name): print("测试订单:", name) foo("张三") 1:带参数的装饰器-:三层装饰器的实际使用 实现一个如下需求: 写了500个函数,每个函数都想加上一个运行时间,为了记录考核 1:flage为True的时候就去计算函数执行的时间,为False就不去计算时间 import time def timmer_out(flag=True): # 多套了这层timmer_out函数就是为了flag传参而已,为了外界能通过装饰器传个参数 def timmer(func): def inner(*args, **kwargs): if flag: # 如果flag=true就计算程序执行的时间 stat = time.time() reps = func(*args, **kwargs) end = time.time() print(f'{func.__name__}:函数执行的时间为:{end - stat}') else: # 如果flag不是等于True,那么什么逻辑也不加直接调用原函数就行 reps = func(*args, **kwargs) return reps return inner return timmer @timmer_out() # @timmer_out()等同wahaha=timmer_out()(wahaha) def wahaha(): time.sleep(0.1) print('wahaha') wahaha()
# 这里调用timmer_out装饰器的时候没有传递flag的参数,那么flag默认为True,flag为True那么就会打印函数运行的时间
@timmer_out(False)
def youxixin():
time.sleep(0.1)
print('wahaha')
youxixin()
# 这里调用timmer_out装饰器的时候传递了flag参数为False,如果flag为False那么就不会打印函数的运行时间
所以这里三层装饰器多搞这么一个参数进去是为了对装饰器的逻辑做某些控制
上面@timmer_out() 执行步骤: 1:timmer_out() 装饰器函数的先调用执行,timmer_out函数执行完成后得到返回结果然后再加@装饰器 timmer_out执行,timmer_out函数的内置空间会定义timmer函数并且返回timmer函数的空间地址,返回给接收者 2:timmer_out返回了timmer后再加上@变成了@timmer
@timmer等同wahaha=timmer(wahaha),所以还是等同把wahaha函数传递给第二层timmer函数的func,然后来装饰这个func函数
上面是整个三层装饰器的调用,一步步分解 也可以理解成: timmer = timmer_out(FLAGE)
因为timmer_out(FLAGE)这个最外层的装饰器函数执行后会返回一个timmer函数 然后@timmer
@timmer_out(FLAGE)这里会调用装饰器timmer_out返回一个timmer函数
最本质的还是如下:
@timmer_out(False)本质上是wahaha=timmer_out()(wahaha)
先等号左边运行,timmer_out()(wahaha)
timmer_out()这个函数调用的时候返回timmer函数,
然后timmer(wahaha)这个函数调用返回了inner,
最后把inner函数赋值给wahaha来覆盖一开始的wahaha函数实现重新定义wahaha函数让他变成新的wahaha函数
# 多个装饰器装饰同一个函数 def wrapper1(func): def inner1(): print('wrapper1 ,before func') ret = func() print('wrapper1 ,after func') return ret return inner1 def wrapper2(func): def inner2(): print('wrapper2 ,before func') ret = func() print('wrapper2 ,after func') return ret return inner2 @wrapper2 @wrapper1 def f(): print('in f') return '哈哈哈' print(f()) 上面函数执行后打印的结果如下: wrapper2 ,before func wrapper1 ,before func in f wrapper1 ,after func wrapper2 ,after func 哈哈哈 上述代码执行步骤: 1:def wrapper1(func): 装饰器wrapper1放内存里 2:def wrapper2(func): 装饰器wrapper2放内存里 3:@wrapper2找不到紧挨的下方的函数,找不到, @wrapper1才找到下方离得近的函数 @wrapper1等同f=wrapper1(f) 先执行@wrapper1后执行@wrapper2 ---先执行离函数近的 1:执行f=wrapper1(f)--->执行等号右边的:wrapper1(f) f函数传到wrapper1局部空间的func变量,然后返回inner1赋值给了f f=inner1
执行了@wrapper1后此时f这个变量指向了wrapper1内部的inner1函数 2:执行f=wrapper2(f)--->执行等号右边的wrapper2(f) 此时f变量已经指向inner1函数了 等同执行:f=wrapper2(inner1) 把inner1传给wrapper2局部空间的func变量然后返回inner2 最后 f=inner2, 4:f() 等于执行inner2() 上面就是两层装饰器的执行过程 ----三层装饰器的执行结果也类似
等同就是f函数先被wrapper1装饰后返回inner1,f此时指向inner1函数
然后f(现在变成了inner1了)又被wrapper2装饰返回inner2.f此时指向inner2函数,所以最终f函数指向的是inner2这个函数
上面函数的执行结果:类似俄罗斯套娃一样 wrapper3 ,before func wrapper2 ,before func wrapper1 ,before func in f wrapper1 ,after func wrapper2 ,after func wrapper3 ,after func 哈哈哈 ---------------- @wrapper2 @wrapper1
假如一个函数被如上两个装饰装饰,那么代码执行顺序,类似俄罗斯套娃 第一部分执行的是:wrapper2装饰器中在被装饰的函数之前的代码 第二部分执行的是:wrapper1装饰器中在被装饰的函数之前的代码 第三部分执行的是:func这个被装饰函数的代码执行 第四部分执行的是:wrapper1装饰器中在被装饰的函数之后的代码 第五部分执行的是:wrapper2装饰器中在被装饰的函数之后的代码 多装饰器执行顺序分析(为什么执行顺序是类似俄罗斯套娃): 虽然写的是 @wrapper2 @wrapper1 def f(): 装饰器的代码从下往上走的,@表示当前正在调用一个装饰器,装饰器必须找离他最近的函数,
本质上是py解释器看到@装饰器先放着,先定义函数,定义完函数后然后一层层找这个函数附近的装饰器,从近到远找这个函数的装饰器
f这个被装饰的函数先找@wrapper1后再找@wrapper2 1:@wrapper1 f=wrapper1(f) 把初始的f参数传递进去然后f指向wrapper1里面的inner1 2:@wrapper2 f=wrapper2(f) 此时f指向inner1,把inner1传递进去然后f指向wrapper2里面的inner2 因为f一开始被wrapper1装饰后f指向inner1了,所以此时f=wrapper2(f)等同于f=wrapper2(inner1)
返回inner2给f变量所以f最后指向inner2这个函数 3:最后f()调用f函数 等同执行了inner2 1:先执行inner2里面的 print('wrapper2 ,before func') 2:然后调用inner2里的ret=func() 这时候func是inner1,就是执行inner1(),然后拿到inner1函数的返回值'哈哈哈'赋值给ret 1:执行inner1的 print('wrapper1 ,before func') 2:执行inner1里的ret=func() inner1里面的func指向最开始被装饰的f函数,也就是原本的f()函数执行
f()函数执行:先:print('in f'),然后返回'哈哈哈'被inner局部空间的ret接收
3: print('wrapper1 ,after func') 此时inner1函数返回值就是最初始f函数的返回值:"哈哈哈"
3:print('wrapper2 ,after func')
4:inner2里面最后返回ret也就是"哈哈哈"被最外层调用或者print打印
# 3:装饰器进阶,多个装饰器装饰一个带返回参数的函数 def wrapper1(func): def inner1(): print('wrapper1 ,before func') ret = func() print('wrapper1 ,after func') return ret return inner1 def wrapper2(func): def inner2(): print('wrapper2 ,before func') ret = func() print('wrapper2 ,after func') return ret return inner2 def wrapper3(func): def inner3(): print('wrapper3 ,before func') ret = func() print('wrapper3 ,after func') return ret return inner3 @wrapper3 @wrapper2 @wrapper1 def f(): print('in f') return '哈哈哈' print(f())
# f这个被装饰的函数带返回参数 # 记录用户的登录情况(日志) # 计算这个函数的执行时间 ---多个装饰器 ---先登录再举记录时间,因为登录成功才执行程序才记录时间
# 装饰器最终形态,定义一个三层装饰器(装饰器自己带参数)来装饰一个带参数和返回值的函数 from functools import wraps def wrapper(*wra_args, **wra_kwargs): def bar(func): @wraps(func) def inner(*inner_args, **inner_kwargs): print('调用外部传递给装饰wrapper的参数来做一些逻辑判断和处理', wra_args, wra_kwargs) print('在被装饰的函数执行之前做的事') res = func(*inner_args, **inner_kwargs) print('在被装饰的函数执行之后做的事') return res return inner return bar @wrapper(1, 2, aaa=111, bbb=222) def li_er_gou(name, age): """这是一个李二狗函数""" print(f"我的名字叫{name},当前年龄{age}") return name, age reps = li_er_gou('李二狗', 18) # 打印如下: 调用外部传递给装饰wrapper的参数来做一些逻辑判断和处理 (1, 2) {'aaa': 111, 'bbb': 222} 在被装饰的函数执行之前做的事 我的名字叫李二狗,当前年龄18 在被装饰的函数执行之后做的事 print(reps) # ('李二狗', 18) print(li_er_gou.__name__) # li_er_gou print(li_er_gou.__doc__) # 这是一个李二狗函数
三层装饰器(带参数的装饰器):
1:需要三层嵌套
2:先执行@后面的代码函数调用。以函数调用返回值为结果作为最终要@的内容
如上@wrapper(1, 2, aaa=111, bbb=222)就先执行wrapper(1, 2, aaa=111, bbb=222)
执行wrapper(1, 2, aaa=111, bbb=222)返回bar函数,最终就是@bar
2:简单练习
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1.编写装饰器,为多个函数加上认证的功能(用户的账号密码来源于文件) 要求登录成功一次,后续的函数都无需再输入用户名和密码 flag = False # True表示登录成功,默认没有登录 ---执行函数之前要认证 # 创建全局变量flage,每一次调用inner函数就重新创建了空间了,inner函数里面的变量都是暂时的,只有func是永远的 # login函数每次调用一次也会重新创建命名空间,里面的变量也是暂时的 def login(func): def inner(*args, **kwargs): # inner函数里面的函数每次调用就重新开辟空间了,但是login里面的变量func永远的 global flag # 一般global和nonlocal写在函数最上面 if flag: # 如果flag为True,True表示登录成果,如果登录成果那么就直接执行调用的函数,不需要验证登录了 ret = func(*args, **kwargs) # func是被装饰的函数 return ret else: # 如果没有登录成功那么验证登录逻辑,输入用户名和密码进行校验 # with open('用户信息') as f: # info = f.readline().split() # str1 = info[0] # str2 = info[1] str1 = 'ywt' str2 = '123456' for i in range(3): user = input('用户名>>>>>') psw = input('密码>>>>') if user == str1 and psw == str2: print('恭喜您,登录成功......') flag = True ret = func(*args, **kwargs) return ret else: print('输入密码错误,登录失败') return inner @login def shoplist_add(): print('增加一件物品') @login def shoplist_delete(): print('删除一件物品') shoplist_add() shoplist_delete() 2.编写装饰器,为多个函数加上记录调用功能,要求每次调用函数都将被调用的函数名称写入文件 def log(func): def inner(*args, **kwargs): ret = func(*args, **kwargs) with open('log', mode='a+', encoding='utf8') as f: f.write(func.__name__ + '\n') return ret return inner @log def shoplist_add(): print('增加一件物品') @log def shoplist_del(): print('删除一件物品') shoplist_add() shoplist_del() shoplist_add() shoplist_del() shoplist_add() shoplist_del() 1.编写下载网页内容的函数,要求功能是:用户传入一个url,函数返回下载页面的结果 2.为题目1编写装饰器,实现缓存网页内容的功能: 具体:实现下载的页面存放于文件中,如果文件内有值(文件大小不为0), 就优先从文件中读取网页内容,否则,就去下载,然后存到文件中 import requests from urllib.request import urlopen import os def cache(func): def inner(*args, **kwargs): resp = func(*args, **kwargs) print(resp) if os.path.getsize('web_case'): # 如果获取的web_case文件大小不是0(文件里有数据),那么读取文件的内容 with open('web_case', mode='r', encoding='utf8') as f: return f.read() else: # 如果获取的web_case文件大小是0(文件里没有数据),那么把获取到的网页内容写入web_case文件 with open('web_case', mode='w', encoding='utf8') as f: f.write(resp) return resp return inner @cache def get_url(url): resp = requests.get(url) return resp.text @cache # 这样写也可以的 def get(url): code = urlopen(url).read() # 这里得到的html是bytes类型的数据,所有写文件需要用到wb模式 return code ret = get_url('http://www.baidu.com') print(ret) ret = get_url('http://www.baidu.com') print(ret) ret = get_url('http://www.baidu.com') print(ret) # {'网址':'文件名'} 写在一个文件里,每个网址都是一个文件, ''' 缓存:访问网页,请求到网站服务器, '''
3:迭代器
迭代器 l=[1,2,3]
列表取值:1:索引,切片 2:for循环取值,for循环从列表取值原理是什么:for i in l: 列表,字符串,元组,字典,set集合,open()打开文件之后的文件句柄f,range(),enumerate()枚举 上面这些数据类型都能被for循环 ---为什么能被for循环 print(dir([])) # dir函数返回[]列表这个数据类型(对象)的所有的方法 print(dir(())) # 查看元组这个数据类型的所有方法 print(dir({})) # 查看字典的这个数据类型的所有方法 print(dir('')) # 查看字符串这个数据类型的所有方法 print(dir(range(10))) # 查看range这个数据类型(对象)的所有方法
# 1:求下面这些能够使用for循环遍历的所有数据类型具有的相同的方法,转集合求交集 set1 = set(dir([])) set2 = set(dir(())) set3 = set(dir({})) set4 = set(dir('')) set5 = set(dir(range(10))) set_in = set1 & set2 & set3 & set4 & set5 print(set_in) # {'__repr__', '__sizeof__', '__doc__', # '__getitem__', '__str__', '__len__', # '__dir__', '__ge__', '__le__', '__hash__', # '__setattr__', '__gt__', '__format__', '__ne__', # '__class__', '__contains__', '__reduce__', '__lt__', # '__new__', '__subclasshook__', '__reduce_ex__', # '__delattr__', '__iter__', '__eq__', '__init__', # '__init_subclass__', '__getattribute__'} # 上面能看到求所有能for循环的数据类型拥有的共同方法的返回的列表里面有个__iter__双下方法 print('__iter__' in dir(int)) # False ,int数据类型不可以for循环因此没有__iter__方法 print('__iter__' in dir(bool)) # false,布尔值不可以被for循环没有__iter__方法 print('__iter__' in dir(list)) # True print('__iter__' in dir(dict)) # True print('__iter__' in dir(set)) # True print('__iter__' in dir(tuple)) # True print('__iter__' in dir(enumerate([]))) # True print('__iter__' in dir(range(1))) # True # 上面可以看出python里的对象只要是可以被for循环的数据类型都有这个__iter__这个双下方法, # 不可以被for循环的使用for循环报错不可iterable,因为这个对象没有没有__iter__这个双下方法, # iter和iterable和iterator print([].__iter__()) # <list_iterator object at 0x00000224C41FCE48> # 对列表这个数据类型调用__iter__方法后就会返回一个iterator迭代器 # 一个列表执行了__iter__()方法之后的返回值就是一个迭代器, # 执行了__iter__()双下划线方法就返回一个迭代器,没有执行之前还不是一个迭代器,只是一个可迭代的对象 print(dir([])) print(dir([].__iter__())) print(set(dir([].__iter__())) - set(dir([]))) # 输出:{'__setstate__', '__next__', '__length_hint__'} # 如上求列表对象的方法和列表调用__iter__返回的迭代器对象的方法的差集:返回如上打印的三个方法 # 1:__length_hint__ 求列元素个数 # 2:__next__ # 3:__setstate_ 指定从任意位于开始取值 print([1, 2, 3, 4, 5].__iter__().__length_hint__()) # 返回:5 __length_hint__这个方法是求列元素个数 l = [1, 2, 3, 4, 5] iterrator = l.__iter__() # l.__iter__() l这个列表调用__iter__方法返回一个迭代器iterrator res = iterrator.__next__() # 迭代器调用__next__方法从iterrator这个迭代器里面取一个值1 print(res) res = iterrator.__next__() # 从iterrator这个迭代器里面取一个值2 print(res) res = iterrator.__next__() # 从iterrator这个迭代器里面取一个值3 print(res) # 能被for循环的都是可迭代的,Iterable可迭代的 对象只要含有__iter__方法的都是可迭代的,且都可以被for循环 # [].__iter__() 生成一个迭代器 -- > __next__,通过next方法就可以从迭代器中一个一个的取值
上面的知识可以总结出:列表,字符串,元组等数据都是可以for循环遍历的对象,
这些for循环可以遍历的对象内部都有个__iter__方法
python中的对象只要含有__iter__方法的那么这个对象就是可以迭代的对象(iterable)
python中对可以迭代的对象调用__iter__方法后就会返回一个迭代器(iterator)
迭代器对象内部的方法比可迭代的对象内部的方法对了3个{'__setstate__', '__next__', '__length_hint__'}
尤其重要的就是迭代器内部的这个__next__方法可以从迭代器里取出一个元素出来
可迭代协议和迭代器协议 可迭代协议:python对象 只要含有__iter__方法的对象都是可迭代的 迭代器协议:python对象 只要含有__iter__和__next__方法的对象都是迭代器(必须同时含有着两个方法才是迭代器,少一个都不行)
2: 只要含有__iter__方法的对象都是可迭代的 ——>这就是可迭代协议——>python规定的,
python可以自己创造一个数据类型,让这个数据类型变得可迭代(只要这个对象内部实现__iter__方法就可以了
只要有__iter__方法解释器就认为可迭代的,可迭代就可以使用for循环进行遍历)
for 循环一个数据类型的时候,先去找这个对象内部有没有__iter__方法,有这个方法才能循环,没有这个方法直接报错打印不可迭代的
迭代器的概念 迭代器协议 —— 内部含有__next__和__iter__方法的就是迭代器(必须同时含有着两个方法才是迭代器,少一个都不行) from collections import Iterable from collections import Iterator print(isinstance([], Iterator)) # False 列表不是迭代器 print(isinstance([], Iterable)) # True 列表是可迭代的 # isinstance方法判断某个数据类型是不是后面参数这个东西
print(isinstance([], list)) # 判断[]这个对象是不是一个列表这里也会返回True class A: def __iter__(self): pass def __next__(self): pass a = A() print(isinstance(a, Iterator)) # True print(isinstance(a, Iterable)) # True
自己创造一个数据类型A,里面有__iter__和__next__两个方法,那么python解释器就认为a是迭代器和可迭代的 1:如果只有__iter__方法没有__next__方法,那么数据类型A只是一个可迭代的,不是一个迭代器 2:如果只有__next__方法没有__iter__方法,那么既不是迭代器也不是可迭代的所以得出迭代器协议: 1:含有__iter__方法就可迭代的 2:必须含有__iter__和__next__两个方法才是一个迭代器
3:列表只是一个可迭代的,并不是一个迭代器,循环一个可迭代的还是循环了一个迭代器,
对于for循环来说没有影响,只要内部含有__iter__方法那么就都可以被for循环 l= [1, 2, 3, 4] for i in l: print(i, end=',') # 打印:1,2,3,4, print() for i in l.__iter__(): print(i, end=',') # 打印:1,2,3,4,
4:迭代器协议和可迭代协议 可以被for循环的都是可迭代的 可迭代的内部都有__iter__方法 只要是迭代器 一定可迭代(只要是迭代器内部一定有__iter__方法,含有__iter__就是可迭代的) 但是可迭代的不一定是迭代器(要看有没有__next__()方法) 可迭代的对象调用__iter__()方法就可以得到一个迭代器 迭代器中的__next__()方法可以从迭代器中一个一个的获取值
5:for循环遍历一个可迭代器对象的底层原理:for循环其实就是在使用迭代器
python中什么调用函数,类等方法什么时候返回的是一个迭代器对象(一般有下面几种情况): 1:iterator(明确告诉是一个iterator类型那么就是迭代器) 2:返回值感觉要返回很多值,但是返回的值看起来是一个什么都没做的事情的时候就是返回一个迭代器:如range(10) 可迭代对象 print([].__iter__()) print(range(10)) # 返回:range(0, 10) 3:直接给你内存地址,也可能是一个可迭代的 4:直接判断__iter__和__next__在数据dir方法里 可以用来判断对象可迭代或者迭代器 5:判断是一个迭代器或者可迭代对象是为了使用for循环(迭代器和可迭代对象才能使用for循环) 遇到一个新的变量,不确定能不能for循环的时候,就判断它是否可迭代 6:[].__iter__().__iter__().__iter__().__iter__().__iter__() 这样iter迭代一百次一千次还是返回的是原来列表里面的迭代器,从原列表取值——>可以认为 迭代器.__iter__()方法没有效果 6:for循环的执行 当我们for i in l: for循环遍历一个可迭代对象l的时候,内部会去调用这个可迭代对象l的__iter__()方法 1:iterator = l.__iter__() 拿到一个iterator迭代器 2:iterator.__next__() 后面循环的时候每拿到一个值i都是通过 迭代器.__next__()方法往迭代器里取值 3:取到报错的时候自动结束循环 这就是迭代器做的事情,for循环的本质就是迭代器(对象调用__iter__方法转变成迭代器然后__next__往迭代器里依次取值出来) l = [1, 2, 3, 4] for i in l: pass iterator = l.__iter__() iterator.__next__()
7:迭代器的好处(方便,不需要关注取的值在哪些位置上,能取出来就行了): 1:从容器类型中一个一个的取值,会把所有的值都取到。 2:节省内存空间 迭代器并不会在内存中再占用一大块内存,而是随着循环每次生成一个或者每次next给我一个 range() ---使用迭代器可以节省空间 range(10000000) 没有在内存空间真的生成一个这么大的可迭代的列表,很耗费内存 不是直接生成好了的,而是一个个的给数据来使用, ---比如range()和文件句柄f f文件句柄 --返回的f也是一个迭代器(dir(f)内部有__next__方法和__iter__方法) 比如打开一个文件,不是一打开全部文件数据都读取出来,而是一句句话去读取的 ---为了节省内存空间
迭代器只关系当前这个数据和下一个数据的状态,不关心已经取完的以及还没有取完的其他的
所以其他的数据都没有再内存里真正的生成,只是有当前这个数据和下一个数据的状态
8:模仿for循环写一个使用迭代器挨个取值例子,l=[1,2,3,8,9] 不使用for循环遍历在列表中依次取值 1:先把l变成一个迭代器 iterator=l.__iter__()如下 2: 我们自己使用while循环iter模拟一个迭代器,for循环更简单,且自己处理了报错异常
(迭代器走到最后一个值,后面没有新的值自动抛出StopIteration异常,所有这里我们使用try except处理报错) try,except处理异常 l = [1, 2, 3, 8, 9] iterator = l.__iter__() try: while True: res = iterator.__next__() print(res) except StopIteration: pass
9:python中双下划线的方法叫双下方法,这种方法都是已经写好的c语言的代码然后可以通过不止一种方式能够调用它
# 双下方法:带双下划线的方法,一般是很少调用的方法,通常通过其他语法来触发的 # 比如我们python中常用的加法add print([1].__add__([2])) # 返回[1, 2] print([1] + [2]) # 返回[1, 2]
# 列表之间可以相加的,[1]+[2]这里实际执行的是[1].__add__([2]) ---》 语法 # 代码只认识函数代码,不认识符号, 1+2 计算加法,其实底层就是分析成__add__函数来进行加法运算 --- # 内部调用这个函数,且加的值当作参数传递 --》函数计算返回一个结果 ---python解释器的作用 # 把1+2这行代码解释成了要去调用一个函数处理然后返回一个结果 # 数字之间的加减乘除都是内部做了函数的操作 ----双下方法(一般不直接调用的),不带下划线的基本都够用了
iter和next双下方法可以通过for循环语法触发的,for循环一个对象本质上就是先调用iter方法转化成一个迭代器然后调用next方法从迭代器里取值
__add__方法是通过+ 号触发的
4:生成器:只要含有yield关键字的函数都是生成器函数
生成器(处理庞大数据量需要用到生成器,不然内存gg) 生成器的本质就是迭代器(生成器有__iter__和__next__方法) 生成器的表现形式(python提供一种语法让我们能够实现生成器的功能) 1:生成器函数 写一个生成器函数, 2:生成器表达式 生成器函数:(生成器和生成器函数不同,生成器函数是产生生成器的(也就是产生一个迭代器)) 含有yield关键字的函数就是生成器函数 生成器函数的特点(和普通函数的区别): 调用函数的时候函数不执行,返回一个生成器(即是生成器也是迭代器) 每次调用next方法的时候会取到一个值 直到取完最后一个,再执行next会报错 写生成器实现:有一个文件,从文件里分段读取内容 readline read(10) 在读出来的内容前面加上一个'***',再yield返回给调用者 def generator(): # generator()这就是一个生成器函数了 for i in range(20): yield '哇哈哈%s'%i # 生成器的核心:1:生成数据的过程被其他人返回使用生成数据(封装成函数) 2:不能把所有的数据都生成后再返回--浪费时间和内存 (采用生成一条数据就返回一条数据yield) g = generator() # 调用生成器函数得到一个生成器 # print(list(g)) ret = g.__next__() # 每一次执行g.__next__就是从生成器中取值,预示着生成器函数中的代码继续执行 print(ret) num = 0 for i in g: # for 循环取值 for循环可以控制数目(控制一一次性取出多少数据) num += 1 if num > 50: break print(i) print(list(g)) # 生成器g强转为list列表,一个个从生成器取出来数据,转列表会从生成器里全部取完然后生成一个列表--这样操作耗内存 从生成器中取值的几个方法 1:next 2:for 3:数据类型的强制转换 : 占用内存,一次性生成全部数据(一般不用)
假设有一个需求:需要生成自定义的200w个字符串,如:'wahaha%s....' 这时候使用迭代器搞不定了:需要使用生成器 写代码的时候如果需要产生大量的数据,但是不希望这些数据一次性在内存里生成 还需要处处使用它,这种情况可以考虑使用自己写迭代器(自己写的迭代器函数这个东西就叫生成器) 生成器形成方式: 生成器的本质还是迭代器(生成器是自己写的迭代器) 1:生成器函数 ---本质上就是我们自己写的函数 2:生成器表达式 生成器和迭代器特点: 特点:惰性运算 默认不返回值,找他要值一次返回一个,懒惰,不找他要不干活 同一个迭代器,从头到尾从中取值只能取一次, 不找他要值(__next__)不干活 生成器中数据只能取一次,取完就没有了 惰性运算,懒惰性质,不找他取值,他就不工作
1:生成器函数 只要是含有yield关键字的函数都是生成器函数 yield和return只能用在函数内部,函数外不能用,yield和return不能共用 # 生成器函数:执行之后会得到一个生成器作为返回值(生成器函数里面的代码块暂时不会运行) def generator(): # generator定义一个生成器函数 print(1) yield 'a' ret = generator() print(ret, dir(ret)) # <generator object generator at 0x00000221FB8B1D58> # 上面运行generator这个生成器函数后返回一个对象赋值给ret,这个ret指向的对象就是生成器 # 返回的ret生成器对象既有__next__()方法也有__iter__()方法, # 所以ret是一个迭代器,所以能用__next__方法取值,也能for循环 print(ret.__next__()) # 输出:a 上面代码执行步骤: 1:def generator(): 在内存放一个generator生成器函数 2:generator() 函数调用,生成器函数在调用执行的过程当中里面的代码一行也不会执行 函数内部看到有个yield就会直接返回一个生成器对象,所以ret拿到的是一个生成器 3:生成器对象ret既有__next__()方法也有__iter__()方法, 说明这个ret生成器还是一个迭代器
(本质上生成器函数调用后返回的就是一个生成器,也是迭代器,生成器对象就是迭代器) 通过迭代器取值方法从生成器里取值, 4:print(ret) 打印生成器对象 5:当执行 生成器对象.__next__() 的时候,才第一个去触发生成器里面的代码执行 6:触发生成器里面的代码执行就会执行:print(1)和 yield 'a'(将字符串a返回出来) # 上面就是最简单生成器函数,
# 生成器函数yield的使用:
yield 和return 都能把后面的值返回给函数外部,
和return不同的是,return之后会直接结束一个函数,而yield不会直接结束函数,
yield能在一个生成器函数内部多次使用
def generator(): print(1) yield 'a' print(2) yield 'b' g = generator() # 返回一个生成器g ret = g.__next__() # 生成器内执行print(1)这行所以终端打印1 print(ret) # 输出:a (ret拿到生成器第一个yield后面的返回值"a")
生成器对象调用__next__的时候,从代码从头开始执行: 1:print(1) 2:yield 'a' 返回一个a出来, 有了返回值就返回到生成器外部调用 ret = 生成器.__next__的地方,现在就走ret=的代码, ret接收生成器返回出来的"a",然后再print(ret)
print(g.__next__()) # 这一次终端打印:2 b 这里是第二次调用__next__方法从生成器对象里面取值, 从上一个yield a 代码后开始执行: 1:print(2) 2:yield 'b'(运行到这个yield右边部分的代码结束这次从生成器内取值,然后继续等下次取值)
print(g.__next__()) # StopIteration 再加next就报错了,因为这里迭代器里的元素取完了,没有再yield数据出来了,取不到元素了报错
生成器函数的特性: 生成器generator内部代码的执行受外部控制的,想让你哪里停就哪里停,想接着往下执行就往下执行,yield和next之间的配合 上面生成器函数执行后返回的g是一个生成器,g也是一个迭代器,可迭代,可以for循环 generator()是一个生成器函数,g是一个生成器
生成器g的for遍历 生成器的for循环遍历和用next方法执行的结果一样的(next可以一个个触发yield可以看中间的结果和运行),---迭代器的伟大 def generator(): print(1) yield 'a' print(2) yield 'b' print(3) g1 = generator() g2 = generator() for i in g1: i # 终端打印: 1,2,3 for i in g2: print(i) # 终端打印:1,a,2,b,3
简单练习:使用生成器制造200w个哇哈哈,为了节省内存,使用生成器 def generator(): for i in range(20000000): yield f'wahaha{i:0>7}' g= generator() # 拿到g这个生成器,使用next一个个取值(每次调用一次__next__就取值出来一个哇哈哈) print(g.__next__()) print(g.__next__()) print(g.__next__()) print(g.__next__()) print(g.__next__()) # 当需要生产的数据足够多的时候,那么内存里面不能一次性生成几个g的数据,每一次都需要内存存储数据,速度会慢一些,找内存需要时间 # 生成器不会一次性全部生成这些数据,生成器一边去生成数据,一边处理数据,不会慢的,给一个你就拿去用 for i in range(50): # 如果只想使用50个wahaha就使用__next__拿50个使用 print(g.__next__()) # case2 生成器里取指定的数据:for i in g(生成器):可以从生成器里取值 def generator(): for i in range(20000000): yield f'wahaha{i:0>7}' count = 0 g = generator() for i in g: count += 1 print(i) if count > 50: break # 这个for循环取0-50 一共取出51个数据 print(g.__next__())
# 接着上面的51个数据后面继续取值(取生成器里的第52个数据) 打印:wahaha0000051 # g是一个生成器,生成器和迭代器特点是一样的,会记录你当前走到哪了,以及下一个该走哪了,随时可以向生成器取下一个值 count = 0 for i in g: # 循环后面可以接着循环,继续拿50个数据,想要继续可以要 count += 1 print(i) if count > 50: break
# 第二个for循环取出51个数据,取出第53到103个数据,生成器对象取值不可逆,前面的拿出来了就没了,只能往后走
列表只是一个可迭代的,不是迭代器,想让他迭代起来应该内部转化成一个迭代器 转化成迭代器的过程内部做的,for i in l:的时候生成一个迭代器 后面又for i in l:又生成一个新的迭代器 两次for循环有两次迭代器,第一迭代器循环到一半结束了 第二个迭代器又生成从头开始走, 列表是一个可迭代的对象,for循环的时候自动把可迭代的转化成迭代器,转化了一次,每一次for循环都会生成一个全新的迭代器 # case1: l= [1, 2, 3, 4, 5] for i in l: print(i) if i == 2: break # 输出:1,2 for i in l: print(i) # 输出:1,2,3,4,5 # case1例子类似下面生成迭代器函数,
# 调用两次生成器函数生成的g和g1是两个迭代器,两个迭代器之间取值是没有关系和联系的(类似同一个函数执行两次) def generator(): for i in range(20000000): yield f'wahaha{i:0>7}' g = generator() g1 = generator() print(g.__next__()) print(g1.__next__()) # 列表对象自己调用__iter__函数生成一个迭代器,记录位置继续取值 l = [1, 2, 3, 9, 8, 7, 5, 6, 4, 0] l_g = l.__iter__() # 生成一个迭代器l_g for i in l_g: if i == 3: break print(i) for i in l_g: print(i) # 这里会接着上面从3后面开始取值
写生成器实现:有一个文件,从文件里分段读取内容(可以一行行读)
readline 可以一行行读
read(10) 按字符读或者按照字节读
在读出来的内容前面加上一个'***',再返回给调用者
# 对迭代器再调用__iter__方法会什么效果 # 对迭代器再调用__iter__方法没有效果,返回的还是初始的迭代器 test_l = [1, 2, 3, 4, 5, 6, 7, 8] for i in test_l: if i == 3: break print(i) # 打印:1 2 for i in test_l: print(i) # 打印:1 2 3 4 5 6 7 8 # 如上:列表对象test_l调用两次for循环生成的是2个没有关系的迭代器,从这2个中取值没有任何影响和关联 test_l_g = test_l.__iter__() for i in test_l_g: if i == 3: break print(i) # 打印:1 2 for i in test_l_g: print(i) # 打印:4 5 6 7 8(3没有打印,上面的for循环把3取出了) # 如上:迭代器对象test_l_g调用两次for循环,两次for循环会对迭代器调用__iter__方法
# 因为 迭代器.__iter__()返回的还是原来的迭代器,所以这里两个for循环使用的是同一个迭代器
# 是最初始的迭代器test_l_g,所以两个for循环公用到一个迭代器从里面取值,所以上面第二个for循环会接着第一个for循环后面取值 test_l_g = test_l.__iter__() test_l_g1 = test_l_g.__iter__() test_l_g2 = test_l_g.__iter__() for i in range(2): print(test_l_g1.__next__()) # 打印:1 2 for i in range(5): print(test_l_g2.__next__()) # 打印:3 4 5 6 7 print(test_l_g, id(test_l_g)) # <list_iterator object at 0x000001DDEBF99C70> 2052658404464 print(test_l_g1, id(test_l_g1)) # <list_iterator object at 0x000001DDEBF99C70> 2052658404464 print(test_l_g2, id(test_l_g2)) # <list_iterator object at 0x000001DDEBF99C70> 2052658404464
# 这里测试和上面类似,对迭代器对象调用__iter__()方法返回的还是初始的迭代器对象,
所以这里test_l_g和test_l_g1和test_l_g23个变量都指向一个迭代器对象
# 一个监听文件输入的例子 # 用户一边往文件里输入,我一边就能通过写的程序看到用户输入了什么 # 这里可以使用生成器来实现 # case1:监听文件的输入 f = open('file', encoding='utf8') # for i in f: for循环这样不可以的,读完这个文件,这个for循环就结束了,不能持续监控,到点就结束了 # 再往文件输入内容读不出来了,不能使用for循环,read,readlines,这都是一次性读 # 一次性读的特点再后面有什么东西就没有了 while True: line = f.readline() # readline的特点每一次只读一行,不知道结束,读到最后不会报错,显示为空 if line: # 不为空才打印,为空不打印 print(line.strip()) # 一直读取,程序没有结束,读出来的东西都是空 # 在文件最后面写也会一直读取---循环一直往最后一行读取 # case2:函数监听文件的输入,传参返回一行行的值,返回生成器---我的电脑似乎不行 def tail(filename): f = open(filename, encoding='utf-8') while True: line = f.readline() if line.strip(): # 一行去掉回车为空也不打印 # 每次输入新的一行敲个回车再输入,输入回车也不为空,会把回车打印出来 yield line.strip() g = tail('file') # g拿到一个生成器 for i in g: # 不断循环生成器,拿到监听的的内容 for循环等于循环使用next方法 if 'python' in i: print('***', i)
case2代码分析:
case2里tail是一个生成器函数,调用生成器函数返回一个迭代器对象g
for循环遍历一个迭代器对象就是循环从迭代器里取值,
这里for循环是循环从生成器里取值,每次取一个值那么就会执行生成器里的函数直到yield出一个对象或者后面没有yield了拿不出值出来了那么就越界报错了
这里for循环迭代器g,循环从生成器函数tail里取值,取值的本质就是执行一段代码,每取值一次就是运行生成器里的代码直到碰到一个yield
上面生成器里的代码是个while死循环,每次读取一行yield返回出来给for循环这里
for这里一直取,tail就一直返回,文件行数被最后一行被取值完后,for循环这里还会最后一次找g生成器取值,
此时line = f.readline()读取出来的line为空,进入不了if循环里面,那么就一直不会yield出来数据,
此时生成器函数又是一个while死循环,而且for循环最后这一次取值必须碰到yiled才结束,所以这里会一直
运行生成器函数while循环里的line = f.readline()一直读取文本,直到读取到不为空的文本才会yield出来一个数据给for循环
所以文本取值完了,最后一次for循环这里取值的时候生成器函数里面会现如死循环一直f.readline()读取文本数据,读取到不为空的文本yield返回出来
5:生成器函数进阶
生成器:已经yield了之后,还有值要计算,但是计算的值的代码后面没有yield了就会报错 yield后面的结果就拿不到 def generator(): # 生成器函数能往外吐两个值(两个yield) print(123) yield 1 print(456) yield 2 print(789) g = generator() # 调用函数拿到一个生成器(函数generator里面啥都不执行) ret = g.__next__() # 执行print(123)和yield 1 拿到返回值1赋值给ret print('***', ret) # 上面终端打印:123 *** 1 ret = g.__next__() # 从上个yiled结束地方才开始继续执行:执行完print(456)和yield 2执行结束 print('***', ret) # 终端打印:456 *** 2 ret = g.__next__() print(ret) # 终端打印:789后 报错:StopIteration # 这里再执行g.__next_的时候,又触发从生成器函数上一个yield代码后面接着执行 # 正常情况必须执行到下一个yield结束才行,这里没有下一个 yield # 输出789 找不到下一个yield返回出来值就报错了(因为每次调用__next__必须拿到一个返回值才不会报错)
2:生成器的另外的使用方式 g.send('hello') send也是生成器里获取值的方式 send:在获取下一个yield值的同时,在上一个yield的地方传一个值进来,这就是send的功效 send 获取下一个值的效果和next基本一致(只是在获取下一个值的时候,给上一yield的位置传递一个数据) 使用send的注意事项 1:第一次使用生成器的时候 是用next获取下一个值(第一个yild前面没有yiled可以传递值 一开始执行代码在print(123)这里,没有上一个yiled可以接受send传递的值) 第一次使用next走到第一个yiled的地方,下一次用send给第一个yild传值 2:最后一个yield不能接受外部的值,执行最后一个send从 content=yield 1 print(content) print(456) yield 2 上面代码执行,返回2了 执行第一个next的时候:print(123) ----走到yield 1 再执行send的时候从content= print(content) print(456) yield 2 ---执行到这个yiled yield 2 这里最后一个yield不能传值,因为后面没有值和yield了,不能再执行一个next或者send了
执行一次next或者send必须要有对应的yield返回值才不会报错,这里最后一个yield 2被第二次取值的send拿到返回值了
如果再send一次或者next一次会报错,因为第三次取值取不出来的,没有第三个yiled返回对象了
所以无法传值了 执行最后一个yield了一般后面不应该有代码了, def generator(): print(123) content = yield 1 print(content) print(456) yield 2 g = generator() ret = g.__next__() print(ret) # ret这里拿到第一个yield返回出来的:1 ret = g.send('hello') # send的效果和next一样:这里传一个hello进去,被content=yield 1的content拿到 print(ret) # ret这里拿到第二个yield返回出来的:2 # ret = g.__next__() 执行第一个next的时候,代码停留在yield 1这个位置结束,停留在这里 # 下一次ret=g.send('hello') 执行这个的时候,send一个值过去,yield 1这个停留的位置就接收了, # 变量content等着接收 ---每次遇到xxx=xxxx 往往先执行右边的,再执行左边的赋值 # 执行了yield 1的时候返回出去了,来不及执行左边的content= ,程序停在这里等着, # send一个值来之后,content这里可以接收,接收后继续执行next了 # send和next的效果一一样,在执行next的同时传了一个值到函数里---- # content接收传递的值,继续往下执行,执行到下一个(第二个)yiled,把yiled内容返回回来
def generator():
print(123)
content = yield 1
print(content)
print(456)
zzz = yield 2
print(zzz)
g = generator()
ret = g.__next__()
print(ret)
ret = g.send('hello')
print(ret)
ret = g.send('第三次') # 这里终端会打印:第三次,但是第三次往生成器取值拿不出值来了就报错:StopIteration
如果硬要yeld后面还要执行,最后使用yield 返回一个空, 最后一个yield支持最后的next和send的执行 arg= print(arg) yield 这些代码才会执行 def generator(): print(123) content = yield 1 print(content) print(456) arg = yield 2 print(arg) yield g = generator() print(g.__next__()) ret = g.send('李二狗') print(ret) res = g.send('傻逼') print(res) 打印如下: 123 1 李二狗 456 2 傻逼 None
获取移动平均值:每一次给个新的值然后连着前面的值计算出一个平均值 10 20 30 10 10 15 20 17.5 avg=sum/count case1: def wrapper(func): def inner(*args, **kwargs): res = func(*args, **kwargs) res.__next__() return res return inner @wrapper def average(): aaa = yield "本次是生成器的初始化" count = 1 sun = aaa while 1: bbb = yield sun / count count += 1 sun += bbb a = average() print(a.send(10)) print(a.send(20)) print(a.send(30)) case2: def average(): sum = 0 count = 0 avg = 0 while 1: num = yield avg sum += num # 总数+10 count += 1 # count+1 avg = sum / count g = average() # 返回一个生成器 avg = g.__next__() # 生成器第一个执行个next的,不能第一个就执行send print(avg) avg = g.send(10) # 传了个10给num print(avg) avg = g.send(20) print(avg) avg = g.send(30) print(avg) # 1:g=average() 返回一个生成器 # 2:avg=g.__next__() 从sum=0执行到第一个 yield avg停止执行,num=现在等着下一次send传值,avg=0 返回0 # 3:avg=g.send(10) 从第一个yield avg执行到第二个yield avg 第一个num接受10 avg=10/1 =10 # 经过一次while循环返上去返回avg=10,在yield avg又停止运行,num等着传参 # 4:avg=g.send(20) ---如上 完美轮询
5:带预激生成器功能的装饰器 def wrapper(func): # 定义wrapper装饰器函数(作用是获取到生成器对象后做一步.__next__激活操作,没做其他事情 # 为了拿到生成器后直接就能send数据就能拿到结果)--为了少些一个next,所以这里next写到装饰器里面去了 def inner(*args, **kwargs): g = func(*args, **kwargs) # g = average() g.__next__() return g return inner @wrapper def average(): sum = 0 count = 0 avg = 0 while True: num = yield avg sum += num # 10 count += 1 # 1 avg = sum / count avg_g = average() # ===> inner ret = avg_g.send(10) print(ret) ret = avg_g.send(20) print(ret) # 代码执行步骤: 1:def wrapper(func): wrapper函数放内存里 2:@wrapper average=wrapper(average) wrapper(average)函数调用 1:average函数传给wrapper局部空间的func变量 2:wrapper函数局部空间内定义inner变量 3:return inner 3:average接受inner 此时average指向inner函数 4:avg_g = average() 调用,实际上执行inner 1:g = func(*args,**kwargs) 执行func就是执行的average func函数执行拿到一个生成器,赋值给g 2:g.__next__() 激活生成器走里面的代码 sum = 0 count = 0 avg = 0 while True: yield avg 走到这个yield avg生成器代码停止在这里等待下一次激活 3:return g 返回生成器,被avg_g接受 5:ret = avg_g.send(10) avg_g是一个生成器,send继续激活avg_g这个生成器执行 这时候装饰器里面的代码不会执行了,只执行average():这个生成器里面的函数代码 10 传传递进去被生成器里被num拿到,然后取出while循环里第二次的yield出来的avg平均值
---一直这样循环send值进去,拿值出来
python 3版本 yield from a的用法 当要循环一个结果,把里面的结果分个返回的时候直接使用:yield from a就行了 # case1 def generator(): a = 'abcde' b = '12345' for i in a: yield i for i in b: yield i g= generator() for i in g: print(i) # 从字符串a和字符串b里拆开拿到一个个字符 # case2 ------》 case2=case1 def generator(): # case2和case1的代码效果一模一样 a = 'abcde' b = '12345' yield from a yield from b g = generator() # 和case1的效果一样,使用yield from a for i in g: print(i) # yield from a一般是用在生成器里面的办法,从一个序列a里取值,不需要一个个返回 # 直接 yield from a 就会把a这个序列拆开后一个个yield返回出来,外面就能一个个的接受到 ---------yield from a的作用
1:生成器send的作用 send的作用范围和next一模一样 第一次不能用send 函数中的最后一个yield不能接受新的值 2:计算移动平均值的例子 3:预激生成器的装饰器的例子 ---生成器的next激活 4:yield from
生成器的表达式 生成器对象形成的方式:生成器的本质还是迭代器(生成器是自己写的迭代器) 1:生成器函数执行后返回生成器对象:生成器函数本质上就是我们自己写的函数 2:生成器表达式也可以产生一个生成器 1:列表解析:列表生成式,列表推导式 for 循环:每一次for循环得到一个值,拿到的这个值希望放到列表是以什么样的形式就写for前面 [i for i in range(10)] ---for循环每拿到一个i就放列表里, [i*i for i in range(10)] ---求平方 # case 1 egg_list = ['鸡蛋%s' % i for i in range(10)] # 列表推导式,列表解析 print(egg_list) # ['鸡蛋0', '鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4', '鸡蛋5', '鸡蛋6', '鸡蛋7', '鸡蛋8', '鸡蛋9'] # 获取鸡蛋列表 # case 2 #我们自己也能实现,case1简单语法,列表推导式 egg_list2 = [] for i in range(10): egg_list2.append(f'鸡蛋{i}') print(egg_list2) # case 3 老母鸡 = (f'鸡蛋{i}' for i in range(10)) # 返回生成器 print(老母鸡) # <generator object <genexpr> at 0x000001CA10C04780> for 蛋 in 老母鸡: # 让老母鸡下蛋,for循环让蛋全下了 print(蛋) # case3 如果把生成器"老母鸡"使用list转化成列表那么会内存,
# case3使用生成器来for循环,代码里从始至终就一个蛋 # 拿一个蛋销毁一个上一个蛋,不是全部蛋都给出来 生成器表达式 ---产生一个生成器对象的简单写法(生成器函数能满足所有需求) 放在[]列表里面就是列表推导式 放在()小括号里面就是生成器表达式---不是元组 生成器表达式和列表推导式: 1:括号不一样,一个是()一个是[] 2:返回的值不一样 ---- 列表推导式耗内存,一次拿到所有的值 生成器表达式几乎不占用内存(但是不能直接拿到值,使用,需要转化和取值) (写程序更多考虑内存和速度---还是生成器表达式好) res = (i for i in range(10)) print(res) # <generator object <genexpr> at 0x000001F21A591D58> 返回的res就是一个生成器对象 for i in res: print(i) '''''' g = (i * i for i in range(10))
# 上一行代码拿到g生成器对象,
#(i*i for i in range(10))里面的代码还一句话都没有执行,和函数一样的,
# 直到for循环一个个取值,for循环本质是内部相当于调用了next
# 每执行一个__next__(),(i * i for i in range(10))这个生成器对象里的代码的for循环里面的才执行一次
res = g.__next__()
print(res)
python中各种推导式的使用: 1:列表推导式(和生成器表达式写的语法完全一样,只是括号不同) 2:字典推导式 3:集合推导式
4:生成器推导式 没有元组推导式,用小括号()就变成了生成器表达式了 如果有一个元组想去推导,可以使用for循环元组然后推导生成一个列表,生成器,集合 各种推导式 : 生成器 列表 字典 集合 遍历操作 筛选操作 1:列表推导式 [每一个元素或者是和元素相关的操作 for 元素 in 一个可迭代数据类型] 遍历之后挨个处理 [满足条件的元素相关的操作 for 元素 in 可迭代数据类型 if 元素相关的条件] 筛选功能 if条件为true的时候才会触发前面的东西,满足条件的元素相关的操作组成的新列表 符合条件的添加到列表,不符合的我不要 # 30以内所有能被3整除的数 res = [i for i in range(31) if i % 3 == 0] # % 求余print(res) # 30以内所有能被3整除的数的平方 res = [i * i for i in range(31) if i % 3 == 0] # % 求余print(res) # 找到嵌套列表中名字含有两个‘e’的所有名字 names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'], ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']] res = [j for i in names for j in i if j.count('e') == 2] print(res) # for for 先for外面大列表,再for里面的小列表,类似for 循环的嵌套2:字典推导式 # 例一:将一个字典的key和value对调 mcase = {'a': 10, 'b': 34} res = {j: i for i, j in mcase.items()} print(res) # {10: 'a', 34: 'b'} # 例二:合并大小写对应的value值,将k统一成小写 mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3} # {'a':10+7,'b':34,'z':3} mcase_frequency = {k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0) for k in mcase} print(mcase_frequency) # mcase.get(k.lower(), 0) + ,case.get(k.upper(), 0) 找到在这个字典里k这个键的大小写对应后面的值相加
print({"aaa": 13, "aaa": 5}) # {'aaa': 5} print({"aaa": 13, "aaa": 13}) # {'aaa': 13}
上面是字典的一个特性,如果字典里有两个键相同的话,那么只取一个,一般取后面的那个,前面的自动消失
3:集合推导式(和列表推导式规则一模一样),自带结果去重功能(集合里面不会有重复的元素) # 1:计算列表中每个值的平方,自带结果去重功能 squared = {x ** 2 for x in [1, -1, 2]} print(squared) # {1, 4} 自动去重 squared = [x ** 2 for x in [1, -1, 2]] print(squared) # [1, 1, 4] 列表不会去重
生成器函数的特点 调用之后函数内的代码不执行,返回生成器 每从生成器中取一个值就会执行一段代码,遇见yield就停止。 如何从生成器中取值: 1:for :如果没有break会一直取直到取完 ----首选for循环 2:next :每次只取一个 3:send :不能用在第一个,取下一个值的时候给上个位置传一个新的值 #需要send去使用生成器的话需要一个预激活(next方法激活)--放在装饰器里面预激活 4:数据类型强制转换 :会一次性把所有数据都读到内存里 生成器表达式 (条件成立想放在生成器中的值 for i in 可迭代的 if 条件)
一些经常碰到的生成器面试题 面试题1:
def demo(): for i in range(4): yield i g = demo() # 返回生成器g,一个生成器里面的内容只能取一次,不找他要的时候不会取的 g1 = (i for i in g) # g1是一个生成器,里面代码没干活--从生成器里取值才for循环的,取一个for循环一次 g2 = (i for i in g1) # g2也是一个生成器 # print(list(g)) print(list(g1)) # list(g1):这里需要把g1当中所有的数据取值出来,g1也是个生成器,g1本来没有数据的,g1需要找g这个生成器取值 # g1要一个值,生成器g里面循环代码拿到一个0,g1拿到这个给前面的list,再1,2,3也这样给过去 print(list(g2)) # list(g2)需要从g1里面取值,g1是一个生成器,g1里面刚从g生成器拿到数据,给了list(g1) # list找g1要了g1生成器全部的数据才能转化成列表,g1已经重头取到尾被取完了,g2从g1中取不到值了
# 所以这里list(g2)返回的是一个空列表 # case2 def demo(): for i in range(4): yield i g = demo() g1 = (i for i in g) g2 = (i for i in g1) print(list(g2)) # 这样写,不先执行list(g1) # list(g2),list从g2里面取值,g2需要从g1里面取值,g1需要从g里面取值,那么生成器g找生成器函数demo每次循环一次给值
# 因为list(g2)这里取值是从g2取值,g2从g1取值,g1从g取值,g从生成器函数demo取值的(本质上所有的取值都来着生成器g) # 如果中间 g 和 g1 g2任何一个生成器被取值取空了那么表示g这个生成器空了,那么list(g2)都取不到值 # case3 def demo(): for i in range(4): yield i g = demo() g1 = (i for i in g) g2 = (i for i in g1) print(list(g)) # list(g) list把g里面的值取空了,g1取值依赖g g2取值依赖g1间接依赖g 现在g现在空了
# 那么list(g1)这里往g1取值间接找g取值,g空了所有这里是个空列表
# 同理list(g2)这里找g2取值,g2间接找g1取值,g1间接找g取值,g空了,返回空列表 # 原因就是迭代器g和g1,g2里面的值只能取一次 print(list(g1)) print(list(g2)) # 面试题:2 def add(n, i): return n + i def test(): for i in range(4): yield i g = test() # 调用生成器函数,返回一个生成器 g=[0, 1, 2, 3] for n in [1, 10]: g = (add(n, i) for i in g)
# n = 1 n=1时候,里面的代码没有执行,相当于执行了g=(add(n,i) for i in test())这段代码产生一个新的生成器赋值给g # g=(add(n,i) for i in test()) 因为是生成器,n=1的时候这句话并没有真的执行,list(g)才执行这行代码 # list(g)才去找他要值,list(g)取值的这时候全局空间的n=10了 # n = 10 n=10时候,里面的代码没有执行,相当于生成g=(add(n,i) for i in (add(n,i) for i in test())) # g重新赋值成新的生成器代码 记录n=10 生成器代码 # g = (add(n, i) for i in (add(n, i) for i in test())) # g=(add(n,i) for i in (add(n,i) for i in test())) # g=(add(n,i) for i in [10,11,12,13])) print(list(g))
# 除了这行代码,从来没有从生成器里取过值,这里调用list(g) 才开始正式从g里取值 # g从g=(add(n,i) for i in (add(n,i) for i in test())) 这个生成器里面取值 # 此时:i = [0,1,2,3] n=10
# 所有最终打印[20, 21, 22, 23]
g2 = test() for n in [1, 10, 5]: g2 = (add(n, i) for i in g2) print(list(g2)) # [15, 16, 17, 18] # 生成器相关的题目看到for循环就拆开算:如下,不看for循环里面,因为for循环里面的生成器返回一个生成器压根就不会运行 # 生成器表达式不取值就不干活,list(g2)才开始干活, # n=1 # (add(n,i) for i in test()) # n=10 # (add(n, i) for i in (add(n, i)for i in test())) # n=5 # (add(n,i) for i in (add(n,i) for i in (add(n,i) for i in test()))) # i=0,1,2,3 n=5 add(n,i)=5,6,7,8 # i=5,6,7,8 n=5 add(n,i)=10,11,12,13 # i=10,11,12,13 n=5 add(n,i)=15,16,17,18
# 所有全部的生成器取值都是在n=5的时候运行的,最终打印[15, 16, 17, 18]
6:简单练习
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
3.处理文件,用户指定要查找的文件和内容,将文件中包含要查找内容的每一行都输出到屏幕 # 使用迭代器编程 def check_file(filename, aim): with open(filename, encoding='utf8') as f: # f文件句柄, 句柄:handler 操作杠, 拿这个东西就可以操作跟他相关的东西, for i in f: # 这个东西就是句柄,操作文件全部依靠f,f是文件处理的操作杆 if aim in i: yield i g = check_file('001 复习', '生成器') for i in g: print(i.strip()) 4:写生成器,从文件中读取内容,在每一次读取到的内容之前加上‘***’之后再返回给用户 def read_file(filename): with open(filename, encoding='utf8') as f: for i in f: yield f'***{i}' g = read_file('001 复习') for i in g: print(i.strip())
7:python中常用的内置函数 一共68个
python中内置函数一共68个,几乎很常用,启动python解释器内置函数就已经在了
不def直接加()就可以使用的都是内置函数
比如:print input len type open tuple list int str bool set dir id
1:作用域相关的内置函数 global和nonlocal print(locals()) # 返回本地作用域中的所有名字(locals根据当前作用域来打印,如果在函数里面调用就打印这个函数局部空间的所有名字) print(globals()) # 返回全局作用域中的所有名字 # global a # 声明一个函数里变量,这个变量函数内部的修改对全局有效 # nonlocal a # 声明一个函数里变量,这个变量函数内部的修改对离他最近一层的函数内部变量修改生效的 # global和nonlocal是关键字,不是内置函数,都是和变量的实际作用域相关的 # globals()和locals()是查看不同作用域当中的所有名字
2:和迭代器/生成器相关的内置函数 next,iter,range 1:next(迭代器) 迭代器.__next__() --》驱使一个迭代器继续取下个值 next(迭代器) -->next这种方式也能驱使 迭代器.__next__() --双下划线next是属于迭代器对应这个类的方法 next(迭代器) --这个next是内置函数 但是next(迭代器)和 迭代器.__next__()做的事情完全一样的事情,且真正完成这件事情的是__next__() 这个__next__()才是迭代器函数才是本质 def next(迭代器): # 我们自己也能定义类似next内置方法,--python源码里其实也是类似这样实现的 迭代器.__next__() # 内部真的有next这个函数的存在,存在在内置的名字空间里面,调用就行,需要传参,拿到参数找__next__() # 里面还做了一些其他操作,因为迭代器类的内部有__next__方法,才能使用内置的next()函数去调用使用 [].__len__() # 正常情况下不这么写,len([])来直接算长度 len([]) # 调用len([])内部走的[].__len__() 这个方法, 2:iter(可迭代对象) --->返回一个迭代器 iter(可迭代对象) 等同 可迭代的.__ietr__() 这样调用
不常用带双下划线的因为用起来比较长,麻烦,使用内置函数就行 3:range(10) range(1,11) range(1,11,2) # 间隔取值 range()返回的对象一定可迭代的,range()返回的结果只是一个可迭代的,而不是一个迭代器 ---和迭代相关的 print(dir(range(1, 11, 2))) # 查看内部只有__iter__方法没有__next__方法
其他的一些内置函数 # 1:dir() 查看一个数据变量(对象)所拥有的所有方法,所有想到的变量都可以,需要帮助想不起方法的时候,可以使用查询方法 print(dir([])) print(dir(1)) # 2:callable() 传参一个对象,如果对象是函数名可以被调用返回True,变量不能够被调用返回False(判断对象是函数还是值) print(callable(print)) # True 查看print是不是能够被调用,True是可以的 a = 1 print(callable(a)) # False a # 3:help() # 查询帮助 help(str) # 直接打印和str字符串对象相关的所有方法,str的简介---类似dir # dir只是查看方法名,help能查看方法名和一些用法(参数,返回值,注释打印出来) # 4:import time 导入一个模块 表面上是使用的关键字import,但是内部还是调用了__import__()函数 import time # time = __import__('time') # 这样使用__import__()函数也能导入time这个库函数 print(time.time()) # 1612768793.1135015 某个方法属于某个数据数据类型的变量,就使用 .调用 如:list.append() 如果某个方法不依赖任何数据类型,就直接调用, 如:print('asda') 能直接调用的只有内置函数和自定义函数,剩下的所有的内容全部都是属于某一个数据类型的方法 print(dir(list)) # 打印一个列表,列表里打出来名字都可以使用 list.方法 来调用 list.append('xxx') list.__len__() # list.append()和list.__len__()是属于list的,除了list其他数据类型都调用不了 # 而print等内置方法不依赖任何数据类型,直接就可以使用的,解释器起来就能用 # __import__()也一样,直接可以使用,不依赖任何数据类型 前面不需要 .调用 # 直接 print('asda') 括号里传数据调用就行 l = [1,2,3,4] l.append(5) # l 有一点像列表句柄,---其实就是列表句柄
# 5:open() 和文件操作相关的方法 # f.read()和f.write()说明read方法和write方法都只属于f # f = open() # f.read() # f.write() f = open('001 复习') print(f.writable()) # False writable能不能写 print(f.readable()) # True readable能不能读
# 6:和内存相关的方法 hash 和id # id 代表变量所在的内存地址 # hash 1:对于相同可hash数据的hash值在一次程序的执行过程中总是不变的 ---一个算法 2:字典的寻址方式 print(hash(12345)) # 12345 print(hash('askdjaskldjaskda')) # 1683974452915746404 print(hash(('1', 'aaa'))) # 7108970239209198825 print(hash([])) # TypeError: unhashable type: 'list' 不可哈希的 # 列表,字典,集合等都是不可哈希的数据类型,其他的数据类型比如元组等都是可哈希的数据类型 # 能执行hash函数就是可哈希的, # 执行完hash函数返回的值(一次程序的执行的当中,对同一个字符串或者数字(可哈希的数据类型)) 永远不会变,一次程序执行中不会变,但是每一次程序执行中是会变化的 # hash - 对于相同可hash数据的hash值在一次程序的执行过程中总是不变的 ---一个算法 # 作用:在一次程序的执行过程当中想对一个字符串(很长),字符串可以很长,但是哈希值有长度范围的 # 数字的话不计算哈希值,把可哈希的数据类型转化成一串数字, ----哈希 # 字典是key:value存储的,字典的查找速度很快,一次寻址找到的,拿到key一次瞬间就能找到value # key value # 有一个key,对key进行一次哈希拿到一个哈希值(一串数),一串数可能标识与一个内存地址 # 把value放到标识的内存地址里,执行一次程序的时候,一个字典,拿着这个字典aict{key} # 想找value,直接对key进行一个hash,拿到hash值,这个hash值就是value的内存地址,就到这个地址上去找value # 这就是速度寻址块的原因,拿到key就能找到value ---hash值在一次程序执行的过程当中总是不变的, # 这就是key为什么不能重复且值要是可哈希的,因为一个key只能对应一个哈希值,只能对应一个地址,地址只能存一个value # 哈希之后才能拿到一个地址,拿到地址才能存value
7:输入和输出相关的 print 和input # 1:input ret = input('提示:') # 阻塞代码,输入回车才会结束,输入值被input函数获取到,并且作为返回值返回 print(ret) # 2:print #def print(self, *args, sep=' ', end='\n', file=None): # print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False) # file: 默认是输出到屏幕,如果设置为文件句柄,输出到文件 # sep: 打印多个值之间的分隔符,默认为空格 # end: 每一次打印的结尾,默认为换行符 # flush: 立即把内容输出到流文件,不作缓存 print(1, 2, 3, 4) str = '我们的祖国是花园\n' # 文档读出来的虽然看不见但是最后有个\n 回车(换行) # 每一次print都会自动换行, 每行后面有个\n换行,print默认自带一个换行 # 会多换行一次 print(str) print(str) print(str, end='') # 指定输出的结束符 end=''按照关键字传参,指定end为空,让print不自带换行 print(str, end='') print(1, 2, 3, 4, 5, sep='|') # 指定输出多个值之间的分隔符 # print输出多个传入的参数,输出的时候默认sep=' '空格分开,也可以自己进行关键字传参 # print() # 为什么print能输出到屏幕上,对于解释器来讲输出到屏幕的内容就是一个文件而已 # 文件里输入内容,我们人看着输入的 f = open('file', mode='w', encoding='utf8') print('袁文韬', file=f) f.close() # file 参数可以把内容输出到指定的文件,本来默认打印到屏幕文件,给文件句柄,把所有输出的内容写到文件里面去 # 依靠print 打印进度条 import time for i in range(0, 101, 2): # 取值0,2,4,6,8.... time.sleep(0.1) # 每次停留0.1s char_num = i // 2 # 打印多少个'*' per_str = '\r%s%% : %s\n' % (i, '*' * char_num) if i == 100 else '\r%s%% : %s' % (i, '*' * char_num) print(per_str, end='', flush=True) # \r 可以把光标移动到行首(接着行首打印)end=''让输入不换行,所有这里后面的打印覆盖前面的打印,一直只打印一行 # 三元运算符: # i=100时候换行,i!=100不换行 # %% %转义%,打印一个% # prgress bar 专门做打印进度条的 # print的flush参数 # 当人为往文件写文件的时候,先缓存存一下,看还有没别的写的东西,现在写了一条,马上又写了一条 # 两条合着一起写到文件里---省事 ---默认不是立即执行 # 如果设置flush值,flush=True的话,给就马上写,再给再写---不会凑一起写入 ---执行就马上写,不会缓存等待
字符串类型代码执行相关的内置函数:eval,exec,compile 1:eval 有返回值 exec和eval都可以执行 字符串类型的代码 eval一般情况下不要使用, 2:exec 没有返回值 exec和eval都可以执行 字符串类型的代码 exec和eval都可以执行 字符串类型的代码 eval有返回值 —— 适合处理有结果的简单计算 exec没有返回值 —— 适合处理简单流程控制 eval只能用在你明确知道你要执行的代码是什么,---写死的 3:compile 将字符串类型的代码编译。代码对象能够通过exec语句来执行或者eval()进行求值。 :compile 负责将代码转变成字节码的--编译 具体执行还是需要exec和eval去做 compile参数说明: 1. 参数source:字符串或者AST(Abstract Syntax Trees)对象。即需要动态执行的代码段。 2. 参数 filename:代码文件名称,如果只要不是从文件读取代码就直接写一个空字符串就可以了 从文件读取代码编译执行的,需要写一个filename,否则写一个空字符串就行 ,如果不是从文件读取代码则传递一些可辨认的值。 当传入了source参数时,filename参数传入空字符即可。 3. 参数model:指定编译代码的种类,可以指定为 ‘exec’,’eval’,’single’。 当source中包含流程语句时,model应指定为‘exec’; 当source中只包含一个简单的求值表达式,model应指定为‘eval’; 当source中包含了交互式命令语句,model应指定为'single'。 exec('print(123)') # 输出:123 eval('print(123)') # 输出:123 print(exec('1+2+3+4')) # 输出:None print(eval('1+2+3+4')) # 输出:10 # exec和eval都能执行一段 看起来是字符串的但是这个字符串符合python语句的--都可以执行 # exec没有返回值,exec('1+2+3+4')这里也执行了,执行后没有返回结果 code = '''for i in range(10): print(i*'*') ''' exec(code) # 执行code字符串代码 ,带流程的没有返回值--一般使用exec # eval 一般使用在不带流程控制,有返回值的 compile 内置方法 1:exec 流程类的 2:eval 计算类的 3:single 计算类的 # eval和exec都是执行代码,计算机不认识写的print等代码,先经过一次翻译,把写的代码变成字节码,然后再去执行 # 假如同样一段代码很长:code 假如有500行,同时执行这段代码5次,每次都需要编译。编译后再执行--编译的时间浪费了 # compile1代码一次编译多次执行 -方便节省时间 code1 = 'for i in range(0,10): print (i)' compile1 = compile(code1, '', 'exec') # compile编译成exec模式的方式 exec(compile1) # 简单求值表达式用compile编译成eval执行 code2 = '1 + 2 + 3 + 4' # compile2 = compile(code2, '', 'eval') res = eval(compile2) print(res) # 10 # compile交互语句用single code3 = 'name = input("please input your name:")' # 交互类型的编译的使用使用single来编译 compile3 = compile(code3, '', 'single') exec(compile3) # 执行时显示交互命令,提示输入 print(name) name # 执行后name变量有值 "'pythoner'"
数字相关的数据类型转化相关的内置函数:
bool,int,float,complex(这4个内置函数只在数据类型强制转化的时候使用,其他不使用) 1:bool() 2:int() 3:float() 4:complex()
数字相关进制转化的内置函数:bin,oct,hex 1:bin() 二进制 2:oct() 八进制 3:hex() 十六进制 print(bin(10)) # 0b1010 0b表示二进制 print(oct(10)) # 0o12 0o表示八进制 print(hex(10)) # 0xa 0x表示十六进制
数字相关的数学运算的内置函数:abs,divmod,round,pow,sum,min,max 1:abs() 求绝对值的,负的变正的,正的还是正的 2:divmod() div 除法 mod求模,取余数 求除余的方法 3:round 小数的精确,会四舍五入 4:pow 2个参数求幂运算的,传三个参数幂运算之后再取余的结果pow(2,3,3) 2**3 /3 取余 5:sum 求和,参数必须是一个可迭代的, print(sum([1,2,3,4,6])) 计算可迭代的元素之和 print(sum([1,2,3,4,6],2)) 计算可迭代的元素之和再加上最后的2参数 ---没啥意义 stat表示从2开始加起 6:min 计算最小值(可以接受iterable可迭代的元素,也可以接受多个参数) 7:max 计算最大值 print(abs(-5)) # 5 print(divmod(7, 2)) # (3, 1) 求除的结果和余数 7/2 商3余1 print(round(3.14659, 2)) # 3.15 print(pow(3, 2)) # 9 print(pow(2, 3, 3)) # 2 print(sum([1, 2, 3, 4, 6])) print(sum([1, 2, 3, 4, 6], 10)) # print(min(1, 2, 3, 4, 9, 10)) # 1 print(min([2, 3, 8, 9, 1])) # 1 print(min(1, 2, 3, -4)) # -4 print(min(1, 2, 3, -4, key=abs)) # 1 以元素绝对值的结果来判断最小值 print(max(1, 2, 3, 4, 9, 10)) # 10 print(max(1, 2, 3, 4, 9, -10, key=abs)) # -10以绝对值来论最大值,返回的还是原列表的数据,根据绝对值来找
和数据类型强制转化相关的内置函数 1:list() 数据类型强转列表 2:tuple() 数据类型强转元组 ---不可变 数据结构:容器数据类型的:dict list tuple set str(看成字符的集合) 数据类型:int bool str .... tuple dict tuple和dict这个两个很重要,很有特色,tuple和dict是python独有的
和数据结构相关的内置函数 1:reverse() l.reverse() 列表的翻转,把原有列表给翻转了,原来的列表顺序拿不到了,需要reverse()再翻转 2:reversed() res=reversed(l) 也可以翻转列表,不改变原来的列表,返回一个反序的迭代器 3:slice() 函数切片,类似 str[1:5:2] slice(1,5,2) 4:str 数据类型强转字符串 5:format 格式化输出 6:bytes 转化成bytes类型 7:bytearray byte类型的数组 8:memoryview 9:ord() 把字符按照unicode转化成数字 10:chr() 数字按照unicode转字符 11:ascii 只要是ascii码中的内容,就打印出来,不是转化成 \ u的形式 12:repr() repr这里所有的数据都会带着它的符号都输出出来 13:dict 字典 ---大括号括起来都是无序的 14:set 集合 --set类似字典的key,没有value的字典,set;里面每一个元素都可哈希,也不重复 和字典的key要求一样 15:frozenset 不可变的集合 ,frozenset不可变的数据类型可以作为字典的key 16:len 求长度 17:enumerate 枚举 enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标 18:all 参数是一个可迭代对象,这个可迭代对象有任何一个是空内容(False)就返回False(0和空字符串都算False) 19:any 参数是一个可迭代对象,这个可迭代对象有任何一个是True就返回True 20:zip() 拉链,把两个可迭代的对象依次取元素到一起--返回一个迭代器 21:fiter() 过滤列表 22:map() 映射函数,传一个f函数和一个序列值,把序列里的每个参数都拿出来都执行一次f,且 把执行f函数的结果填在新的变量里 filter 执行了filter之后的结果集合元素 小于等于 执行之前的个数 filter只管筛选,不会改变原来的值 map 执行前后集合元素个数不变, map值可能发生改变 23:sorted() # 1:reverse():列表翻转,改变原列表对象l l = [1, 2, 3, 4] l.reverse() print(l) # [4, 3, 2, 1] # 2:reversed():返回一个新的翻转了顺序的迭代器 l = [1, 2, 3, 4] res = reversed(l) print(res) # <list_reverseiterator object at 0x000001764EF99898> # 产生一个新的反序的迭代器---为了节省空间 # 3:slice 生成一个切片规则 l= [1, 22, 222, 2222, 3333, 4444, 5555] sli = slice(1, 5, 2) # 拿到一个切片规则,切什么需要看后面l[sli] print(l[sli]) # [22, 2222] # 4:format:字符串可以提供的参数,指定对齐方式,<是左对齐, >是右对齐,^是居中对齐 print(format('test', '<20')) print(format('test', '>20')) print(format('test', '^20')) # 5:bytes # 正常下读到内存的都是unicode编码,转成utf8是转成bytes类型的utf8 # bytes把unicode编码转化成utf8或者gbk什么的 # 我拿到的是gbk编码的,我想转成utf8编码 需要先decode然后encode # gbk --decode成unicode 然后unicode --encode成utf8 编码转化 # 读出来是gbk就已经是bytes类型了, print(bytes('您好', encoding='gbk')) # b'\xc4\xfa\xba\xc3' bytes类型 unicdoe转成gbk的bytes print(bytes('您好', encoding='utf8')) # b'\xe6\x82\xa8\xe5\xa5\xbd' 也是bytes类型 unicode转换成utf8的bytes # 两个bytes类型,gbk编码的bytes类型和utf编码的bytes类型 print(bytes('您好', encoding='gbk').decode('gbk')) # 先转成bytes类型,然后decode解码 # 1:您好 --写到解释器是Unicode编码的, # bytes('您好',encoding='gbk') 把Unicode编码的的您好转成gbk编码的您好 # 本身gbk的文件,以二进制形式读进来就是gbk编码的,自己把二进制decode('gbk') ---文件转化成unicode了 # 再转一层转成成utf8了 # # 一个文件存储的gbk编码,想最终存储的utf8格式的文件 # 1:gbk文件读到内存里--gbk转成unicode(decode解码)--再成unicdoe转成utf8(bytes encode编码) # 网络编程过程中,传输的角度---只能传二进制, # 文件存储,open(encodeing--指定编码读),照片视频也是二进制存储的,html网页爬取到内存也是bytes编码 # 6:bytearray b_arry = bytearray('您好', encoding='utf8') # 字节数组 print(b_arry) # bytearray(b'\xe6\x82\xa8\xe5\xa5\xbd') print(b_arry[0]) # 230 \xe6\x82\xa8\xe5\xa5\xbd看成一个整体字节数组, # 打印第一个元素xe6的十进制 # 把bytes看成一个整体, 如果字符串很长,想改一个地方,直接改会生成两个字符串,占内存, # 如果改字节数组,修改其中的一个值----但是需要看得懂改哪里,操作字节---很少用 # 改字符串其中一个地方进行修改,不会再在内存里生成一个长的变量了----修改的时候可以一起改三个字节,查看编码 # bytes类型--完整的字节,不能对中间任何一个进行修改,如果修改了就会生成一个新的字节长串--2个字节在内存里 # bytearray ---类似列表,列表里每一个元素就是一个编码,这样修改其中一个字节,把字节替换了就可以--- # 列表可变的,中间内容替换还是以前的列表,bytes类型很长修改其中一块占的内存空间还是那么多--节省内存 # bytearray --修改的时候节省内存,只能通过byte字节编码来操作他 # 7:memoryview l = 'asdasdadwadwdwzdsada' l2 = l[:10] # 每次这样切片就在内存开辟空间存储前十个 # memoryview:把数据类型转化成bytes之后进行一次memoryview拿到这个东西就可以进行切片了--这个切片不创建新的 # 字节类型的切片,不占用内存,只是切出来看看,看到的是字节,转化成字符串之后又占内存了 # 字节--不占空间 转化成字符串又占空间(内存)了 ----了解一下 # 切片本质是切1-3个就把1-3个拿出来存在一个内存里,相当于切了就复制一份出来 # memoryview切了不复制,只是把本体数据切片的地方拿出来看看 ----从原本内存拿出来数据看,原数据还在 # 8:ord():返回字符对应的unicode编码 print(ord('a')) # 97 print(ord('1')) # 49 print(ord('傻')) # 20667 unicode编码转的 # 9:chr把数字按照unicode转字符 print(chr(97)) # a print(chr('')) # 10:ascii 只要是ascii码中的内容,就打印出来,不是转化成 \ u的形式 print(ascii('好')) # '\u597d' print(ascii('1')) # '1' # 11:repr() name = 'egg' print('你好%s' % name) # 你好egg print('你好%r' % name) # 你好'egg' 这里带着引号打印出来了-- # %s对应str %r对应repr print(repr('1')) # '1' print(repr(1)) # 1 # repr:repr这里所有的数据都会带着它的符号都输出出来(数据变量原封不动输出) # 12:enumerate() ---枚举 seasons = ['Spring', 'Summer', 'Fall', 'Winter'] print(dir(enumerate(seasons))) # enumerate返回一个迭代器 print(list(enumerate(seasons))) # [(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')] # 18:all 和any都接受一个可迭代的 print(all(['a', '', 123])) # False print(all(['a', 123])) # True print(all([0, 123])) # False print(any(['a', '', 123])) # True print(any(['', 0])) # False # 19:zip():拉链方法,以最小的元素为基准拉。字典只能key拿到一起 l = [1, 2, 3] l2 = ['a', 'b', 'c'] d = {'k1': 1, 'k2': 2} # 字典只能key拿上 print(zip(l, l2)) # <zip object at 0x0000020FCBC049C8> 返回一个迭代器 for i in zip(l, l2, d): print(i) ''' 输出: (1, 'a', 'k1') (2, 'b', 'k2') ''' # 20:filter()函数接收一个函数 f 和一个list, ----filter过滤函数,和列表生成器用法差不多 # 把可迭代的当中的每一个值都传到前面f函数作为参数,每个值都执行一次, # 函数返回的结果为True的时候才会放在新的筛选结果里,如果返回False就不要它,list 迭代传 # 根据返回的结果是True或者False来决定你在不在我筛选的范围内 # 返回由符合条件元素组成的新list。 # 1:利用filter()从一个list [1, 4, 6, 7, 9, 12, 17]中删除偶数,保留奇数,首先,要编写一个判断奇数的函数: def is_odd(x): return x % 2 == 1 # 奇数返回True,偶数返回False---奇偶性布尔表达式 list = [1, 4, 6, 7, 9, 12, 17] res = filter(is_odd, list) # res返回的是一个迭代器节省内存 ---filter过滤 for i in res: print(i) # [i for i in list if i %2 ==1] --filter类似列表生成式功能,筛选列表元素。 # filter肯定循环可迭代的内容,前面方法的内容做一个判断,函数f 返回的一定是一个布尔值, # 即便是内容,这个内容也要能转化成布尔值---根据布尔值内容判断是不是在筛选范围内 # 2:利用filter()要从一个列表里过滤字符串,只要字符串,别的都不要--filter def is_str(x): return type(x) == str list = [1, 4, 6, 7, 9, 12, 'hello', 'yuanwentao', 17] res = filter(is_str, list) for i in res: print(i) # 3:利用filter(),可以完成很多有用的功能,例如,删除 None 或者空字符串: def is_not_empty(s): return s and len(s.strip()) > 0 list(filter(is_not_empty, ['test', None, '', 'str', ' ', 'END'])) # and-与,return s那么None 和''空字符串 return回去就是false,没问题。但是' '这个带空格字符串就不行了 # 4:请利用filter()过滤出1~100中平方根是整数的数, import math # math模块的sqrt是开平方的函数,和数学相关的都是math模块 def is_sqr(x): return math.sqrt(x) % 1 == 0 # % 除1取余=0就是整数 取整 // # print(list(filter(is_sqr, range(1, 101)))) # 21:map() ---映射,没有筛选功能, 接受一个函数名和序列参数 # Python中的map函数应用于每一个可迭代的项,返回的是一个结果list。 # 如果有其他的可迭代参数传进来,map函数则会把每一个参数都以相应的处理函数进行迭代处理 # map()函数接收两个参数,一个是函数,一个是序列, # map将传入的函数依次作用到序列的每个元素,并把结果作为新的list返回。 l = [-1, - 2, 3, - 8] res = map(abs, l) # abs是求绝对值的函数 print(list(res)) # [1, 2, 3, 8] print(l) # [-1, -2, 3, -8]
# 22:sorted() 排序方法,和sort()类似 # 参数说明: # iterable:是可迭代类型; # key:传入一个函数名,函数的参数是可迭代类型中的每一项,根据函数的返回值大小排序; # reverse:排序规则. reverse = True 降序 或者 reverse = False 升序,有默认值。 # 返回值:有序列表 # 1:sort() sort在原列表基础上进行排序的,改变原列表,默认从小到大排序 l = [4, 3, 1, 0] l.sort() # sort在原列表基础上进行排序的,改变原列表,默认从小到大排序 l.sort(key=abs) # 根据绝对值从小到大排序 l.sort(reverse=True) # reverse=True 从大到小排序 print(l) # 2:sorted() 不是在原列表的基础上进行排序,而是生成一个新的列表,不改变原列表,生成一个新列表,占用内存 # reversed返回一个可迭代类型,而sorted()返回整个列表,慎用---内存问题 # 排序必然需要返回一个新的值,不然排序不了---sorted()使用了后整个内存空间两个列表了(数据少才用sorted()) l = [4, 3, 1, 0] print(sorted(l)) # [0, 1, 3, 4] --生成一个新的列表排序,l还在 print(sorted(l, reverse=True)) # reverse=True 翻转,从大到小,默认False,从小到大 l = [4, 3, 1, 0] print(sorted(l, key=abs, reverse=True)) # key给一个函数,按照函数返回进行排序,参数abs按照绝对值进行排序 # test:列表按照每一个元素的len排序 # 元素传给函数,函数告诉元素的长度,然后按照长度排序就可以了 # 列表里面的元素挨个传进去,得到了元素的长度,根据长度进行排序---默认从短到长 l = [[1, 2], [3, 4, 5, 6], (7,), ' 123'] print(sorted(l, key=len)) # 排序可以各种加工作的各种事情:sorted返回一个新列表,没有返回一个可迭代的
8:python中的complex复数,实数,虚数,浮点数等
复数 complex 实数 : 有理数 (整数,有限小数,无限循环小数) 无理数 (无限不循环小数) 虚数 :虚无缥缈的数(一个数的平方是负数这个数就是虚数) 5 + 12j === 复合的数 === 这就是复数 实数的单位是1,虚数的单位i 认为i的平方就是-1 ---数学里面 python里的虚数的单位是j 6 + 15j --》实部:6 虚部:15j 这种有实部和虚部的就是一个复数 ---复合的数 复数之间不能比较大小的,只能相加相减相乘,相除 ---
float 浮点数 小数:有限小数,无限循环小数,无限不循环小数 有限小数和无限循环小数属于浮点数 无限不循环小数不属于浮点数 ---是小数 1:354.123 =3.54123 *10**2 =35.4123*10 ---如左点是可以浮动的,所以叫浮点数 2:python中小数太长可能数就不准了,计算机不认识十进制,只认识二进制 十进制小数转化成二进制小数的过程中有微弱的偏差---小数特别长转化可能有问题,不准 f = 1.54212154512121455456454 print(f) # 打印:1.5421215451212145 # 小数太长可能数就不准了,计算机不认识十进制,只认识二进制 # 十进制小数转化成二进制小数的过程中有微弱的偏差---小数特别长转化可能有问题,不准
9:简单面试题
print(3 > 2 == 2) # 类似: 3>2 and 2==2 与(and)的关系,所有这里是True and True所有返回True # and 与 or或 not 非 not >and >or print(ord('a')) # 97 print(ord('A')) # 65 # 小写字母的ascll码值一定比大写的要大, print(True and 1) # 1 返回最后执行的,最后执行的是1 print(True or 1) # 返回True print(3 > 2 > 2) # false 3 >2 and 2>2 print(0x56 > 56) # True 0x十六进制,16进制的56比十进制的56大 # print((3,2)> ('a','b')) # 报错,元组不能比较大小 # min =x if x <y else y ---三元运算符
while True: pass # 永生语句,死循环 k = 1000 while k > 1: print(k) k = k / 2 # k为整形,while循环执行的次数 python3和python2答案不同---9和10次都对 # python 2里面只有9次循环,python3里10次循环 # 虚数的实部和虚部都是浮点数 c = 4 + 5j c1 = 4 - 5j # 这两个c就是共轭复数,一个加一个减 print(c.real) # 4.0 打印实部,打印的是4.0浮点数 print(c.imag) # 5.0 打印虚部,浮点数打印5.0 print(c.conjugate()) # (4-5j) conjugate返回的是复数c的共轭复数 # 练习2 f = range(100) # range 是一个生成器(错误的结论:range()只是返回一个可迭代对象而不是迭代器) print('__next__' in dir(f)) # 经过自己验证range只是返回一个可迭代对象,没有返回生成器或者迭代器, # 1:取前面三个元素 print(list(range(100)[0:3])) # [0, 1, 2] # 需要先对range(100)切片,对生成器取三个元素, 不能直接list(range(100)) 把100个元素都取出来--方法不得行,占内存 # 迭代器也是也是可以切片的, ----经过自我验证迭代器不可以切片操作(这里有误,迭代器不可以进行切片,而且range返回的对象也不是个迭代器) # 迭代器因为缺少__getitem__ ,因此不能使用普通的切片语法。想要实现切片, # 无非两种思路:一是自己造轮子,写实现的逻辑;二是找到封装好的轮子。 # 2:取倒数第二个元素 print(f[-2]) # 98 # 3:取后10个元素 print(list(f[-10:])) # 4:把l复制给l1用 l1 = f[:] # f[:]这样切片就是一个浅拷贝 判断dict有没有某个key: in方法和get都可以(但是一种特殊情况{'k':None},这种情况下使用get('k') 返回一个None,不知道有没有k这个键) python2有haskey这个方法判断元素是不是属于字典。python3没有
# 一个默认参数为可变数据类型的陷阱问题 def extendlist(val, list=[]): list.append(val) return list # 返回的是可变数据类型,list=[]这个变量相当于定义了一个全局变量list=[] list1 = extendlist(10) list2 = extendlist(123, []) list3 = extendlist('a') print(list1) # [10, 'a'] print(list2) # [123] print(list3) # [10, 'a'] # 默认参数的陷阱问题: # list1没有传列表,用的默认列表list # list2=extendlist(123,[]) list2用的自己传的[]列表 # list3=extendlist('a') list3没有传列表参数用的默认的extendlist局部空间默认的list ---list1和list3使用的一个列表 判断变量是不是字符串:type(变量) is 和 == 区别,一个是比较内存地址,一个是比较值 Python中的对象包含三要素:id、type、value。 其中id用来唯一标识一个对象,type标识对象的类型,value是对象的值。 is判断的是a对象是否就是b对象,是通过id来判断的。 ==判断的是a对象的值是否和b对象的值相等,是通过value来判断的。 tuple和list的转化:数据类型的强制转化 list和tuple:tuple不可变 如何得到列表list的交集和差集:set(list) & set(list) set(list) - set(list) python定义函数,可变参数和默认参数:def func(*args,a=1,**kwargs) *args和**kwargs:不确定有多少个参数的时候,args接受所有按照位置传参的参数,kwargs接受按照关键字传参 实例:装饰器,不知道装饰器装饰的函数的参数是啥,有多少个参数
Unicode,utf8,gbk编码的关系:
unicode万国码,占内存
所有就有了utf8:一个中文三个字节表示 gbk是中国编码:16位表示一个中文,不兼容其他国家的语言,只兼容ascll码 使用python删除一个文件:os模块 os.remove(file)
for k,j in dict.item():
一般不要如上这么用,字典的key很小,很小,
value可能很长,这样直接拿到value数据很大,费时间,用到value的时候再去拿就行 写一段代码删除list里面你的重复元素 1:使用转set集合 ----但是set转化成集合后变成无序的了 2:for循环做 list=[] newlist=[] for i in list: if i not in newlist: newlist.append(i)
10:匿名函数 lambda
匿名函数的关键字:lambda ---谨记 lambda整个函数表达式是一个匿名函数,匿名函数可以有名字,也可以真的匿名 函数必须有:函数名。参数,返回值 匿名函数整个函数不允许有换行,一行写完 函数名 = lambda 参数 :返回值 # 匿名函数的常态 参数可以有多个,用逗号隔开 匿名函数不管逻辑多复杂,只能写一行,且逻辑执行结束后的内容就是返回值 返回值和正常的函数一样可以是任意数据类型
# 1:匿名函数 返回 n*n calc = lambda n: n ** n print(calc(10))
# 2:普通函数匿名函数转化 def add(x, y): return x + y add = lambda x, y: x + y # 匿名函数 print(add(10, 11))
# 3:匿名函数真的匿名,在和其他功能函数合作的时候 l= [3, 2, 100, 999, 213, 1111, 31121, 333] print(max(l)) # 31121 dic = {'k1': 10, 'k2': 100, 'k3': 30} print(max(dic)) # k3 max默认取key的值求max,k3的ascii码最大 print(max(dic, key=lambda k: dic[k])) # k2 # key=lambda k:dic[k]这个匿名函数根据key返回value,但是max函数返回dict字典value值最大的的这组数的key # max对可迭代数据类型的排序排的是这个类型:列表排序排的是列表里面几个元素 # 对字典排序本质上排的是字典里的key,最终根据什么排序,是根据key指定函数的返回值来排序 # 排序后取最大的,最大值任然是key的,但是排序是根据value计算出来的 print(dic[max(dic, key=lambda k: dic[k])]) # 求这个字典value最大的值的key值,最大值100对应的key:k2 # 一行实现字典里求value最大的key # 带key这个参数的内置函数 1:sort 2:sorted 3:max 4:filter 5:min 6:map # 这几个内置函数都可以有key这个参数,可以参数传一个函数,也可以和匿名函数lambda组合做 # 1:map + lambada 求平方 res = map(lambda x: x ** 2, [1, 5, 7, 4, 8]) for i in res: print(i) # 2:筛选列表中大于10的数 filter+lambdares = filter(lambda x:x>10,[5,8,11,9,15]) for i in res: print(i)
# 一些匿名函数简单的面试题 # 1.下面程序的输出结果是: d = lambda p: p * 2 t = lambda p: p * 3 x = 2 x = d(x) # x=4 x = t(x) # x=12 x = d(x) # x=24 print(x) # 24 最终打印:24 # 2:现有两元组(('a'),('b')),(('c'),('d')),请使用python中匿名函数生成列表[{'a':'c'},{'b':'d'}] # case1 func = lambda x, y: [{x[0][0]: y[0][0]}, {x[1][0]: y[1][0]}] print(func((('a'), ('b')), (('c'), ('d')))) # case2 res = zip((('a'), ('b')), (('c'), ('d'))) # zip -拉链 返回一个类似[('a', 'c'), ('b', 'd')]的迭代器 ret = map(lambda x: {x[0]: x[1]}, res) # map映射 lambda匿名函数 list(ret)可迭代数据类型转化 print(list(ret)) # [{'a': 'c'}, {'b': 'd'}] 3:以下代码的输出是什么?请给出答案并解释 def multipliers(): return [lambda x: i * x for i in range(4)] # i =0,1,2,3 # 相当于 return [lambda x:i*x,lambda x:i*x,lambda x:i*x,lambda x:i*x] # 返回列表推导式i从0循环到3,所以最后i=3 print([m(2) for m in multipliers()]) # multipliers()调用 # print([m(2) for m in [lambda x:i*x,lambda x:i*x,lambda x:i*x,lambda x:i*x]]) 相当于这 # print([m(2) for m in [lambda 2:i*2,lambda 2:i*2,lambda 2:i*2,lambda 2:i*2]]) 这时候i=3 # 因为for i in range(4)都已经执行完了i=3了才返回四个lambda表达式内存地址,这四个函数暂时没有调用 # m(2)的时候才调用,i从头到尾没有用到过,最终底下调用使用最后一个i=3,和2乘 ---[6, 6, 6, 6] # 每一个m 都是lambda表达式,函数的内存地址 # def multipliers(): # return (lambda x:i*x for i in range(4)) #返回改成生成器, # # print([m(2) for m in multipliers()]) #[0, 2, 4, 6] --- # print([m(2) for m in g) #等于拿到一个生成器,这里for一次上面生成器才取值一次 # 每for一次才拿到一个lambda--第一次for的时候 i=0 0*2=2 # 再取一个m又生成一个lambda--生成器又进行一个for循环 i=1
11:简单练习
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
3.用map映射函数来处理字符串列表,把列表中所有人都变成sb,比方alex_sb name = ['alex', 'wupeiqi', 'yuanhao', 'nezha'] def func(item): return item + '_sb' ret = map(func, name) # ret是迭代器 for i in ret: print(i) print(list(ret)) # 打印:[] ret迭代器,for循环把迭代器里的值取完了 # 如果ret不是迭代器而是可迭代对象,那么每次for 循环ret都会重新生成一个迭代器 # 然后next方法取值---每次for 循环都会产生迭代器 # 如果ret就是迭代器,那么每次for 循环没啥用,for循环产生的迭代器还是最原来的迭代器 # 一次for循环就能取完值,再for循环就取不到值了(迭代器里面的值只能取一次) ret = map(lambda item: item + '_sb', name) # map搭配匿名函数使用 --map返回一个迭代器 print(list(ret)) # ['alex_sb', 'wupeiqi_sb', 'yuanhao_sb', 'nezha_sb'] 4:用filter函数处理数字列表,将列表中所有的偶数筛选出来 偶数 %2 取余为0(filter和map的返回都是一个迭代器) num = [1, 3, 5, 6, 7, 8] res = filter(lambda x: x % 2 == 0, num) # filter 搭配lambda匿名函数使用 print(list(res)) # [6, 8] ret = filter(lambda x: True if x % 2 == 0 else False, num) # lambda表达式里的三元运算符的使用 5.随意写一个20行以上的文件 运行程序,先将内容读到内存中,用列表存储。 接收用户输入页码,每页5条,仅输出当页的内容 with open('file', encoding='utf-8') as f: l = f.readlines() page_num = int(input('请输入页码 : ')) pages, mod = divmod(len(l), 5) # 求有多少页,有没有剩余的行数 if mod: # 如果有剩余的行数,那么页数加一 pages += 1 # 一共有多少页 if page_num > pages or page_num <= 0: # 用户输入的页数大于总数或者小于等于0 print('输入有误') elif page_num == pages and mod != 0: # 如果用户输入的页码是最后一页,且之前有过剩余行数 for i in range(mod): print(l[(page_num - 1) * 5 + i].strip()) # 只输出这一页上剩余的行 else: for i in range(5): print(l[(page_num - 1) * 5 + i].strip()) # 输出5行 # case 2 page = int(input('请输入页码>>>')) def get_file(): with open('file', mode='r', encoding='utf8') as f: for i in f: yield i g = get_file() for i in range(5): print(g.__next__().strip()) 6.如下,每个小字典的name对应股票名字,shares对应多少股,price对应股票的价格 portfolio = [ {'name': 'IBM', 'shares': 100, 'price': 91.1}, {'name': 'AAPL', 'shares': 50, 'price': 543.22}, {'name': 'FB', 'shares': 200, 'price': 21.09}, {'name': 'HPQ', 'shares': 35, 'price': 31.75}, {'name': 'YHOO', 'shares': 45, 'price': 16.35}, {'name': 'ACME', 'shares': 75, 'price': 115.65} ] # 6.1.计算购买每支股票的总价 print([[i['name'],round(i['shares']*i['price'],2)] for i in portfolio]) ret = map(lambda dic : {dic['name']:round(dic['shares']*dic['price'],2)},portfolio) print(list(ret)) # 6.2.用filter过滤出,单价大于100的股票有哪些 ret = filter(lambda dic: True if dic['price'] > 100 else False, portfolio) # 三元表达式 print(list(ret)) ret = filter(lambda dic: dic['price'] > 100, portfolio) print(list(ret)) 学习一下流程图 流程图: 1:开始和结束用圆角矩形 2:判断使用菱形 3:
12:python中二分查找算法
什么叫算法:研究计算机过程中,用一些策略让计算机把一些事情变简单--优秀的算法 计算的方法 : 人脑复杂(便捷) 计算机简单 99 * 13 = 1287 = 13*100 - 13 查找算法 : 庞大数据量里找数据(数据库) 排序算法 : 最短路径算法:百度地区,最短路径 我们学习的算法 都是过去时 了解基础的算法 才能创造出更好的算法 不是所有的事情都能套用现成的方法解决的 有些时候会用到学过的算法知识来解决新的问题
二分查找算法:必须用在处理有序的列表 如果有这样一个列表,让你从这个列表中找到66的位置,你要怎么做? l = [2,3,5,10,15,16,18,22,26,30,32,35,41,42,43,55,56,66,67,69,72,76,82,83,88] # case1:index查找 l = [2, 3, 5, 10, 15, 16, 18, 22, 26, 30, 32, 35, 41, 42, 43, 55, 56, 66, 67, 69, 72, 76, 82, 83, 88] l.index(66) # 输出:17 # case2 # for循环查找 # case3:二分查找算法 必须处理有序的列表:先找中间和66判断大小 l = [2, 3, 5, 10, 15, 16, 18, 22, 26, 30, 32, 35, 41, 42, 43, 55, 56, 66, 67, 69, 72, 76, 82, 83, 88] def find(l, aim): # ''' :param l: l是查找的列表 :param aim: aim是要查找的值 :return: ''' mid_index = len(l) // 2 # 整除 --找到中间值,拿到一个整数的索引 if l[mid_index] < aim: # 找的值小于中间的值,右边继续二分找 new_l = l[mid_index + 1:] find(new_l, aim) elif l[mid_index] > aim: new_l = l[:mid_index] find(new_l, aim) else: print('找到了', mid_index, l[mid_index]) find(l, 66) # 找到了 0 66 # case4(简单二分寻址算法,这是一个递归函数的调用) l = [2, 3, 5, 10, 15, 16, 18, 22, 26, 30, 32, 35, 41, 42, 43, 55, 56, 66, 67, 69, 72, 76, 82, 83, 88] def find(l, aim, start=0, end=len(l)): # mid_index = (end - start) // 2 + start # 计算中间值 if l[mid_index] < aim: # 从mid_index+1后面去找了,但是不能传新列表,只能还是旧列表,还得传l find(l, aim, start=mid_index + 1, end=end) elif l[mid_index] > aim: find(l, aim, start=start, end=mid_index - 1) else: print('找到了', mid_index, aim) find(l, 3)
case4还有如下几个缺点 参数 end :函数定义普通参数没有问题,但是定义一个默认参数end=len(l),默认的值要用到的变量l一定是之前已经存在过的 但是一个查找算法不能列表已经已知,所以不能直接用到l 函数没有返回值 找不到得话怎么办,所以case5改进一下 # case5 改进版 # 1:参数 end :函数定义普通参数没有问题,但是定义一个默认参数end=len(l),默认的值要用到的变量l一定是之前已经存在过的 #但是查找算法不能列表已经已知,所以不能直接用到l,最好用默认参数,l必须先定义,不能要求先定义一个l再定义函数 #不使用len(l) # 2:函数没有返回值 # 找不到得话怎么办 def find(l,aim,start=0,end=None): # end = len(l) if end is None else end # 如果end没传是默认值None就赋值end = len(l),否则用传的值 mid_index=(end-start)//2+start # 计算中间值 if start <= end: if l[mid_index] < aim: #从mid_index+1后面去找了,但是不能传新列表,只能还是旧列表,还得传l return find(l,aim,start=mid_index+1,end=end) elif l[mid_index] > aim: return find(l, aim, start=start, end=mid_index-1) else: print(mid_index) return mid_index else: # 各种计算,如果算到最后start > end 返回找不到这个值 return '找不到这个值' l = [2,3,5,10,15,16,18,22,26,30,32,35,41,42,43,55,56,66,67,69,72,76,82,83,88] res=find(l,66) print(res) # 这样return找不到,递归函数递归到最后一次找到了元素的index, # 通过调用了n次find函数才找到了66这个元素, # 需要层层返回返回值
# 简单练习(使用递归函数) 5的阶乘:5*4*3*2*1 1:求阶乘 2:求斐波那契数列----n个斐波拉契数是多少
13:简单的递归函数
递归函数 了解什么是递归 : 在函数中调用自身函数 最大递归深度默认是997/998 —— 是python从内存角度出发做得限制 --而不是程序真的报错 能看懂递归 能知道递归的应用场景 初识递归 —— 算法 —— 二分查找算法 三级菜单 —— 递归实现
1:初级递归函数 # while True: # print('从前有座山')
# 下面开始才是递归函数 n = 0 def story(): # 递归函数是在函数中调用自身函数 global n n += 1 print('从前有座山', n) story() # 函数里面调用函数, print(111) story()
# 1:def story(): stroy放内存里 # 2:story()调用stroy # 3 print('从前有座山') # 4 story() 又调用story # 5:print('从前有座山') # 6:story() 又调用story # 停不下来了 # 每调用一个函数,开一个独立的内存空间,第一次调用story开辟了一个内存空间 # 第二次调用story,第一个空间没有释放掉,因为第一个story函数还没有执行结束 # 每次内部调用都是在开辟空间,开空间浪费内存,不会让一直无限制的浪费内存下去---超过固定最大限度就报错 # RecursionError: maximum recursion depth exceeded while calling a Python object # 上面是:递归的错误,超过了递归的最大深度 ---内存级别的保护动作,重复开空间关不上浪费内存-- # python对最大递归深度做了一个约束 -----递归最大的深度不能超过1000次(997-998左右) # 算上第一次调用就有998.只算内部调用就是997 ---最大递归深度是997或者998,可以修改的
2:修改递归函数的最大深度 sys.setrecursionlimit(1000000) 基本上很多电脑只能跑到3222,---和电脑配置相关 如果擅自修改这个值:程序没有结束,但是意外的退出---递归需要设置在安全范围,别瞎几把改 如果递归次数太大就不适合使用递归解决问题 递归的缺点:太占内存了,递归很占用内存, 递归的有点:但是某些问题让代码更简单 import sys sys.setrecursionlimit(1000000) # 修改程序递归的最大深度 --recursion:递归 n = 0 def story(): global n n += 1 print(n) story() story()
3:使用递归函数解决一个简单的问题 alex多大 alex 比 egon大两岁 n=1 alex多大 age=age(n+1)+2 n是参数 age(1)=age(2)+2 n是参数,age(1)= alex的年纪 age(1)=age(2)+2 age(2)是egen的年龄 alex的age=age(2)+2 age(2)=egen age(2)=age(3)+2 =age(n+1)+2 egen多大 egen比wusir大两岁 n=2 egen多大 age=age(n+1)+2 wusir多大 wusir比金老板大两岁 n=3 wusir多大 age(3)=age(4)+2 =age(n+1)+2 金老板多大 金老板40了 n=4 金老板多大 age(4)=40 这就是简单的递归的例子---递归想求解一个问题,不能直接得到答案 需要通过另外一个结果得到这个题,另外一个结果的求取方式和上面的是一样的 问题一直走能找到结果----类似斐波拉契 想知道40个斐波拉契的数,需要知道39和38,38+39=40 38,39也不知道,一直往下问,问到第一个数1第二个数1----- 然后顺着往前算,正常是从前算到尾的,但是递归是从结果出发, 从结果这个问题应该怎么往下取解决来思考问题,最终形成一个递归函数 n = 4 age(4) = 40 n <4 age(n) = age(n+1) +2
age(1)是alex,age(2)是egen,age(3)是wusir,age(4)是金老板 def age(n): if n == 4: return 40 elif n > 0 and n < 4: return age(n + 1) + 2 print(age(1)) # 46 # 递归函数的运行:
def age(n):
if n == 4:
return 40
elif 1 <= n <= 3:
return age(n + 1) + 2
else:
return '没有大于4的情况了'
print(age(5))
递归函数的一些小知识:
超过最大递归限制的报错
只要写递归函数,必须要有结束条件。
返回值
不要只看到return就认为已经返回了。要看返回操作是在递归到第几层的时候发生的,然后返回给了谁。
如果不是返回给最外层(第一次执行的函数)函数,调用者就接收不到。
需要再分析,看如何把结果返回回来。
循环
递归
1:斐波那契 # 问第n个斐波那契数是多少 1,1,2,3,5,8 fib(6) = fib(5) + fib(4) fib(5) = fib(4)+fib(3) fib(4) = fib(3)+fib(2) fib(3) = fib(2)+fib(1) fib(2) = 1 # 结束的条件,到了这里函数就就有返回值不会再往内继续函数执行 fib(1) = 1 递归推一个结果,一般是从结果往前推,递的过程 ---要知道fib(6)需要知道fib(5)和fib(4) 知道表面想求的问题,往下去走才能找到想要的结果 # case1 从1,1开始的斐波拉契数 def fib(n): if n == 1 or n == 2: # 结束条件,n不能比1还小 return 1 # 结束的点 else: return fib(n - 1) + fib(n - 2) print(fib(100)) # 递归函数的效率问题: 一个fib()里面需要调用两个fib(),两个相加 # fib(6) # fib(5) fib(4) # fib(4) fib(3) fib(3) fib(2) # fib(3) fib(2) # fib(2) fib(1) # 而上面普通的find()一层调用find(),一层层调用,调用一百层同一个函数也就调用一百次 # 而如果个fib()里面需要调用两个fib()相加,1,2,4,8这样累加,递归运算速度会慢很多--还不如for循环速度快 # 写递归的时候递归里面不要双递归,两次调用fib,不然运算gg,速度很慢 # case2 从0,1开始的斐波拉契数 def fib(n): if n == 1: return 0 elif n == 2: return 1 else: return fib(n-1) + fib(n-2) print(fib(11)) # case3 循环搞定斐波拉契(for循环一个个加上去) def fib(n): if n == 1 and n == 2: return 1 else: sum = 0 i = 1 j = 1 for z in range(n - 2): sum = i + j m = j j = i + j i = m return sum print(fib(100)) # case4 一个递归实现斐波拉契 # 如果fib(n) 调用fib(n-1)得到两个值a和b,然后再把
# fib(1) = 1
# fib(2) = 1 # fib(3) fib(1)+fib(2) == 1+1 a=1 b=1 # fib(4) = fib(3)+fib(2) = 1+2+1 a=1 b=2+1 = b + a+b # fib(5) = a + b(2+3) return 3,5 # 1 1 =fib(3) def fib(n, a=1, b=1): if n == 1: return a return fib(n - 1, b, a + b) print(fib(100)) # fib(6) = fib(5) + fib(4) # fib(5) = fib(4)+ fib(3) # fib(4) = fib(3)+ fib(2) # fib(3) = fib(2)+ fib(1) # fib(2) = 1 # fib(1) = 1 def fib(n, l=[0]): l[0] += 1 if n == 1 or n == 2: l[0] -= 1 return 1, 1 else: a, b = (n - 1) l[0] -= 1 if l[0] == 0: return a + b return b, a + b print(fib(50))
# 1 1 2 3 5 8 13
# fib(0) = 0
# fib(1) = 1 return 0 1
# fib(2) = 0+1 return 1 1
# fib(3) = 1 + 1 return 1 (1+1)
# fib(4) = 1 + 2 return 2 (1+2)
# fib(5) = 2 + 3 return 3 (2+3)
# fib(6) = 3 + 5 return 5 (3+5)
def fib(n):
if n == 1:
return 0, 1
elif n == 2:
return 1, 1
else:
a, b = fib(n - 1)
return b, a + b
print(fib(6))
fib(5)
# fib(3) 拿到fib(2)返回出来的 1,1赋值给a,b,然后直接返回1和 1+1
# fib(4) 拿到fib(3)返回出来的 1,2赋值给a,b。然后直接返回2和 1+2
# fib(5) 拿到fib(4)返回出来的 2,3赋值给a,b。然后直接返回3和 2+3
# ......一直继续这样的循环
# fib(2) 返回1 1 返回的是fib(1)的值1和fib(2)的值1
# fib(3) 返回1 2 返回的是fib(2)的值1和fib(3)的值2
# fib(4) 返回2 3 返回的是fib(3)的值2和fib(4)的值3
# fib(5) 返回3 5 返回的是fib(4)的值3和fib(5)的值5
# 也就是下个fib拿到上个fib的a和b,再返回出来b和a+b给下个fib
# 比如fib(5)拿到fib(4)返回出来的a=2,b=3,然后返回出来b和a+b也就是返回3和5出来
# 就是按照上面的规律这样来的,调用一个fib(n)就返回两个值a和b可以给下面的fib(n+1)使用计算出fib(n+1)的值来
# 逻辑就是:
# fib(2)返回fib(1)和fib(2)的值
# fib(3)先拿到fib(2)返回fib(1)和fib(2)的值计算出fib(3)的值,然后返回fib(2)和fib(3)的值
# fib(4)先拿到fib(3)返回fib(2)和fib(3)的值计算出fib(4)的值,然后返回fib(3)和fib(4)的值
# 走这样一个循环的逻辑
# 所以停止条件就是:n = 2的时候返回 1, 1
# 不断循环的条件是:n > 2的时候,比如n = 3,拿到n = 2也就是fib(2)传递来的1,1,计算出fib(2)=1+1 然后把fib(2)=1和fin(3)=1+1 return出去
# 本质上就是一个大逻辑
# 求fib(n),拿到fib(n - 1)的a,b值,然后把b和a+b返回出去
上面的代码就是初级版本,但是打印fib(5)返回出来的是一个元组(3, 5)理论上斐波那契序列5就是等于5,我们不需要返回一个元组
代码改进版,使用一个默认的陷阱参数l,l里面就是存储一个公共数据,每次调用fib函数公用的:
fib函数调用到最外层的时候需要返回一个数据而不是一个元组,但是没在最外层的时候需要返回一个元组,因为下一层的函数的返回需要依赖上一层元组,
但是到了最外层的时候不需要再计算下一层了所以可以直接返回一个结果就行
假设一个f(5)简写,f=fib函数
每调用一层函数的时候l列表里的数字+1,比如f(5)需要依赖f(4),f(4)需要依赖f(3),f(3)需要依赖f(2)一直这样递归执行了4次f函数所以l里等于了4
此时先走f(2),l里的-1后变成3,然后返回 1 1
再走f(3),拿到f(2)返回的1 1赋值给a,b,l里的减少1变成2 然后返回1 2
走f(4),拿到f(3)返回的1,2赋值给a,b,l里减少1变成1,然后返回2 3
走f(5),拿到f(4)返回的2,3赋值给a,b,l里减少1变成0.直接返回2+3=5
def fib(n, l=[0]): l[0] += 1 if n == 1 or n == 2: l[0] -= 1 return 1, 1 else: a, b = fib(n - 1) l[0] -= 1 if l[0] == 0: return a + b return b, a + b print(fib(50))
2: 阶乘使用递归函数结果 fac(5) =5*fac(4) fac(4) =4*fac(3) fac(1)=1 # 停止条件,fac(1)返回1 def fac(n): if n == 1 : return 1 return n * fac(n-1) print(fac(12)) # fac(2) --- return 2 * fac(1) -- 2 *1 # # fac(3) --- return 3 * fac(2) # fac(2) --- return 2 * fac(1) ---2 *1
14:正则表达式和re模块的使用
计算器 re模块 正则表达式:用来做字符串匹配的,大段文章筛选想要的东西---规则 python操作这件事(筛选文章字符串)--使用re模块 学习正则表达式 学习使用re模块来操作正则表达式
一个需求: 注册登录:输入手机号 输入手机号需要全是数字,需要11位长度,以1XX开头 ---关于字符串的匹配 写的字符串合不合法, 1:怎么判断这个phone_number是合法的 # len(phone_number) == 11 长度11 # and phone_number.isdigit()\ 内容是不是digit数字 # and (phone_number.startswith('13') \ # (phone_number.startswith('13') or phone_number.startswith('14') or phone_number.startswith('15') \ or phone_number.startswith('18')) 是不是13或14或15或18开头 只要满足这些条件就是合法的电话号码 case1:if else多重判断来实现 while True: phone_number = input('please input your phone number : ') if len(phone_number) == 11 \ and phone_number.isdigit()\ and (phone_number.startswith('13') \ or phone_number.startswith('14') \ or phone_number.startswith('15') \ or phone_number.startswith('18')): print('是合法的手机号码') else: print('不是合法的手机号码') # case 2 使用re模块判断这个phone_number是不是合法的 import re phone_number = input('please input your phone number : ') if re.match('^(13|14|15|18)[0-9]{9}$', phone_number): print('是合法的手机号码') else: print('不是合法的手机号码')
re正则根据一个字符串,给一个规则,根据规则匹配到想要的字符串里面的内容
re:爬虫,数据分析(处理字符串)
python里使用正则表达式需要re模块,正则有自己的规则和意义,和python没有关系
正则表达式本身也和python没有什么关系,就是匹配字符串内容的一种规则。
首先你要知道的是,谈到正则,就只和字符串相关了。和任何其他的数据类型没有关系(只和字符串相关)
正则表达式中的字符组 1:字符组 :[] [字符组]:字符组是同一个位置上只能出现的内容。值匹配一个位置 在同一个位置可能出现的各种字符组成了一个字符组,在正则表达式中用[]表示 字符分为很多类,比如数字、字母、标点等等。 假如你现在要求一个位置"只能出现一个数字",那么这个位置上的字符只能是0、1、2...9这10个数之一。 [0,1,2,3,4,5,6,7,8,9] :表示字符串一个位置上匹配0-9这一堆数 [0123456789] 8 True 在一个字符组里枚举合法的所有字符,字符组里的任意一个字符 和"待匹配字符"相同都视为可以匹配 [0123456789] a False 由于字符组中没有"a"字符,所以不能匹配 [0-9] 7 True 也可以用-表示范围,[0-9]就和[0123456789]是一个意思 只能小的数到大的[0-9]这样写,不能[9-0] [a-z] s True 同样的如果要匹配所有的小写字母,直接用[a-z]就可以表示 [A-Z] B True [A-Z]就表示所有的大写字母 [A-Za-z] 匹配所有的字母 [A-Za-z0-9] 匹配字母和数字 [0-9a-fA-F] e True 可以匹配数字,大小写形式的a~f,用来验证十六进制字符 [A-z] 这样写可以的,但是不推荐这样写,大写和小写的ascii码之间有其他字符,不准确
假如匹配的特殊字符里有"-" 需要转义一下 [0\-9]:表示匹配 0, \-,和 -9(因为在正则里特殊字符"-"有特殊含义)
正则表达式中的元字符: 2:元字符,在正则表达式里有特殊的意义 . 匹配除换行符以外的任意字符(re.S主要针对这个点匹配,表示"."可以匹配换行符,让"."变成万能匹配字符) \w 匹配字母或数字或下划线(word:字母的意思) \s 匹配任意的空白符(tab,空格都能匹配 space:空格) \d 匹配数字 digit:数字 \W 匹配非字母或数字或下划线,带上大写就是非 \D 匹配非数字 \S 匹配非空白符 [\w\W] 匹配任意字符:全局,全都能匹配,换行符和点都能匹配 \n 匹配一个换行符(匹配回车) --换行符就是\n \t 匹配一个制表符 --tab按下就是制表符 \b 匹配一个单词的结尾 \bg ---匹配单词的结尾是g的 ^ 匹配字符串的开始^e:字符串以e开头能匹配,等于statwith,以什么开头 ^[0-9] 必须以数字开头 ^[a-z] 必须以小写数字开头
如果^和$不在字符组里,那么^和$一定要在正则表达式的最开始,如果^前面有任何字母,这个正则肯定匹配不出东西
a^b:字符串以b开头,前面还要有个a,不存在这种字符串,匹配不上的所以^必须写在最前面,$写在最后
$ 匹配字符串的结尾 e$:字符串以e结尾能匹配,类似endwith ^[a-z]$:表示字符串开始和结束中间只能有一个 a-z的字母,中间只有一个字符组,一个字符组表示一位 ^[a-z][a-z]$ 开始和结束中间必须两位,都是a-z,两个字符组对应两个字符,少一个不行,多一个也不行 a|b 匹配字符a或字符b abc|b 匹配b或者abc连在一起 从左到右依次匹配,如果能匹配上左边就不在往后匹配了 () 匹配括号内的表达式,也表示一个分组,只许一个匹配一个是[]中括号,写在()小括号里面的是分组
一个字符组的量词约束作用范围只有一个字符
分组可以对多个字符组整体量词约束的时候用的:比如身份证后三位要么都有要么都没有,整体约束 re模块:分组是有优先的 findall: split: findall和split分组优先:findall会优先把匹配结果组里内容返回, 如果想要匹配结果,分组里面加?取消权限即可
[...] 匹配字符组中的字符 [^...] 匹配除了字符组中字符的所有字符 [^a] 除了a不匹配剩下的都匹配(匹配除了字符组中字符的所有字符) [^ab] 除了ab不匹配,剩下的都匹配 ^a 匹配以a开头的 [^a-z]小写字母都不匹配了, ^ ==非
| 或,或者,从左到右匹配,只要匹配上就不继续匹配了。所以应该把长的放前面 [a|b] 两者取一个
() 分组的使用:对多个字符组整体量词约束的时候用的
import re
st = "abaaabbbaaabbb"
print(re.search('(ab)+', st).group()) # ab
st = "abababababababaccccccc"
print(re.search('(ab)+', st).group()) # ababababababab
print(re.search('(a)+(b)+', st).group()) # ab
(ab)+对字符组整体量词约束,出现一个或者多个ab,这里是默认贪婪匹配默认就匹配多个
(ab){3}:如果这样就匹配3个ab也就是ababab
正则表达式中的量词 3:量词:约束匹配次数 * 重复零次或更多次 \d* :默认往多了匹配,匹配越多越好,贪婪匹配,,数字重复0次或者多次 + 重复一次或更多次 \d+:匹配一次或者多次,没有0,什么都没写,写了个不是数字都不匹配上,一次或者多次 ? 重复零次或一次 ?\d:每次只匹配一个结果 {n} 重复n次 \d{11} 匹配重复11次的数字 {n,} 重复n次或更多次 {n,m} 重复n到m次 所有的量词都要用在正则匹配规则的后面,
比如:\d{11} :\d是正则规则,要匹配所有的数字,后面+量词{11}这个量词只约束前面这个正则规则, [a-z]\d+ :匹配一个字母+一个或者以上的数字 +只能约束 \d不能约束[a-z]字符组出现的次数 [a-z]+\d+ :这样就能匹配任意多个字母+数字 :元字符和量词,先规则后量词,且量词只约束它前面一个字符
实例1: .茶 : .匹配任意一个字符,茶:匹配茶字 匹配两个字符以茶结尾 .茶+ : +量词约束"茶",能匹配一个除换行符以外的任意字符+一个或者多个茶(如果有多个茶默认贪婪匹配) import re st = '红茶茶绿茶茉莉茶黑茶' res = re.findall('.茶', st) res1 = re.findall('.茶+', st) print(res) # ['红茶', '绿茶', '莉茶', '黑茶'] print(res1) # ['红茶茶', '绿茶', '莉茶', '黑茶']
实例2: . ^ $ 实例 海. 海燕海娇海东 海燕,海娇,海东 匹配所有"海."的字符 ^海. 海燕海娇海东 海燕 只从开头匹配"海." 海.$ 海燕海娇海东 海东 只匹配结尾的"海.$"--以海..结尾的 ^ 永远在所有正则开头 $ 永远在所有正则结尾 import re st = '海燕海娇海东' print(re.findall('海.', st)) # ['海燕', '海娇', '海东'] print(re.findall('^海.', st)) # ['海燕'] print(re.findall('海.$', st)) # ['海东']
实例3: * + ? { } 的使用 李.? ?表示重复零次或一次,.重复零次或者一次 [^和]+ 匹配非和的任意多个字符 李.* .*:任意字符匹配0次或者多次, 李.+ ==李.* 李.{1,2} .出现一次或两次,默认贪婪匹配 import re st = '李杰和李莲英和李二棍子李' print(re.findall('李.?', st)) # ['李杰', '李莲', '李二', '李'] print(re.findall('李.*', st)) # ['李杰和李莲英和李二棍子李'] print(re.findall('[^和]+', st)) # ['李杰', '李莲英', '李二棍子李']
实例4:正则默认贪婪匹配,使用 "?" 取消贪婪匹配改成惰性(非贪婪)匹配 正则默认都是贪婪匹配,前面的*,+,?等都是贪婪匹配,也就是尽可能多匹配,后面加?号使其变成惰性匹配 量词后面加上一个问号 ?就变成非贪婪匹配了,能少匹配就少匹配, 非贪婪匹配就是惰性匹配 ? :作为量词是重复零次或一次 ? :放在量词后面表示非贪婪匹配 :李.*? .*表示.出现零次或更多次,?非贪婪模式那就匹配最少的0次 import re st = '李杰和李莲英和李二棍子' res = re.findall('李.*?', st) print(res) # ['李', '李', '李']
实例5:字符集 st = "李杰和李莲英和李二棍子" 李[杰莲英二棍子]*: 表示匹配"李"字后面[杰莲英二棍子]的字符任意次:['李杰', '李莲英', '李二棍子'] 李[^和]*:表示匹配一个不是"和"的字符任意次 :['李杰', '李莲英', '李二棍子'] import re st = "李杰和李莲英和李二棍子" print(re.findall('李[杰莲英二棍子]*', st)) # ['李杰', '李莲英', '李二棍子'] print(re.findall('李[^和]*', st)) # ['李杰', '李莲英', '李二棍子'] st = "456bdha3" [\d]:表示匹配任意一个数字,匹配到4个结果(一个\d匹配一个字符):['4', '5', '6', '3'] [\d]+:表示匹配任意个数字(一次或者多次),匹配到2个结果:['456', '3'] import re st = "456bdha3" print(re.findall('[\d]', st)) # ['4', '5', '6', '3'] print(re.findall('[\d]+', st)) # ['456', '3'] 一般都是字符集+量词的使用
实例6:分组() 分组:需要对一个整体的式子做量词的约束的时候就用分组 ()与 或 |[^]
[abc][123]:能够匹配从a1到c3的任意两个字符 ([abc][123])+:"+"这个量词能够约束[abc]和[123]所有字符组,a1到c3出现一次或者n次 实例匹配身份证号码:
身份证号码是一个长度为15或18个字符的字符串,
如果是15位则全部由数字组成,首位不能为0;
如果是18位,则前17位全部是数字,末位可能是数字或x,
下面我们尝试用正则来表示: ^[1-9]\d{13,16}[0-9x]$ ^[1-9]约束第一位不能位0必须是1-9 \d {13,16} 数字取13-16个 [0-9x]$ 结尾是0-9还能是x 这样写如果是15,16,17位都能以x结尾,规则15位就全数字,这样写还是有一些问题的 ^[1-9]\d{14}(\d{2}[0-9x])?$ ^[1-9]1个数字 \d{14}:14个数字 已经15个了 (\d{2}[0-9x])?$ \d{2}:两个数字 [0-9x]:一个0-9或者x ?:量词 0次或者一次:\d{2} 可能出现0次或者1次 ,可能有或者没有两个数字,[0-9x]可能出现1个或者0个"x"或者数字 要么就是匹配15位数字 后面啥都没有 要么就是匹配18位 后面\d{2}[0-9x]出现一次(两个数字+一位) 只约束15或者18位 ^([1-9]\d{16}[0-9x]|[1-9]\d{14})$ | 或运算,长的规则放前面,短的规则放后面, [1-9]\d{16}[0-9x] 18位 [1-9]\d{14} 15位的规则 必须先写18位的后写15位的,先写15位的18位的身份证匹配不上
正则表达式中的转义符 \ 在正则表达式中,有很多有特殊意义的是元字符,比如\n和\s,\d等, 如果要在正则中匹配正常的"\n"而不是"换行符"就需要对"\"进行转义,变成'\\'。 \n \n False 因为在正则表达式中\n是有特殊意义的字符, 所以要匹配\n本身,用表达式\n无法匹配 \\n \n True 转义\之后变成\\,即可匹配 "\\\\n" '\\n' True 如果在python中,字符串中的'\'也需要转义,所以每一个字符串'\'又需要转义一次 r'\\n' r'\n' True python在字符串之前加r,让整个字符串不转义 r real,真实的没有转义的,取消整个字符串的转义
相当于在python中需要一个\转义一个\
在正则中也需要一个\专业一个\
所以正则里想匹配一个\需要\\
而python里\\传递给正则的话需要\\\\才能传递一个\\给正则里
转义的问题
待匹配的字符里面出现上面这些元字符比如\d,\t,\s匹配空白,如果字符串里出现\s,
那么正则表达式需要加转义\\s,正则表达式要多加个\,
放在python里转成字符串,想要再python里表达 \\s这个正则表达式的话还需要 \\\s再加个\来表达\\s
或者python里面加r ---简单一点,正则表达式和待匹配的字符串前后都加上r'',
然后里面写的和正则里面的一模一样就可以了
待匹配的字符里面出现元字符就在写的正则和待匹配的字符串前面都加上r
这样就不会有转义的错误了
量词也有特殊意义,比如想匹配一个*,*是量词,匹配0次或者多次,正则表达式需要写成\*
[*]字符组里的型号没有量词的意思了,字符组不会出现量词,正常情况下字符组里所有字符都表示
可以匹配一次,只能整体对字符组做量词约束[]*这样就是量词了,
[*]这个正则表达式就不需要转义,
如果[1-9],想匹配1,-,和9,这时候就匹配的1-9的数字这样就需要转义
[1\-9],
万无一失的办法:如果遇到特殊字符又不希望它表示原有的意思
就通通给他转义了都不会出现问题,比如* 单纯表示* 而不是0次多次
-单纯表示减号而不是范围,全都\-,\*转义了
# 1:本来在python里是要这样的
import re
res = re.findall('\\\s', r'\s') # python自己需要一个\转义,消耗一个\,re自己也需要一个\转义,消耗一个\
print(res[0]) # \s
print(res) # ['\\s']
# python中'\\s'就算表示字符串'\s'第一个\是python中用来转义反斜杠使用的
# python打印列表类型里面元素带字符串的时候py自己加 \ 的处理,\\s py解释器才认为是字符串的\s
# 2:但是使用r:real可以这么写,不需要转义,原字符操作
import re
print(re.findall(r'\\s', r'\s')) # ['\\s']
# 待匹配的字符串是一个有特殊意义的元字符的话,正则表达式加上转义(要么转义要么使用r),
# 放到python里面转成普通字符串了,而不是特殊含义的作用
正则中的贪婪匹配和非贪婪匹配 贪婪匹配:贪婪匹配:在满足匹配条件时,匹配尽可能长的字符串,默认情况下,采用贪婪匹配 <.*> 前面<后面> ,中间任意取多少次 <.*?> 非贪婪匹配,量词*后面加?问号,就表示惰性匹配了,尽可能少取 贪婪模式是回溯算法:直接往后找,找到最后匹配不了往回走,往回找第一个
正常情况下就是贪婪匹配,找最大的集合 非贪婪模式:在量词后加? 就变成了惰性匹配,从左往右找到第一个符合条件的就停了,惰性匹配 几个常用的非贪婪匹配Pattern *? 重复任意次,但尽可能少重复 +? 重复1次或更多次,但尽可能少重复 ?? 重复0次或1次,但尽可能少重复 {n,m}? 重复n到m次,但尽可能少重复 {n,}? 重复n次以上,但尽可能少重复
.*?的用法 . 是任意字符 * 是取 0 至 无限长度 ? 是非贪婪模式。 合在一起就是 取尽量少的任意字符,一般不会这么单独写,他大多用在: .*?x 就是取前面任意长度的字符,直到一个x出现,找到x就结束
re里面三种匹配方法:正则都只能和字符串打交道
findall:找所有放在一个列表里(找所有)
search:找第一个,那个位置找到都行(只想找一个)
match:从头开始找一个(头也必须要匹配)(要求开头也能匹配上)
split:按照正则规则进行切片(分隔一个字符串)
sub:替换一个字符串
subn:替换一个字符串
compile:当正则需要反复使用且正则表达式很长的时候使用
finditer:返回一个迭代器为了节省内存(找到的东西很多,不能一下子全都放内存里用finditer)
1:findall def findall(pattern, string, flags=0): pattern:正则规则 string:待匹配的字符串 flags有很多可选值: re.I(IGNORECASE)忽略大小写,括号内是完整的写法 re.M(MULTILINE)多行模式,每一行都是新的开头,每到回车前都是新的结尾改变^和$的行为 re.S(DOTALL)点可以匹配任意字符,包括换行符,本来.是不可以匹配换行符的,加了re.S就可以匹配换行符了 re.L(LOCALE)做本地化识别的匹配,表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境,不推荐使用 windows和linux里面不同操作系统对不同的字符集会有不一样的地方,根据本地操作系统去识别 re.U(UNICODE) 使用\w \W \s \S \d \D使用取决于unicode定义的字符属性。在python3中默认使用该flag 根据unicode的编码去规定数字字母下划线,unicode兼容ascii码,对于数字字母下划线这一类的 看ascii码就够了,对于中文才涉及到unicode re.X(VERBOSE)冗长模式,该模式下pattern字符串可以是多行的,忽略空白字符,并可以添加注释 把多行字符串变成一个字符串了, findall返回所有满足匹配带条件的结果放在列表中 split: ret = re.findall('a', 'eva egon yuan') # 返回字符串里所有的a,两个a print(ret) # 结果 : ['a', 'a'] ret = re.findall('[a-z]', 'eva egon yuan') # 匹配所有的a-z的字母 print(ret) # ['e', 'v', 'a', 'e', 'g', 'o', 'n', 'y', 'u', 'a', 'n'] ret = re.findall('[a-z]+', 'eva egon yuan') # 匹配所有的a-z的字母任意次数 print(ret) # ['eva', 'egon', 'yuan'] ret = re.findall('[a-z]*', 'eva egon yuan') # 匹配所有的a-z的字母任意次数 print(ret) # ['eva', '', 'egon', '', 'yuan', '']
2:search:找整个字符串,遇到匹配上的就返回,遇不到返回None
比如从字符串中找一个a,只找到一个a,从前往后找到一个就返回,且返回的时候返回的是一个 <_sre.SRE_Match object; span=(2, 3), match='a'> 看成一个结果的变量 这个返回变量需要调用.group()才能获取到结果 如果元素找不到,返回的ret是None,None没有group方法,调用group会报错 import re ret = re.search('a', 'eva egon yuan') if ret: # 如果ret存在在调用group(),ret如果是None那么不会进入if循环里,不打印也不报错 ---ret这样用 print(ret.group()) # a # 函数会在字符串内查找模式匹配,直到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以 # 通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None
3:match:match和search的用法完全一样的,match是从头开始匹配,如果正则规则从头开始可以匹配上,就返回一个变量。 匹配的内容需要用group才能显示 如果没匹配上,就返回None,调用group会报错 match:匹配,从头开始匹配 seach:寻找到就行,找整个字符串 import re ret = re.match('[a-z]+', 'eva egon yuan') # a-z匹配1个到任意个 if ret: print(ret.group()) # eva
4:re.split:按照正则关系进行split切割 ret = re.split('[ab]', 'abcd') :先按'a'分割得到''和'bcd',在对''和'bcd'分别按'b'分割 import re ret = re.split('[ab]', 'abcd') # 先按'a'分割得到''和'bcd' 再对''和'bcd'分别按'b'分割 print(ret) # ['', '', 'cd']
5: re.sub:类似replace 规则替换,数字替换,字母替换都可以 import re ret = re.sub('\d', 'H', 'eva3egon4yuan4' ,1) # 将\d数字替换成'H',参数1表示只替换1个,默认全部替换 print(ret) # evaHegon4yuan4
6:re.subn:和sub用法一样,但是返回结果是个元组,返回替换的结果和替换的次数 import re ret = re.subn('\d', 'H', 'eva3egon4yuan4') # 将数字替换成'H',返回元组(替换的结果,替换了多少次) print(ret) # ('evaHegonHyuanH', 3)
7:re.compile:内置函数的compile就是转义成计算机能够识别的二进制,re里面的也类似 把re表达式转义成计算机能够识别的机器代码---编译正则规则,拿到正则表达式对象 用同一个正则规则匹配不同的字符串,把正则规则编译一下 import re obj = re.compile('\d{3}') # 将正则表达式编译成为一个正则表达式对象,规则要匹配的是3个数字 ret = obj.search('abc123eeee') # 正则表达式对象调用search,参数为待匹配的字符串 print(ret.group()) # 123 ret = obj.search('abcashgjgsdghkash456eeee3wr2') # 正则表达式对象调用search,参数为待匹配的字符串 print(ret.group()) # 456
8:re.finditer :finditer返回一个存放匹配结果的迭代器 import re ret = re.finditer('\d', 'ds3sy4784a') # finditer返回一个存放匹配结果的迭代器 print(ret) # <callable_iterator object at 0x000001B82A46AE50> # print(next(ret).group()) #查看第一个结果 # print(next(ret).group()) #查看第二个结果 # print([i.group() for i in ret]) #查看剩余的左右结果 for i in ret: # i还是对象元素,可以group()调用拿到元素 print(i.group())
9:正则里可以取分组内的:如下 ^([1-9])(\d{14})(\d{2}[0-9x])?$ 这个表达式: group(0):打印全部的信息,整个表达式的信息 ---默认group()打印的就是group(0) group(1):打印第一个括号表达式匹配到的:一定要括号才是分组 group(2):打印第二个括号表达式匹配到的 group(3):打印第三个括号表达式匹配到的 这个分组的group的机制和正则表达式规则没有关系,只和python相关 用search的group来说一下子就能取到全部,取局部分组按照顺序取取就行了 search和match都有的机制,都要group拿到结果,都能拿到group分组里面的值 import re ret = re.search('^([1-9])(\d{14})(\d{2}[0-9x])?$', '110105199912122277') print(ret.group()) # 110105199912122277 print(ret.group(0)) # 110105199912122277 print(ret.group(1)) # 1 print(ret.group(2)) # 10105199912122 print(ret.group(3)) # 277 res = re.findall('^([1-9])(\d{14})(\d{2}[0-9x])?$', '110105199912122277') ret = re.findall('([1-9]\d{14})', '110105199912122277') print(res) # [('1', '10105199912122', '277')] print(ret) # ['110105199912122']
10:findall和split:存在的分组优先问题:
findall会优先把匹配结果组里内容返回, 如果想要匹配结果,分组里面加?取消权限即可 findall和split原本都没有分组的机制,就在这里有分组的情况做了一次分组优先,无论findall还是split 只要内部有分组就特别对待 findall有分组就优先显示分组里的内容 split有分组就在切割的时候保留下分组切点的内容 正常情况下没有分组就正常匹配,正常切 import re ret = re.findall('www.(baidu|oldboy).com', 'www.oldboy.com') # (baidu|oldboy) 分组:baidu或者oldboy都可以匹配 print(ret) # ['oldboy']
# 本来打算匹配www.oldboy.com全部网址内容,但是现在只匹配了上了oldboy # findall没有对分组的group机制,只能优先匹配分组里的 # 这是因为findall会优先把匹配结果组里内容返回, # 如果想要匹配结果,取消权限即可 使用?可以取消分组优先 ret = re.findall('www.(?:baidu|oldboy).com', 'www.oldboy.com') # ?取消分组优先 print(ret) # ['www.oldboy.com']
ret = re.split("\d+", "eva3egon4yuan") print(ret) # 结果 : ['eva', 'egon', 'yuan'] ret = re.split("(\d+)", "eva3egon4yuan") # 加了分组,会保留切掉的内容 print(ret) # 结果 : ['eva', '3', 'egon', '4', 'yuan']
正则中 ? 的三种功能:多功能
1:当量词表示匹配0次或者一次
2:放在量词后面表示非贪婪匹配
3:放在分组的第一个,取消分组优先
简单爬虫练手: url从网页上把代码搞下来 bytes decode ——> utf-8 网页内容就是我的待匹配字符串 ret = re.findall(正则规则,待匹配的字符串)
ret是所有匹配到的内容组成的列表 返回一个生成器 一页是一个列表,每一条匹配上的信息是一个元组(元组就是分组里单位内容) import re import requests def get_page(url): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'} res = requests.get(url, headers=headers) return res.text def parse_page(s): ret = re.findall( '<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>' '.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)评价</span>', s, re.S) return ret ''' com = re.complite( '<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*? <span class="title">(?P<title>.*?)</span>' '.*?<span class="rating_num" .*?>(?P<rating_num>.*?) </span>.*?<span>(?P<comment_num>.*?)评价</span>',s,re.S) ret=com.fiditer(s) 匹配完整的字符串,对想要的信息加上分组就行了: findall和split:存在的问题:findall会优先把匹配结果组里内容返回,如果想要匹配完整结果(取消分组优先),分组里面加?取消权限即可 优先提取分组里的内容,对想要的内容做分组
.*? 任意字符的惰性匹配
(?P<id>\d+) 这是一个分组, \d+这是正则匹配表达式 ?P<id>:给分组取的名字叫id, ?P<>是固定搭配,id是分组名称 以前可以可以group1和group2去找结果, 现在不仅仅名字找还可以名称取找,方便 group('id') 取(?P<id>\d+)这个分组的正则匹配到的内容.... ''' def main(num): url = 'https://movie.douban.com/top250?start=%s&filter=' % num response_html = get_page(url) ret = parse_page(response_html) print(ret) count = 0 for i in range(10): # 10页 main(count) count += 25 # url从网页上把代码搞下来 # bytes decode ——> utf-8 网页内容就是我的待匹配字符串 # ret = re.findall(正则,带匹配的字符串) ret是所有匹配到的内容组成的列表
# 正则解析:贪婪和非贪婪,findall优先打印分组内的内容 import re st = "aaaabbbbccccdddd" print(re.findall("a.*?d", st)) # ['aaaabbbbccccd'] print(re.findall("a.*d", st)) # ['aaaabbbbccccdddd'] print(re.findall("a(.*)d", st)) # ['aaabbbbccccddd'] 优先打印分组内的,尽可能多的匹配 print(re.findall("a(.*?)d", st)) # ['aaabbbbcccc'] print(re.search("a.*?d", st).group()) # aaaabbbbccccd print(re.search("a.*d", st).group()) # aaaabbbbccccdddd print(re.search("a(.*)d", st).group()) # aaaabbbbccccdddd print(re.search("a(.*?)d", st).group()) # aaaabbbbccccd
# 正则表达式分组取名:?P<name> 给分组取名字,获取的匹配结果可以直接使用group("名字")拿到对应值 import re ret = re.search("<(?P<tag_name>\w+)>\w+</(?P=tag_name)>", "<h1>hello</h1>") # \w:匹配字母数字和下划线 print(ret.group('tag_name')) # 结果 :h1 print(ret.group()) # 结果 :<h1>hello</h1> print(ret.group(0)) # <h1>hello</h1> group(0)等同group()打印匹配到的全部的信息,整个表达式的信息 print(ret.group(1)) # h1 # 可以在分组中利用?p<name>的形式给分组起名字 # 获取的匹配结果可以直接用group('名字')拿到对应的值 (?P<tag_name>\w+):分组,\w+正则表达式,?P<tag_name>组的名字:tag_name (?P=tag_name):用了tag_name这个名字,就是这个地方和(?P<tag_name>\w+)这里匹配的东西一模一样的 前后匹配的东西需要一模一样 \w:数字字母下划线 +:匹配1次或者多次 命名和引用必须在一条正则里 import re ret = re.search(r"<(\w+)>\w+</\1>", "<h1>hello</h1>") print(ret.group(1)) # h1 print(ret.group()) # <h1>hello</h1> # 如果不给组起名字,也可以用 \序号 来找到对应的组,表示要找的内容和前面的组内容一致 # \1==(?P=tag_name) 效果一模一样 # 获取的匹配结果可以直接用group(序号)拿到对应的值
# 匹配整数 import re ret = re.findall(r"\d+\.\d+|\d+", "1-2*(60+(-40.35/5)-(-4*3))") print(ret) # ['1', '2', '60', '40.35', '5', '4', '3'] ret = re.findall(r"\d+\.\d+|(\d+)", "1-2*(60+(-40.35/5)-(-4*3))") # | 或:先匹配长的再匹配短的筛选自己想要的
# (如果先匹配了短的\d+那么不会匹配到小数了,匹配到整数就会返回) # 对自己真正想要的东西进行分组强行优先--找到想要的东西 # \d+\.\d+: 先匹配小数, .需要转义,不然能匹配任意字符 # (\d+): 再匹配整数 print(ret) # ['1', '2', '60', '', '5', '4', '3'] # 加了(\d+)分组,优先匹配分组里面都是整数的 # 匹配上了\d+\.\d+小数也不显示,非要显示(\d+)匹配上的内容 # 没有匹上,显示一个空在这里匹配上的位置 ret.remove('') print(ret) # ['1', '2', '60', '5', '4', '3']
print(re.findall(r"\d+\.\d+|\d+", "1-2*(60+(-40.35/5)-(-4*3))"))
# ['1', '2', '60', '40.35', '5', '4', '3']
print(re.findall(r"\d+|\d+\.\d", "1-2*(60+(-40.35/5)-(-4*3))"))
# ['1', '2', '60', '40', '35', '5', '4', '3']
# 先匹配长的小数,再匹配短的整数,如果先匹配短的整数那么就会匹配不上小数数据
# 匹配:-负数优先,然后正数 import re ret = re.findall(r"-?\d+\.\d*|(-?\d+)", "1-2*(60+(-40.35/5)-(-4*3))") print(ret) # ['1', '-2', '60', '', '5', '-4', '3'] ret.remove("") print(ret) # ['1', '-2', '60', '5', '-4', '3']
#-?:- 符号出现0次或者1次那么就可匹配负数或者正数
15:写一个简单的能计算加减乘除的结果的脚本
首先得到一个数学表达式的字符串 1:去空格得到没有空格的字符串 2:先算最里层括号里的 : 找括号 ,且括号里没有其他括号 3:得到了一个没有括号的表达式 :只有加减乘除 从左到右先找到第一个乘除法 —— 重复 4:所有的乘除法都做完了 5:计算加减 —— 加减法 6:只有一个数了 就可以结束了 import re a = '1 - 2 * ( ( 6 0 -3 0 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )' a = a.replace(' ', '') # print(a) #1-2*((60-30+(-40/5)*(9-2*5/3+7/3*99/4*2998+10*568/14))-(-4*3)/(16-3*2)) # res=re.findall('\(.*?\)',a) res = re.findall('(\([^\(\)]*?\))', a) print(res) # ['(-40/5)', '(9-2*5/3+7/3*99/4*2998+10*568/14)', '(-4*3)', '(16-3*2)'] # def String_calculation(s): # a.replace('')
a.replace(' ', ''):1:把a字符串所有的空格去掉得到一个没用空格的字符串
re.findall('(\([^\(\)]*?\))', a):2:找到一个带一对括号的表达式,括号里面没有括号才行
16:collections模块:python除了基础数据类型外collectins模块扩展了一些其他数据类型
1:collections模块:python中的扩展数据类型模块 在内置数据类型(dict、list、set、tuple)的基础上, collections模块还提供了几个额外的数据类型:
Counter、deque、defaultdict、namedtuple和OrderedDict等。 1.namedtuple: 生成可以使用名字来访问元素内容的tuple 2.deque: 双端队列,可以快速的从另外一侧追加和推出对象 3.Counter: 计数器,主要用来计数 4.OrderedDict: 有序字典 5.defaultdict: 带有默认值的字典 python常见的数据类型 列表、元祖 字典 集合、frozenset(不可变的集合) 字符串 堆栈 : 一种数据结构,先进后出,后进先出 队列 :先进先出 FIFO
1:namedtuple:可命名元组 我们知道tuple元组可以表示不变集合,例如,一个点的二维坐标就可以表示成:p = (1, 2) 但是,看到(1, 2),很难看出这个tuple是用来表示一个坐标的。 这时,namedtuple就派上了用场: 使用场景:1:创建一个点 2:创建一张扑克牌,每张扑克牌有花色和数字 # case1:描述一个点的可命名元组 from collections import namedtuple Point = namedtuple('point', ['x', 'y'])
# 元组里面即将有几个元素就可以这里列表写几个元素来给元组里面每个元组命名,可以 x,y,z p1 = Point(1, 2) p2 = Point(2, 4) # 这样p1和p2都具有x,y这两个名字了,这时候必须传参x y两个数,少一个多一个都不可以--对应 print(p1.x) # 1 print(p1.y) # 2 print(p1) # point(x=1, y=2) print(p2) # point(x=2, y=4)
# 上面p1是一个命名元组对象,p2也是一个命名元组对象
# case2:描述一个扑克牌的可命名元组,这样描述使用更加清晰 from collections import namedtuple Card = namedtuple('card', ['suits', 'number']) card1 = Card('红桃', 2) print(card1) # card(suits='红桃', number=2) # case3:类似的,如果要用坐标和半径表示一个圆,也可以用namedtuple定义: # namedtuple('名称', [属性list]): Circle = namedtuple('Circle', ['x', 'y', 'r'])
2:queue队列和deque双端队列 使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了, 因为list是线性存储,数据量大的时候,插入和删除效率很低。 deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈 队列 :先进先出,后进后出,买票, FIFO:frist in frist out deque除了实现list的append()和pop()外,还支持appendleft()和popleft(), 这样就可以非常高效地往头部添加或删除元素
# 1:队列:queue 只能get和put操作取值和放值 import queue q = queue.Queue() # 创建一个空的队列q q.put(10) # put 往队列里面放东西,类似append(丢后面) q.put(5) q.put(6) print(q) # <queue.Queue object at 0x000001F64645C080>:打印队列对象 res = q.get() print(res) # 10 队列先进先出,后进后出,10先进先出 res = q.get() print(res) # 5 res = q.get() print(res) # 6 res = q.get() print(res) # 值取完了再取不会报错, 这里的get类似input,不给值会一直等阻塞这里 # 不给值整个程序阻塞在这里,直到再给一个值就停止阻塞了,不然一直等着 print(q.qsize()) # 查看队列的大小,就是队列里有多少元素 # for i in q: # 队列不是可迭代的,内部没用__iter__方法,所以不能循环.for循环就报错 # print(i) # 2:deque:双端队列:一个管道两个头,可以前面放也可以后面放,可以前面取值也可以后面取值 # 这就是双端队列 from collections import deque dq = deque([1, 2]) # 创建一个双端队列对象,队列里面放了一个列表[1,2] dq.append(1) # 正常的从双端队列后面append添加数据 dq.appendleft('b') # 从双端队列前面放数据 dq.insert(2, 3) # insert双端队列的中间插队,0是一号位置,2是三号位置插入一个元素3 print(dq) # deque(['b', 1, 3, 2, 1]) print(dq.pop()) # 打印:1 从后面取数据, print(dq.popleft()) # 打印:b 从前面取数据 dq.reverse() # 反向 print(dq) # deque([2, 3, 1])
队列queue先进先出,栈先进后出,双端deque没用先进先出后进后出的概念能从左右两边取值和添加元素,类似一个列表
3:OrderedDict:有序字典 使用dict时,Key是无序的。在对dict做迭代时,我们无法确定Key的顺序。 如果要保持Key的顺序,可以用OrderedDict有序字典 from collections import OrderedDict d = dict([('a', 1), ('b', 2), ('c', 3)]) # 定义dict普通字典: (key value),可以理解把列表套数组强转字典 print(d) # {'a': 1, 'b': 2, 'c': 3} ,dict的Key是无序的 od = OrderedDict([('a', 1), ('b', 2), ('c', 3)]) print(od) # OrderedDict的Key是有序的 :OrderedDict([('a', 1), ('b', 2), ('c', 3)]) print(od['a']) # 打印:1 可以通过key value的形式访问,依然可以循环 print(od.get('a')) # 打印:1 也可以get取值,和字典的用法完全一样 for key in od: # 支持for循环 print(key) print(od[1]) # 报错,就算这个字典有序了但是本质还是一个字典,不能通过索引取值 # 字典通过key取value是非常块的,但是字典的存储比列表要占用内存一些,有序的字典又要更加占内存一点 # 所以很长的字典不太适合使用有序字典
4:defaultdict :带有默认值的字典 有如下值集合 [11,22,33,44,55,66,77,88,99,90...], 将所有大于 66 的值保存至字典的第一个key中,将小于 66 的值保存至第二个key的值中。 即: {'k1': 大于66 , 'k2': 小于66} from collections import defaultdict values = [11, 22, 33, 44, 55, 66, 77, 88, 99, 90] my_dict = defaultdict(list) # 没有给这个字典添加任何元素的时候,所有的value都默认是list # 可以默认是各种类型的参数,但是参数必须是callable可以被调用的参数(名字) # list,set,dict都是可调用的 # 比如默认值是个5就不行,因为5不可以被调用 for value in values: if value > 66: my_dict['k1'].append(value) else: my_dict['k2'].append(value) # 使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict: from collections import defaultdict dd = defaultdict(lambda: 'N/A') # lambda: 'N/A' 匿名函数可以调用,返回的值:N/A, # 字典任意的key都有一个默认的值 dd['key1'] = 'abc' print(dd['key1']) # 返回:abc key1存在 print(dd['key2']) # 返回:N/A key2不存在,返回默认值
# 调用dd['key2']的时候默认字典dd就会在字典添加一个键"key2"值等于默认的"N/A"
from collections import defaultdict # dd = defaultdict(5) # 这里使用会报错TypeError: first argument must be callable or None # 希望字典默认是5,但是参数必须是callable可以调用的,所有需要使用lambda匿名函数才能指定默认值5 dd = defaultdict(lambda: 5)
from collections import defaultdict dd = defaultdict(lambda: 5) # 这个字典的默认值可以是任何数据:list,dict,set,值都可以 print(dd['a']) # 5 这就是默认字典:字典里面任意的key都有一个默认值5 print(dd) # defaultdict(<function <lambda> at 0x0000023308C12EA0>, {'a': 5}) # 类似dd['a']通过字典key查询值的时候字典内马上就创建了一个字典的元素
5:Counter:计数器 可以对一个字符串进行计算,直接Counter一下就打印a有几个,b有几个....能够马上计算出来---计数器,只能计算字符串 Counter类的目的是用来跟踪值出现的次数。它是一个无序的容器类型, 以字典的键值对形式存储,其中元素作为key,其计数作为value。 计数值可以是任意的Interger(包括0和负数)。Counter类和其他语言的bags或multisets很相似。 from collections import Counter c = Counter('abcdeabcdabcaba') print(c) # Counter({'a': 5, 'b': 4, 'c': 3, 'd': 2, 'e': 1})
17:time模块(时间模块)
time模块 1:time.sleep(100) # (线程)推迟指定的时间运行。单位为秒 2:time.time() # 获取当前时间戳,返回一个以秒为单位的浮点数,1970-01-01到现在的秒数
表示时间的三种方式 (1)时间戳(timestamp) :通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量。 我们运行"type(time.time())",返回的是float类型。 计算机只认识010101,和十进制的数最接近,时间戳给计算机看的 (2)格式化的时间字符串(Format String): ‘1999-12-06’ 给人看,直观 (3)元组(struct_time:结构化的时间) :struct_time元组共九个元素: (年,月,日,时,分,秒,一年中第几周,一年中第几天等) 计算用的,2018年五月-2016年8月差了多少时间--计算 夏令时:到了夏天凌晨把表调快两个小时,过了这段时间调整回去
# 1:时间戳 import time print(time.time()) # 1642942612.47405 看到当前的时间戳
2:格式化的时间:strftime :str format time :格式化时间 %Y:Year年 %m:month月 %d:day 日 %x:忘记,可以不用 %H:hour时 %M:minutes分 %S:second秒 年月日时分秒都是固定的,格式是固定的 ---计算当前时间 %y 两位数的年份表示(00-99) %Y 四位数的年份表示(000-9999) %m 月份(01-12) %d 月内中的一天(0-31) %H 24小时制小时数(0-23) %I 12小时制小时数(01-12) %M 分钟数(00=59) %S 秒(00-59) %a 本地简化星期名称 %A 本地完整星期名称 %b 本地简化的月份名称 %B 本地完整的月份名称 %c 本地相应的日期表示和时间表示 %j 年内的一天(001-366) %p 本地A.M.或P.M.的等价符 %U 一年中的星期数(00-53)星期天为星期的开始 %w 星期(0-6),星期天为星期的开始 %W 一年中的星期数(00-53)星期一为星期的开始 %x 本地相应的日期表示 %X 本地相应的时间表示 %Z 当前时区的名称 %% %号本身 import time print(time.strftime('%Y-%m-%d %x')) # 2022-01-23 01/23/22 print(time.strftime('%Y-%m-%d %a %H:%M:%S')) # 2022-01-23 Sun 20:58:03
3:时间元组(struct结构化时间):localtime将一个时间戳转换为当前时区的struct_time import time struct_time = time.localtime() # 打印元组结构化时间 print(struct_time) # time.struct_time(tm_year=2021, tm_mon=2, tm_mday=16, tm_hour=11, tm_min=8, tm_sec=34, tm_wday=1, tm_yday=47, # tm_isdst=0) print(struct_time.tm_year) # 2022 有点像可命名元组,元组里每个都有一个名字,调用名字拿到结果
4:Format String ---struct_time ---timestamp Format String和timestamp不能直接转化,需要通过struct_time结构化时间去转化 时间戳转化成结构化时间再转化成格式化时间 (中间转化都需要通过结构化时间转化)
# 时间戳-->结构化时间 # 1:time.gmtime(时间戳) # UTC时间,与英国伦敦当地时间一致 # 2:time.localtime(时间戳) # 当地时间。例如我们现在在北京执行这个方法: # 与UTC时间相差8小时,UTC时间+8小时 = 北京时间 import time print(time.gmtime(1500000000))
# time.struct_time(tm_year=2017, tm_mon=7, tm_mday=14, tm_hour=2, tm_min=40, tm_sec=0, tm_wday=4, tm_yday=195, tm_isdst=0) print(time.localtime(1500000000))
# time.struct_time(tm_year=2017, tm_mon=7, tm_mday=14, tm_hour=10, tm_min=40, tm_sec=0, tm_wday=4, tm_yday=195, tm_isdst=0)
# 结构化时间-->时间戳 time.mktime(结构化时间) time_tuple = time.localtime(1500000000) # time_tuple是一个结构化时间 print(time.mktime(time_tuple)) # 1500000000.0(使用time.mktime把结构化时间转化成时间戳) now_time = time.time() print(now_time) # 1613445579.0678723(时间戳) print(time.localtime(now_time)) # localtime把时间戳转结构化时间 print(time.gmtime(now_time)) # 格林位置时间,返回一个结构化的时间 print(time.mktime(time.localtime(now_time))) # 1613445638.0 mktime又把结构化时间转时间戳时间
# 结构化时间-->字符串时间 time.strftime # time.strftime("格式定义",结构化时间) 结构化时间参数若不传,则显示当前时间 print(time.strftime("%Y-%m-%d %X", time.localtime(1500000000)))
# 字符串格式化时间-->结构化时间 # time.strptime(时间字符串,字符串对应格式) 还需要告诉计算机什么格式写的 ---转成结构化时间 print(time.strptime("2017-03-16", "%Y-%m-%d")) # 返回结构化时间 print(time.strptime("07/24/2017", "%m/%d/%Y"))
5:结构化时间 --> 格式化时间 time.asctime(结构化时间) 如果不传参数,直接返回当前时间的格式化串 import time print(time.localtime(1500000000)) # time.struct_time(tm_year=2017, tm_mon=7, tm_mday=14, # tm_hour=10, tm_min=40, tm_sec=0, tm_wday=4, tm_yday=195, tm_isdst=0) res = time.asctime(time.localtime(1500000000)) print(res) # Fri Jul 14 10:40:00 2017 # localtime拿到结构化时间 ,然后asctime转成格式化字符串完整时间
6:时间戳 --> 格式化时间 time.ctime(时间戳) 如果不传参数,直接返回当前时间的格式化串 import time print(time.ctime()) # Sun Jan 23 23:24:29 2022 print(time.ctime(1500000000)) # Fri Jul 14 10:40:00 2017
7:计算时间差: '2017-09-11 08:30:00','%Y-%m-%d %H:%M:%S' '2017-09-12 11:00:00','%Y-%m-%d %H:%M:%S' 1:中间差了多少秒:换成时间戳直接减 2:中间过了几年几月几时几秒 # 1:中间差了多少秒---换成时间戳直接减 # 计算时间差 import datetime starttime = datetime.datetime(1999,11,25) endtime = datetime.datetime.now() print(starttime) # 1999-11-25 00:00:00 print(endtime) # 2021-02-17 01:24:09.902518 print(endtime-starttime) import time true_time = time.mktime(time.strptime('2017-09-11 08:30:00', '%Y-%m-%d %H:%M:%S')) print(true_time) # 1505089800.0 # 结构化时间转化为时间戳(格式化时间字符串转结构化时间字符串) time_now = time.mktime(time.strptime('2017-09-12 11:00:00', '%Y-%m-%d %H:%M:%S')) # 结构化时间转化为时间戳(格式化时间字符串转结构化时间字符串) print(time_now) # 1505185200.0 dif_time = time_now - true_time # 时间差 print(dif_time) # 95400.0 时间差 struct_time = time.gmtime(dif_time) # 时间戳转化结构化时间 print(struct_time) # 得到一个时间戳,时间戳转化成结构化时间struct_time,就是计算和1970年的时间差 # 再利用和1970年的时间相减得到结果 print('过去了%d年%d月%d天%d小时%d分钟%d秒' % (struct_time.tm_year - 1970, struct_time.tm_mon - 1, struct_time.tm_mday - 1, struct_time.tm_hour, struct_time.tm_min, struct_time.tm_sec))
# 过去了0年0月1天2小时30分钟0秒
18:random随机模块
import random 1: 随机小数:random.random:返回大于0且小于1之间的小数 res = random.random() # 大于0且小于1之间的小数 print(res) # 0.9655396862881808
2:random.uniform(1,3):大于1小于3的小数 res2 = random.uniform(1, 3) # 大于1小于3的小数 print(res2) # 1.2475232082135344
3:随机整数:random.randint和random.randrange(1,10,2) res3 = random.randint(1, 5) # 大于等于1且小于等于5之间的整数 1-5包括1和5,randint不能传步长 print(res3) # 5 res4 = random.randrange(1, 10, 2) # 大于等于1且小于10之间的奇数,可以传步长 1-10不包括10 print(res4) # 3 4:random.choice随机选择一个返回 random.choice(可迭代的) ---从可迭代的里面选一个返回 res5 = random.choice([1, '23', [4, 5]]) # 1或者23或者[4,5] print(res5) 5:random.sample:随机选择多个返回,返回的个数为函数的第二个参数 res6 = random.sample([1, '23', [4, 5]], 2) # 列表元素任意抽2个组合一起 [[4, 5], 1] print(res6) 6:打乱列表顺序:random.shuffle item = [1, 3, 5, 7, 9] random.shuffle(item) # 打乱次序 print(item) # [1, 3, 9, 7, 5] ---洗牌的时候使用
一个需求: 生成随机验证码 1234 432145 Abc123 a17698 int:数字类型的验证码 0-9 chr:字母类型的验证码 [65-90] 数字 字母 = chr(数字) ascii码转化 随机数字 随机选一个 [随机数字,随机字母] # case1 import random number = str(random.randint(0, 9)) # 数字0-9 letter = chr(random.randint(65, 90)) Verification_Code = '' for i in range(6): Verification_Code = Verification_Code + random.choice([number, letter]) print(Verification_Code) # MM888M # case2 import random def v_code(): code = '' for i in range(5): num = random.randint(0, 9) alf = chr(random.randint(65, 90)) add = random.choice([num, alf]) code = "".join([code, str(add)]) return code print(v_code()) # WYZPH
19:os模块 os模块是和操作系统相关的模块
os模块:os模块是和操作系统相关的模块,os模块所管的范畴不是python解释器所在的局限 整个操作系统的东西都有权限操作,使用各种os方法操作----借助操作系统来操作的 os模块常用的方法: os.getcwd() 返回现在执行文件所在的目录,根据现在目录找其他目录 os.chdir() 改变当前脚本的工作目录 print(os.curdir) #. 返回当前目录:('.') print(os.pardir) #.. 获取当前目录的父目录字符串名('..') os.makedirs('dirname1/dirname2') 可生成多层递归目录 os.removedirs('dirname1') 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推 os.mkdir('dirname') 生成单级目录;相当于shell中mkdir dirname os.rmdir('dirname') 删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname os.listdir('dirname') 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印 os.remove() 删除一个文件 os.rename("oldname","newname") 重命名文件/目录 os.stat('path/filename') 获取文件/目录信息 os.system("bash command") 运行shell命令,直接显示 os.popen("bash command).read() 运行shell命令,获取执行结果 os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径 os.chdir("dirname") 改变当前脚本工作目录;相当于shell下cd os.path相关的方法 os.path.abspath(path) 返回path规范化的绝对路径 os.path.split(path) 将path分割成目录和文件名二元组返回 os.path.dirname(path) 返回path的目录。其实就是os.path.split(path)的第一个元素 os.path.basename(path) 返回path最后的文件名。如何path以/或\结尾,那么就会返回空值。即os.path.split(path)的第二个元素 os.path.exists(path) 如果path存在,返回True;如果path不存在,返回False os.path.isabs(path) 如果path是绝对路径,返回True os.path.isfile(path) 如果path是一个存在的文件,返回True。否则返回False os.path.isdir(path) 如果path是一个存在的目录,则返回True。否则返回False os.path.join(path1[, path2[, ...]]) 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略 os.path.getatime(path) 返回path所指向的文件或者目录的最后访问时间 os.path.getmtime(path) 返回path所指向的文件或者目录的最后修改时间 os.path.getsize(path) 返回path的大小
import os # 1:os.getcwd:返回现在执行文件所在的目录, print(os.getcwd()) # E:\Users\ywt\PycharmProjects\Python_Development\day19 # 2:改变当前脚本的工作目录,想用另外一个目录的文件,再执行open的话,相当于从 # E:\Users\ywt\PycharmProjects这个目录下先找文件了 ----了解一下就行 os.chdir(r'E:\Users\ywt\PycharmProjects') # 改变当前脚本的工作目录 os.chdir('..') # 使用相对路径往上走一层, 那么就走到了PycharmProjects目录的上一层 print(os.getcwd()) # E:\Users\ywt # 3:当前目录 .. ,上一层目录 .. ---相对路径 print(os.curdir) # 打印:. 返回当前目录:('.') print(os.pardir) # 打印:.. 获取当前目录的父目录字符串名('..') # 4:可生成多层递归目录 # os.makedirs('ywt3/ywt3') os.makedirs('E:/Users/ywt/PycharmProjects/Python_Development/day19/ywt3/ywt3') # 5:递归删除---目录存在就删除文件目录,不存在就报错, # ywt2删除后发现上一层的ywt1是空的ywt1也会删除,如此往上一直删除到不为空为止 os.makedirs('E:/Users/ywt/PycharmProjects/Python_Development/day19/ywt3/ywt3') # 创建ywt3/ywt3 os.removedirs('ywt3/ywt3') # 删除上面创建的:ywt3/ywt3 # 6:创建单级目录,创建ywt1/ywt2 这样创建的话需要ywt1存在才能创建ywt2,总之只创建最后一个/后的一个目录 os.mkdir('ywt1') os.mkdir('ywt1/ywt2') # os.mkdir('ywt3/ywt4') # ywt3不存在就报错,这里会报错 # 7:删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname os.rmdir('ywt1/ywt2') # 8:列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印 print(os.listdir(r'E:/Users/ywt/PycharmProjects/Python_Development')) # 9:os.remove() 删除一个文件,只删除文件不删除目录 # 10:os.rename("oldname","newname") 重命名文件/目录 # 11:os.stat('path/filename') 获取文件/目录信息 print(os.stat('test.py')) # stat 结构: # st_mode: inode 保护模式 # st_ino: inode 节点号。 # st_dev: inode 驻留的设备。 # st_nlink: inode 的链接数。 # st_uid: 所有者的用户ID。 # st_gid: 所有者的组ID。 # st_size: 普通文件以字节为单位的大小;包含等待某些特殊文件的数据。 # st_atime: 上次访问的时间。 # st_mtime: 最后一次修改的时间。 # st_ctime: 由操作系统报告的"ctime"。 # 在某些系统上(如Unix)是最新的元数据更改的时间,在 # 其它系统上(如Windows)是创建时间(详细信息参见平台的文档)。 # 12:os.sep 输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/" print(os.sep) # \ windwos操作系统上拼路径---让程序lunux上和windows跑都可以 --python代码跨平台 # 13:os.linesep 输出当前平台使用的行终止符,win下为"\r\n",Linux下为"\n" r_lis = [] r_lis.append(os.linesep) print(r_lis) # ['\r\n'] # 14:os.pathsep 输出用于分割文件路径的字符 win下为;,Linux下为: print(os.pathsep) # ; 环境变量的路径分隔符 # 15:os.name 输出字符串指示当前使用平台。win->'nt'; Linux->'posix' print(os.name) # nt nt表示windows # 16:os.system("bash command") 运行shell命令,直接显示,没有返回值,不能操作这些数据 os.system('dir') # python里面执行操作系统识别的语言,查看当前目录下有什么东西 # 17:os.popen("bash command).read() 运行shell命令,获取执行结果 res = os.popen('dir').read() print(res) # popen('dir') 有返回值的 # python解释器只能识别python代码。python解释器写shell需要借助python的os去执行操作系统能看懂的命令 # os模块:操作系统相关的模块----利用操作系统相关的模块执行操作系统的语言了 # 18:os.environ 获取系统环境变量 --- print(os.environ) # 19:os.path.abspath(path) 返回path规范化的绝对路径 print(os.path.abspath('.')) # E:\Users\ywt\PycharmProjects\Python_Development\day19 # 20:os.path.split(path) 将path分割成目录和文件名二元组返回 print(os.path.split(os.getcwd())) # ('E:\\Users\\ywt\\PycharmProjects\\Python_Development', 'day19') print(os.getcwd()) # E:\Users\ywt\PycharmProjects\Python_Development\day19 # 21:os.path.dirname(path) 返回path的目录。其实就是os.path.split(path)的第一个元素 print(os.path.dirname(os.getcwd())) # E:\Users\ywt\PycharmProjects\Python_Development # 22:os.path.basename(path) 返回path最后的文件名。如果path以/或\结尾, # 那么就会返回空值。即os.path.split(path)的第二个元素 print(os.path.basename(os.getcwd())) # day19 # 23:os.path.exists(path) 如果path存在,返回True;如果path不存在,返回False # 24:os.path.isabs(path) 如果path是绝对路径,返回True # 25:os.path.isfile(path) 如果path是一个存在的文件,返回True。否则返回False # 26:os.path.isdir(path) 如果path是一个存在的目录,则返回True。否则返回False # 27:os.path.join(path1[, path2[, ...]]) 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略 # 给一个路径或者几个值,按照当前操作系统的路径分隔符拼接起来 print(os.path.join('c', 'user', 'local')) # c\user\local # 28:os.path.getatime(path) 返回path所指向的文件或者目录的最后访问时间 # 29:os.path.getmtime(path) 返回path所指向的文件或者目录的最后修改时间 # 30:os.path.getsize(path) 返回path的大小 print(os.path.getsize(os.getcwd())) # 返回当前路径day19的文件大小 4096 print(os.path.getsize(os.getcwd() + '\\001 复习.py')) # 3370 查看文件001 复习.py大小 print(os.path.getsize(os.path.join(os.getcwd(), '001 复习'))) # 7378 查看文件001 复习的大小 # 计算文件夹的大小不准确,计算文件大小准确的 ---计算文件夹所有文件的大小,循环一下,把所有文件+起来就是文件大小
20:sys模块:sys模块是与python解释器交互的一个接口
sys模块:sys模块是与python解释器交互的一个接口 sys.argv 命令行参数List,第一个元素是程序本身路径 sys.exit(n) 退出程序,正常退出时exit(0),错误退出sys.exit(1) 返回给操作系统,让操作系统直到是正常退出还是错误退出 程序执行,人为输入不好的内容,自己恢复不了,使用 sys.exit(1) 发生给系统就关闭了,告诉系统错误关闭了----操作系统判断 sys.version 获取Python解释程序的版本信息 sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值 各种模块的导入路径,sys.path记录了所有搜索模块的路径 先从自己路径搜索,再上一层搜,再到python解释器自带的包里面搜了 sys.platform 返回操作系统平台名称 import sys print(sys.platform) # win32 32位这个玩意不准,返回操作系统平台名称 print(sys.version) # 获取Python解释程序的版本信息 # 3.9.5 (tags/v3.9.5:0a7dcbd, May 3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)] print(sys.exit()) # 执行到这里程序退出 # print('111') # 这里不会执行了,因为上面sys.exit()退出了程序
sys.path:返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值 各种模块的导入路径,sys.path记录了所有搜索模块的路径 先从自己路径搜索,再上一层搜,再到python解释器自带的包里面搜了 import sys print(sys.path) # 返回python path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值 sys.path.clear() print(sys.path) import re # ModuleNotFoundError: No module named 're' 但是实际执行的时候没有报错 # 清空了这个路径搜索列表,再import导入模块肯定报错--这个列表就是导入模块所引用的路径 # 列表里面还有寻找模块的先后顺序 ---修改sys.path来导入其他路径
sys.argv:命令行参数List,第一个元素是程序本身路径 文件有很多种执行方式: 1:创建一个test.py脚本内容如下: import sys print(sys.argv) 2:cmd下运行后面命令来启动test.py这个脚本:python test.py 1 2 3 4 5 6 ['test.py', '1', '2', '3', '4', '5', '6'] cmd命令下执行文件的时候,可以后面加参数, sys.argv:假设程序在服务器上直接执行, 执行程序的时候输入:python test.py alix 123456 输入alix用户名,密码:123456 用户名和密码当参数全部都传输到程序里了 可以程序里面直接判断 ret=sys.argv # ret获取到的是一个列表数据 name=ret[1] # 2号位置获取到用户名 pwd=ret[2] # 3号位置获取到密码 if name='alex' and pwd='123456': # 判断密码和用户名是否正确 print('登录成功') else: print('错误的用户名和密码') sys.exix() 登录成功后才可以使用计算器或者其他什么功能 没有登录成功sys.exix()退出程序 执行脚本之前就可以给脚本传一些参数,参数传递到sys.argv里面了 pycham执行就不得行,程序一般都是终端执行,
import sys
print(sys.argv) # ['E:/Users/ywt/PycharmProjects/Python_Development/day19/006 sys模块.py']
21:序列化模块 json pickle shelve
什么叫序列化:
将原本的字典,列表等内容转换成一个字符串的过程就叫做序列化。 str 转换成 其他数据类型 这就是反序列化 其他数据类型 转化成 str 这就是序列化 序列:就是字符串 字典和列表特殊数据类型转化成字符串使用场景 1:写文件(数据存储):文件写数据,不能写字典,只能写字符串 2:网络上传输只能传bytes,字典转化成字符串,字符串再转化成bytes类型
1:json模块:最通用的序列化格式,
loads:字符串转化成字典数据类型(反序列化):json.loads("字符串"): 返回一个字典类型数据
dumps:字典转字符串数据类型(序列化):json.dumps("字典"): 返回一个字符串类型数据
load:从文件一次性读取数据出来,读取出来的数据就是字典或者列表格式:json.load("文件句柄"): 返回从文件里读取出来的数据,拿到就是字典等格式
dump:把一个字典或者列表直接写入文件里:json.dump("字典列表等数据", "f文件句柄", "ensure_ascill参数"): 把字典等数据写入f文件,返回值为None 只有很少一部分的数据类型能够通过json转化成字符串 dict转str str转dict
# 1:字典转成str字符串: json.dumps:序列化方法 # 正常情况下字典按类型的python解释器打印key和value的时候都是单引号的,如果转化成json的时候会双引号显示 # json对数据格式要求很严格,json本身外面是一个'',内部所有的字符串元素都需要使用""双引号引起来 import json dic = {"k1": "v1"} print(json.dumps(dic), type(json.dumps(dic))) # {"k1": "v1"} <class 'str'> # 2:字符串转字典 json.loads:反序列化方法 import json str_a = '{"zzzz":1111}' print(json.loads(str_a), type(json.loads(str_a))) # {'zzzz': 1111} <class 'dict'> # 正常情况下:数字,字符串,列表,字典来进行序列化 # 元组也能进行序列化 # 3:元组进行序列化,序列化当成一个列表,转化回来后又变成一个列表了 import json tup = (1, 2, 3, 4) res = json.dumps(tup) print(res, type(res)) # [1, 2, 3, 4] <class 'str'> # 元组序列化之后成了一个列表了[1, 2, 3, 4] print(json.loads(res), type(json.load(res))) # [1, 2, 3, 4] <class 'list'> # 4:set集合类型不能使用json.dumps进行序列化转化成字符串的 import json set1 = {1, 2, 3} print(json.dumps(set1)) # TypeError: Object of type 'set' is not JSON serializable # json只能转化成很少一部分数据类型:集合不能转化的,字典,列表和元组能够进行转化 # json.dumps和json.loads直接在内存中操作一个可以被序列化的类型把他转化成字符串,然后转回来的所有类型 # 5:json.dumps和json.loads直接对内存当中的数据进行操作,操作完了数据还在内存里 # json.dump和json.load是和文件相关的操作(读和写) # 1:json.dump:可以把字典写入到文件里(json支持的数据类型可以使用json.dump方法直接写到文件) # 2:json.load:可以把文件里的字符串读取出来,读出来后是一个字典格式 # 分段往文件里写也能分段往外读, import json dic = {"yuanwentao": "帅哥"} with open('fff', mode="w", encoding="utf8") as f: # 写的模式打开一个文件 json.dump(dic, f, ensure_ascii=False) # 使用json.dump把一个字典dic写入文件fff 这一步:类似把字典类型直接写入文件fff # 正常情况不能把字典写到文件,使用json.dump可以直接把字典写入f文件 # 先把字典转成一个字符串(序列化)然后传进去 # 上面这样直接写中文"帅哥"到fff文件,查看fff文件里的中文写成bytes类型了 # 如果不想写如中文变成bytes类型,需要dump的时候加个参数:ensure_ascii=False # 加了参数后写到fff文件的"帅哥"就变成中文了,默认ensure_ascii=True, # 如果读取的数据不是ascii码,解读不了就以bytes数据类型写进去了 # 指定了ensure_ascii=False,还是按照正常的编码格式写进去了 ----不看也行 f = open('fff', encoding='utf8') # 不指定文件格式encoding,默认操作系统打开什么编码就是什么编码,默认gbk打开
res = json.load(f) print(res, type(res)) # {'yuanwentao': '帅哥'} <class 'dict'> f.close()
# load读取文件中的数据,dump把数据写入文件 # 6:dump两条数据
多个数据dump进去的时候都会写在一行 ,假设有多条数据然后load读出来会 报错 # json的dump和load只能一次性写进去然后一次性读出来 # dump写进去没问题,但是load读多条数据不得行,报错 import json f = open('ywt', 'w', encoding='utf8') json.dump({"傻逼": "李狗"}, f, ensure_ascii=False) json.dump((1, 2, 3, 4), f, ensure_ascii=False) f.close() f = open('ywt', encoding='utf8') json.load(f) # 这里因为ywt文件有两条json数据在同一行,所以这里load多条数据出来会报错 # 7:json:dumps一条数据加一个 \n # dumps {} ----> '{} \n' # 相当于每一个字典写一行 # 读文件的时候直接一行行读取,读出来的每一行都是字符串,’{} \n‘ 这种字符串 # 对每一行字符串进行一个loads # 写 l = [{"k1": "111"}, {"k2": "222"}, {"k3": "333"}] # 对这三个字典分次写到文件里 import json f = open('file', 'w', encoding='utf8') for dic in l: str_dic = json.dumps(dic) # 拿到字符串类型的数据 ---内存里变成字符串写文件里 f.write(str_dic + "\n") # 写的字符串每行后面加换行 f.close() # 读 import json f = open('file') # f是一个迭代器 l = [] for line in f: # 遍历读取f dic = json.loads(line.strip()) # strip() 去换行符 ,字符串转字典 l.append(dic) f.close() print(l) # [{'k1': '111'}, {'k2': '222'}, {'k3': '333'}] # 要么load和dump一次性从文件写和一次性读,分次多个数据的的话用loads和dumps转化 # 正常情况下写一个文件,很少格式化写
# json模块dumps的使用
import json
data = {'username':['李华','二愣子'],'sex':'male','age':16} # 嵌套的列表
json_dic2 = json.dumps(data, sort_keys=True, indent=4, separators=(',',':'), ensure_ascii=False)
# indent=4 设置字符串前面的缩进,sort_keys=True 是不是按照key排序
# ensure_ascii=False :如果不想写如中文变成bytes类型需要dump的时候加个参数ensure_ascii=False
# separators=(',',':') 标识出字典当中的一些分隔符 ,和: ,根据这个进行格式化的 ,dumps把字典转化成json类型的字符串,json就是字符串类型的数据
print(json_dic2)
2:pickle:所有python中的数据类型都可以使用pickle转化成字符串形式 pickle也是四个方法loads,dumps,load,dump pickle序列化的内容只有python能理解 且部分反序列化依赖python代码,别的都不认识 使用场景:玩游戏,退出,下次再登录还能保持原状,游戏退出消失在内存, 游戏状态存储在本地或者服务器上,整个把角色的所有数据pickle了 存储在数据库或者文件,什么时候上线读取翻译成角色状态 pickle----使用 玩游戏需要装一个客户端,客户端代码固定的,放进去的东西能读出来 pickle ----游戏人物相关的 # pickle也是四个方法:loads,dumps,load,dump 用法和json一样,pickle能序列化任何数据类型 # pickle和文件打交道,文件的所有形式都要加b,pickle还支持分次load,json不支持 # 1:pickle.dumps(dic) 返回的是个bytes数据类型,
然后把这个bytes类型数据loads过来就变正常了,所以pickle序列号之后看不出有什么内容的 # pickle.dumps返回的是bytes数据类型,可以正常的loads和dumps import pickle dic = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'} str_dic = pickle.dumps(dic) print(str_dic) # 返回的是一串bytes数据,是一串二进制内容 dic2 = pickle.loads(str_dic) print(dic2) # 返回一个字典 # 2:pickle操作文件的dump和load需要使用rb方式和wb打开文件
因为数据使用pickle序列化之后是个bytes类型,bytes类型(二进制数据)写到文件里进去需要使用wb # pickle什么对象都能dump序列化(字典,类对象,元组等都可以序列化),序列化后返回的都是bytes数据,python就能读懂 # pickle能够分步dump和分步load --对于json来说不行了 import pickle import time struct_time1 = time.localtime(1000000000) struct_time2 = time.localtime(2000000000) print(struct_time1) print(struct_time2) f = open('pickle_file', 'wb') pickle.dump(struct_time1, f) # 把struct_time1和2 dump写入f这个文件里取 pickle.dump(struct_time2, f) f.close() f = open('pickle_file', 'rb') struct_time1 = pickle.load(f) struct_time2 = pickle.load(f) print(struct_time1.tm_year) print(struct_time2.tm_year) f.close()
# 从f文件load读取数据后拿到的struct_time1就是一个结构化的时间类型,和写进去的时候一样
# 本质上类似把struct_time1使用pickle.dumps转化成序列化数据(也就是bytes数据,一堆二进制数据)
# 然后把这个二进制数据write写入文件f:这就是pickle.dump
pickle进行序列化和反序列化需要相同的环境(都需要pickle模块和python环境)
3:shelve模块:操作简单 序列化句柄 使用序列化句柄直接操作,很方便 shelve只有一个open 函数,open打开一个文件,获取文件句柄,文件句柄可以把复杂的数据类型往里写 把数据类型直接写到文件句柄里 import shelve f = shelve.open('shelve_file') f['key'] = {'int': 10, 'float': 9.5, 'string': 'Sample data'} # 直接对文件句柄f操作,就可以存入数据 f.close()
import shelve f1 = shelve.open('shelve_file') existing = f1['key'] # 取出数据的时候也只需要直接操作文件句柄f1,用key获取即可,但是如果key不存在会报错 f1.close() print(existing)
# 打印:{'int': 10, 'float': 9.5, 'string': 'Sample data'}
# shelve模块不支持多个应用同一时间往同一个DB(文件)进行写操作,
# 所以当我们的应用如果只进行读操作,我们可以让shelve通过只读方式打开DB # DB:数据库,文件 import shelve f = shelve.open('shelve_file', flag='r') # r只读模式打开文件shelve_file,只读打开也可以修改文件里的值 existing = f['key'] # 拿到key对应的值 print(existing) f['key'] = 10 # 修改文件key对应的值(py3.9版本只读打开文件还去修改的话会报错,这里会报错) f.close() f = shelve.open('shelve_file') existing1 = f['key'] # 读文件的操作 print(existing1)
# 由于shelve在默认情况下是不会记录待持久化对象的任何修改的,所以我们在shelve.open()时候需要修改默认参数 # 否则对象的修改不会保存 加了:writeback=True写了才会保存 # 不用writeback修改不了,但是可以f['key'] = {'int': 10, 'float': 9.5, 'string': 'Sample data'}写入数据进去
# 没有writeback参数,这样f1['key']['new_value'] = 'this was not here before' 去修改值无法修改,但是可以上面那样重新写入全部的数据
# shelve里的任何修改影响文件需要writeback,正常不需要老是修改一个文件,修改文件很麻烦比如python(创建新的文件删除旧的然后新的文件改名成旧的来修改文件)
import shelve f1 = shelve.open('shelve_file') # 打开文件 print(f1['key']) # 打印key的值 f1['key']['new_value'] = 'this was not here before' # 对值进行修改之后close了 f1.close() f2 = shelve.open('shelve_file', writeback=True) print(f2['key']) f2['key']['new_value'] = 'this was not here before' f2.close()
writeback方式有代点也有缺点。代点是減少了我們出錯的概率,并且止対象的持久化対用戸更加的透明了;
但这种方式并不是所有的情况下都需要,首先,使用writeback以后, shelf在open()的吋候会増加額外的内存消耗,
并且当DB(文件)在close的吋候会將緩存中的毎一个対象都写入到DB ,文也会带来额外的等待时间。
因カsheve没有か法知道緩存中耶些対象修改了, 啣些対象没有修改,因此所有的対象都会被写入、
close的时候把整个文件修改都感知到,相当于把当前f1这个句柄又写了一遍,
修改文件很麻烦比如python(创建新的文件删除旧的然后新的文件改名成旧的来修改文件)
shelve这里的修改也相把f1这个文件句柄重新写了一遍来修改
pickle模块的实际使用: # 使用pickle模块往f文件中写入多个实例对象然后读取出来 class Teacher(): def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def p_name(self): print(self.name) def p_age(self): print(self.age) def p_sex(self): print(self.sex) import pickle Mrwang = Teacher('王老师', 30, '女') with open('pickle_file', 'ab') as f: pickle.dump(Mrwang, f) Mrwu = Teacher('吴老师', 35, '女') with open('pickle_file', 'ab') as f: pickle.dump(Mrwu, f) 2:读取pickle里的文件的代码 with open('pickle_file', 'rb') as f: alist = [] while 1: try: alist.append(pickle.load(f)) except EOFError: # 捕获到错误就停止循环 break for i in alist: print(i.name) 3:读取pickle里的文件返回一个迭代器的代码 def load(): with open('pickle_file', 'rb') as f: while 1: try: yield pickle.load(f) except EOFError: # 捕获到错误就停止循环 break generator = load() print(generator) for i in generator: print(i) # 使用pickle模块能在文件写如任何数据,对象也能写入 # 创建一个讲师对象,把对象存储在讲师对应的文件里 pickle序列化一个对象进去
22:模块
import 文件名(不加.py) 模块的名字必须符合命名规则:
1:见名知意
2:驼峰命名法则
3:不和python内置的关键字冲突 import demo 这句话发生了什么,import这个模块。文件就被执行了 import demo import demo 一个模块多次import,不会报错,也不会多次导入,只导入一次
demo.py文件里的代码 __all__ = ['money', 'read', 'read2'] print('in demp.py') money = 100 def read(): print('in read', money) def read2(): print('in read2') 1:import demo
import导入一个模块,需要模块名.函数名才能调用函数 比如:demo.read() import demo这里发生的事情: 1:从sys.path路径里找到这个模块,然后加载这个模块里面的东西(读取到导入模块的专属命名空间里了) 文件里的所有名字都是要通过 demo模块名字取调用, 2:创建这个模块的命名空间 3:把文件中的名字都放到命名空间里 demo.read() # 调用函数 demo.money # 调用变量 4:各个模块之间的命名空间完全隔离,a模块有个 变量money=100 b模块也有个变量 money=200 a模块调用b模块的函数调用了money变量,使用的还是b模块的money=200 import demo def read(): print('my read func') money = 200 demo.read()
# demo.read()调用的是demo模块里面的read()函数,read()函数使用了demo模块的money,所以打印:in read 100
2:为什么一个模块不会被重复导入 sys.modules:
导入一个模块的时候,先到sys.modules看一下导入的模块在不在这个字典里面,如果在了就不再重复导入了 ,如果不在再去从sys.path路径寻找 import导入一个模块的时候,先从sys.modules查看是否存在这个模块,查看是否已经被导入 如果被导入之后不会再重复导入,如果没有被导入就依据sys.path路径去寻找模块 找到了就导入,导入之前得先创建属于这个模块的命名空间,然后执行文件把模块的名字放在命名空间里 这才是完整得模块导入流程
所有一个模块被多次导入不会重复是因为sys.modules这一步都没过,有了不会重复导入 import sys, demo print(sys.modules.keys()) # 打印:dict_keys(['sys', 'builtins', # '_frozen_importlib', '_imp', '_thread', # '_warnings', '_weakref', '_io', 'marshal', # 'nt', 'winreg', '_frozen_importlib_external', # 'time', 'zipimport', '_codecs', 'codecs', # 'encodings.aliases', 'encodings', 'encodings.utf_8', # '_signal', 'encodings.latin_1', '_abc', 'abc', # 'io', '__main__', '_stat', 'stat', '_collections_abc', # 'genericpath', 'ntpath', 'os.path', 'os', '_sitebuiltins', # '_locale', '_bootlocale', '_codecs_cn', '_multibytecodec', # 'encodings.gbk', 'types', 'importlib._bootstrap', 'importlib. # _bootstrap_external', 'warnings', 'importlib', 'importlib.machinery', # '_heapq', 'heapq', 'itertools', 'keyword', '_operator', 'operator', # 'reprlib', '_collections', 'collections', 'collections.abc', '_functools', # 'functools', 'contextlib', 'enum', '_sre', 'sre_constants', 'sre_parse', # 'sre_compile', 'copyreg', 're', 'typing.io', 'typing.re', 'typing', # 'importlib.abc', 'importlib.util', 'google', 'pywin32_bootstrap', 'zc', # 'zope', 'sitecustomize', 'site', 'demo'])
3:import as 别名 :给一个模块起别名 1:模块名字太长,使用 as 起别名 2:数据库 oracle和mysql --类似excel表格存入数据 操作不同数据库需要导入不同的模块 import oracle import mysql 假设想可以使用MySQL或者oracle都行,oracle数据库oracle模块操作 mysql使用mysql数据库操作 1:连接数据库 2:登录认证 3:增删改查操作 4:关闭数据库 操作oracle使用oracle模块,操作mysql使用mysql的模块,两个数据库操作都是一样的, mysql.connet 和oracle.connect if 数据库=='oracle' import oracle as db elif 数据库=='mysql' import mysql as db 这时候后面的操作都是一样的了,不管是Oracle和MySQL都是同一套逻辑来操作,避免代码重复 import time as t print(t.time()) # 1613700801.2363229 获取当前时间戳 time.time # 重命名之后只有重命名的名字t可以使用,之前的time模块名字不能使用了,所以这里报错
4:一次性导入多个模块: import time,sys,os 不推荐这么写,修改不方便,降低代码可读性 1:导入模块一般在文件最上面,一开始全部导入使用写好的模块,别人一打开就知道你用了哪些模块 不用从头到尾查看 2:模块写的顺序 第一顺序先导入内置的模块:re,time,os,sys 第二顺序再导入扩展的,pip安装的,jiango 第三顺序最后导入我们自己自定义的模块 规范,编码规则,让使用模块的人一眼就能看到 import time, sys, os
5:from xxx import xxx :单独从某个模块导入一个方法或者变量就使用from xxx import xxx 这样从模块导入方法或者类不需要 模块名.方法名 这样调用,直接使用方法名调用就行吧 但是from xxx import xxx 只是import了单个xxx函数或者变量,没有import其他,其他变量和函数使用不了 pycham认为项目根目录就是寻找模块的路径--这是pycham自带的功能 包的导入模块的路径是根据sys.path来寻找的 模块在sys.path里就能找到 from time import sleep sleep(3) from demo import read read() # 上面这样只是单纯的import了read这个单独的函数,没有import money,使用不了money这个变量 # 然而这里read函数内部调用了money却能够使用,想要导入read还要从头到尾去读取整个demo文件 # 读取的时候发生read函数的money变量在read函数里需要用到,所以还保存在导入模块的局部内存里,用的时候还是可以拿出来用
import sys print(sys.path) def read(): print('my read') read() # 本地定义了read函数,那么调用本地的read,本地优先,
# 这里本地定义了一个read后再也调用不到其他模块的read函数了 - --只能调用自己本地的了(优先调用)
6:使用import demo这种形式去导入一个模块会创建一个属于demo的局部命名空间,我们在本地这个文件里面可以随意的命名(可以和demo模块的名称冲突也没啥) # 都和demo里面文件和本地文件里面的任何一个名字都没有冲突--可以随意的命名 使用from demo import 变量名 的时候,这个变量名就是一个属于全局的名字了,属于当前文件全局的名字 # 这时候再写一个变量名和和导入的变量名一模一样的话那么就再也找不到之前导入进来的变量了--前面导入的被覆盖了 使用import demo这种形式导入虽然不会有名称冲突的问题,但是文件所有名字都被加载进了一个局部空间,文件很大很长 使用import demo会全部存储到内存里,这样搞不得行,占用内存 使用from demo import 变量名 每次只导入进来一个变量,节省内存 一般情况下import 变量名不会自己傻逼去起这个变量名,各有千秋
7:from支持导入多个名字:from demo import money,read from demo import money, read print(money) # 打印:100 直接使用demo空间的money变量 read() # 打印:in read 100,调用函数demo空间的read函数 money = 200 read() # 打印:in read 100,调用函数demo空间的read函数 # 这个地方的read函数虽然属于全局了,但是read函数里面拿到的money还是 从demo模块自己的空间去取,不从当前空间去取值 ---存储的是内存地址
相当于本地空间存储了一个read变量指向demo模块定义的read函数,调用的时候其实还是相当于demo模块的函数调用
read函数使用到的变量还是需要从demo局部空间里取
8:from demo import * * 代表导入demo模块全部的东西 相当于把demo模块的名字全部放在我自己模块的全局空间里了,直接使用就行了 但是重名会很麻烦 ----这个特别不安全 from demo import * print(money) # 打印:100 read() # 打印:in read 100 调用demo模块的read函数 read2() # 打印:in read2 调用demo模块的read2函数 import math # 不建议这样使用 from math import pi # 这样使用节省内存一些 print(pi) # 3.141592653589793 pi = 3 print(pi) # 3
9:被导入的模块demo模块设置了一个 __all__= ['money']后面必须是一个列表,
列表里面必须是已经存在名字的字符串,只有列表里写了的变量才能被import * 导入使用,
列表里没写的不能使用,会报错 __all__= 这个all只和 import * 这样导入相关联,
如果不是from demo import *,而是直接import demo的话,就不受__all__影响,正常使用demo模块的任何变量 from demo import * print(money) read() # 如果read没有在demo模块的__all__=[]这个后面的列表的话那么这里就会报错
模块知识总结: 0:模块不会被重复被导入:sys.modules:导入的模块在sys.modules这个模块检测一下 看有没有被导入过就知道了:这就是sys.modules的作用
1:从哪儿导入模块 ---sys.path 这个sys.path列表存放所有路径,导入模块的时候从这个列表当中的每一个路径去找,找到模块来导入 2:import 模块名 导入一个模块名 模块名.变量名 使用 这样导入的话导入模块的变量名和本地文件的变量名完全不冲突 3:import 模块名 as 重命名的模块名 提供代码兼容性的时候使用, 4:import 模块1,模块2 这样方式不推荐,更推荐分开import 5:from 模块名 import 变量名 这样写直接使用变量名就可以完成操作了 如果本文件有相同的变量名会发生冲突 6:from 模块名 import 变量名 as 重命名的变量名 7:from 模块名 import 变量名1,变量名2 ---支持导入多个变量名 8:from 模块名 import * ----将demo模块所有变量名都放到内存中 如果本文件有相同的变量名会发生冲突 还不知道模块里有什么名字---少用 9:from 模块名 import * 和 __all__是一对 如果没有__all__这个变量就会导入所有的名字 如果有__all__只导入all列表中的名字:__all__=['变量'] all只能约束from 模块名 import * 10:所有的模块导入都应该尽量往上写,且顺序尽量遵循 1:内存模块 2:扩展模块 3:自定义模块 11:import time # ctrl +鼠标左键查看模块源码 12:包:包就是一大堆模块的集合 创建一个文件夹里面放的每个都是模块---形成一个包了 13:if __name__ == '__main__': 调试程序
10:__name__ 在模块中有一个变量 __name__, 当我们直接执行这个模块时候这个 __name__ 的值等于 __main__ 当我们执行其他模块在其他模块中引用这个模块的时候,这个模块中的 __name__等于模块名字,利用这点调试代码 import demo # 这里就会执行demo模块里面的一些代码,import time等模块都不会打印多余的东西 # 因为demo模块有个print # 希望demo模块运行的代码print不影响当前模块的导入使用:if __name__ == '__main__': print(__name__) # __main__ # __name__ 在当前模块__name__等于__main__ # 在其他模块被调用__name__ 等于被调用的模块名 demo