Python入门之函数
一 函数知识体系
什么是函数?
为什么要用函数?
函数的分类:内置函数与自定义函数
如何自定义函数
语法
定义有参数函数,及有参函数的应用场景
定义无参数函数,及无参函数的应用场景
定义空函数,及空函数的应用场景
调用函数
如何调用函数
函数的返回值
函数参数的应用:形参和实参,位置参数,关键字参数,默认参数,*args,**kwargs
高阶函数(函数对象)
函数嵌套
作用域与名称空间
装饰器
迭代器与生成器及协程函数
三元运算,列表解析、生成器表达式
函数的递归调用
内置函数
面向过程编程与函数式编程
二 函数基础
2.1 引子
2.1.1 为何要用函数之不用函数的问题
#1、代码的组织结构不清晰,可读性差 #2、遇到重复的功能只能重复编写实现代码,代码冗余 #3、功能需要扩展时,需要找出所有实现该功能的地方修改之,无法统一管理且维护难度极大
2.1.2 函数是什么
针对二中的问题,想象生活中的例子,修理工需要实现准备好工具箱里面放好锤子,扳手,钳子等工具,然后遇到锤钉子的场景,拿来锤子用就可以,而无需临时
再制造一把锤子。
修理工===>程序员
具备某一功能的工具===>函数
要想使用工具,需要事先准备好,然后拿来就用且可以重复使用
要想用函数,需要先定义,再使用
2.1.3 函数分类
#1、内置函数
为了方便我们的开发,针对一些简单的功能,python解释器已经为我们定义好了的函数即内置函数。对于内置函数,我们可以拿来就用而无需事先定义,
如len(),sum(),max()
ps:我们将会在最后详细介绍常用的内置函数。
#2、自定义函数
很明显内置函数所能提供的功能是有限的,这就需要我们自己根据需求,事先定制好我们自己的函数来实现某种功能,以后,在遇到应用场景时,调用自定义
的函数即可。例如
2.2 定义函数
2.2.1 如何自定义函数?
#语法
def 函数名(参数1,参数2,参数3,...):
'''注释'''
函数体
return 返回的值
#函数名要能反映其意义
1 def auth(user:str,password:str)->int: 2 ''' 3 auth function 4 :param user: 用户名 5 :param password: 密码 6 :return: 认证结果 7 ''' 8 if user == 'egon' and password == '123': 9 return 1 10 # print(auth.__annotations__) #{'user': <class 'str'>, 'password': <class 'str'>, 'return': <class 'int'>} 11 12 user=input('用户名>>: ').strip() 13 pwd=input('密码>>: ').strip() 14 res=auth(user,pwd) 15 print(res)
2.2.2 函数使用的原则:先定义,再调用
1 函数即“变量”,“变量”必须先定义后引用。未定义而直接引用函数,就相当于在引用一个不存在的变量名 2 #测试一 3 def foo(): 4 print('from foo') 5 bar() 6 foo() #报错 7 8 #测试二 9 def bar(): 10 print('from bar') 11 def foo(): 12 print('from foo') 13 bar() 14 foo() #正常 15 16 #测试三 17 def foo(): 18 print('from foo') 19 bar() 20 21 def bar(): 22 print('from bar') 23 foo() #会报错吗? 24 25 26 #结论:函数的使用,必须遵循原则:先定义,后调用 27 #我们在使用函数时,一定要明确地区分定义阶段和调用阶段 28 29 #定义阶段 30 def foo(): 31 print('from foo') 32 bar() 33 def bar(): 34 print('from bar') 35 #调用阶段 36 foo()
2.2.3 函数在定义阶段都干了哪些事?
#只检测语法,不执行代码
也就说,语法错误在函数定义阶段就会检测出来,而代码的逻辑错误只有在执行时才会知道
2.2.4 定义函数的三种形式
#1、无参:应用场景仅仅只是执行一些操作,比如与用户交互,打印
#2、有参:需要根据外部传进来的参数,才能执行相应的逻辑,比如统计长度,求最大值最小值
#3、空函数:设计代码结构
1 #定义阶段 2 def tell_tag(tag,n): #有参数 3 print(tag*n) 4 5 def tell_msg(): #无参数 6 print('hello world') 7 8 #调用阶段 9 tell_tag('*',12) 10 tell_msg() 11 tell_tag('*',12) 12 13 ''' 14 ************ 15 hello world 16 ************ 17 ''' 18 19 #结论: 20 #1、定义时无参,意味着调用时也无需传入参数 21 #2、定义时有参,意味着调用时则必须传入参数
1 def auth(user,password): 2 ''' 3 auth function 4 :param user: 用户名 5 :param password: 密码 6 :return: 认证结果 7 ''' 8 pass 9 10 def get(filename): 11 ''' 12 :param filename: 13 :return: 14 ''' 15 pass 16 17 def put(filename): 18 ''' 19 :param filename: 20 :return: 21 ''' 22 def ls(dirname): 23 ''' 24 :param dirname: 25 :return: 26 ''' 27 pass 28 29 #程序的体系结构立见
2.3 调用函数
2.3.1 调用函数
函数的调用:函数名加括号
1 先找到名字
2 根据名字调用代码
2.3.2 函数返回值
无return->None
return 1个值->返回1个值
return 逗号分隔多个值->元组
什么时候该有返回值?
调用函数,经过一系列的操作,最后要拿到一个明确的结果,则必须要有返回值
通常有参函数需要有返回值,输入参数,经过计算,得到一个最终的结果
什么时候不需要有返回值?
调用函数,仅仅只是执行一系列的操作,最后不需要得到什么结果,则无需有返回值
通常无参函数不需要有返回值
2.3.3 函数调用的三种形式
1 语句形式:foo()
2 表达式形式:3*len('hello')
3 当中另外一个函数的参数:range(len('hello'))
2.4 函数的参数
2.4.1 形参与实参
#形参即变量名,实参即变量值,函数调用时,将值绑定到变量名上,函数调用结束,解除绑定
2.4.2 具体应用
1 #1、位置参数:按照从左到右的顺序定义的参数 2 位置形参:必选参数 3 位置实参:按照位置给形参传值 4 5 #2、关键字参数:按照key=value的形式定义的实参 6 无需按照位置为形参传值 7 注意的问题: 8 1. 关键字实参必须在位置实参右面 9 2. 对同一个形参不能重复传值 10 11 #3、默认参数:形参在定义时就已经为其赋值 12 可以传值也可以不传值,经常需要变得参数定义成位置形参,变化较小的参数定义成默认参数(形参) 13 注意的问题: 14 1. 只在定义时赋值一次 15 2. 默认参数的定义应该在位置形参右面 16 3. 默认参数通常应该定义成不可变类型 17 18 19 #4、可变长参数: 20 可变长指的是实参值的个数不固定 21 而实参有按位置和按关键字两种形式定义,针对这两种形式的可变长,形参对应有两种解决方案来完整地存放它们,分别是*args,**kwargs 22 23 ===========*args=========== 24 def foo(x,y,*args): 25 print(x,y) 26 print(args) 27 foo(1,2,3,4,5) 28 29 def foo(x,y,*args): 30 print(x,y) 31 print(args) 32 foo(1,2,*[3,4,5]) 33 34 35 def foo(x,y,z): 36 print(x,y,z) 37 foo(*[1,2,3]) 38 39 ===========**kwargs=========== 40 def foo(x,y,**kwargs): 41 print(x,y) 42 print(kwargs) 43 foo(1,y=2,a=1,b=2,c=3) 44 45 def foo(x,y,**kwargs): 46 print(x,y) 47 print(kwargs) 48 foo(1,y=2,**{'a':1,'b':2,'c':3}) 49 50 51 def foo(x,y,z): 52 print(x,y,z) 53 foo(**{'z':1,'x':2,'y':3}) 54 55 ===========*args+**kwargs=========== 56 57 def foo(x,y): 58 print(x,y) 59 60 def wrapper(*args,**kwargs): 61 print('====>') 62 foo(*args,**kwargs) 63 64 #5、命名关键字参数:*后定义的参数,必须被传值(有默认值的除外),且必须按照关键字实参的形式传递 65 可以保证,传入的参数中一定包含某些关键字 66 def foo(x,y,*args,a=1,b,**kwargs): 67 print(x,y) 68 print(args) 69 print(a) 70 print(b) 71 print(kwargs) 72 73 foo(1,2,3,4,5,b=3,c=4,d=5) 74 结果: 75 2 76 (3, 4, 5) 77 3 78 {'c': 4, 'd': 5}
2.5 练习题
1、写函数,,用户传入修改的文件名,与要修改的内容,执行函数,完成批了修改操作
2、写函数,计算传入字符串中【数字】、【字母】、【空格] 以及 【其他】的个数
3、写函数,判断用户传入的对象(字符串、列表、元组)长度是否大于5。
4、写函数,检查传入列表的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者。
5、写函数,检查获取传入列表或元组对象的所有奇数位索引对应的元素,并将其作为新列表返回给调用者。
6、写函数,检查字典的每一个value的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者。
dic = {"k1": "v1v1", "k2": [11,22,33,44]}
PS:字典中的value只能是字符串或列表
1 #题目一 2 def modify_file(filename,old,new): 3 import os 4 with open(filename,'r',encoding='utf-8') as read_f,\ 5 open('.bak.swap','w',encoding='utf-8') as write_f: 6 for line in read_f: 7 if old in line: 8 line=line.replace(old,new) 9 write_f.write(line) 10 os.remove(filename) 11 os.rename('.bak.swap',filename) 12 13 modify_file('/Users/jieli/PycharmProjects/爬虫/a.txt','alex','SB') 14 15 #题目二 16 def check_str(msg): 17 res={ 18 'num':0, 19 'string':0, 20 'space':0, 21 'other':0, 22 } 23 for s in msg: 24 if s.isdigit(): 25 res['num']+=1 26 elif s.isalpha(): 27 res['string']+=1 28 elif s.isspace(): 29 res['space']+=1 30 else: 31 res['other']+=1 32 return res 33 34 res=check_str('hello name:aSB passowrd:alex3714') 35 print(res) 36 37 38 #题目三:略 39 40 #题目四 41 def func1(seq): 42 if len(seq) > 2: 43 seq=seq[0:2] 44 return seq 45 print(func1([1,2,3,4])) 46 47 48 #题目五 49 def func2(seq): 50 return seq[::2] 51 print(func2([1,2,3,4,5,6,7])) 52 53 54 #题目六 55 def func3(dic): 56 d={} 57 for k,v in dic.items(): 58 if len(v) > 2: 59 d[k]=v[0:2] 60 return d 61 print(func3({'k1':'abcdef','k2':[1,2,3,4],'k3':('a','b','c')}))
三 函数对象、函数嵌套、名称空间与作用域、装饰器
3.1 函数对象
3.1.1 函数是第一类对象,即函数可以当作数据传递
#1 可以被引用 #2 可以当作参数传递 #3 返回值可以是函数 #3 可以当作容器类型的元素
3.1.2 利用该特性,优雅的取代多分支的if
1 def foo(): 2 print('foo') 3 4 def bar(): 5 print('bar') 6 7 dic={ 8 'foo':foo, 9 'bar':bar, 10 } 11 while True: 12 choice=input('>>: ').strip() 13 if choice in dic: 14 dic[choice]()
3.2 函数嵌套
3.2.1 函数的嵌套调用
1 def max(x,y): 2 return x if x > y else y 3 4 def max4(a,b,c,d): 5 res1=max(a,b) 6 res2=max(res1,c) 7 res3=max(res2,d) 8 return res3 9 print(max4(1,2,3,4))
3.2.2 函数的嵌套定义
1 def f1(): 2 def f2(): 3 def f3(): 4 print('from f3') 5 f3() 6 f2() 7 8 f1() 9 f3() #报错,为何?请看下一小节
3.3 名称空间与作用域
3.3.1 什么是名称空间?
#名称空间:存放名字的地方,三种名称空间,(之前遗留的问题x=1,1存放于内存中,那名字x存放在哪里呢?名称空间正是存放名字x与1绑定关系的地方)
3.3.2 名称空间的加载顺序
python test.py #1、python解释器先启动,因而首先加载的是:内置名称空间 #2、执行test.py文件,然后以文件为基础,加载全局名称空间 #3、在执行文件的过程中如果调用函数,则临时产生局部名称空间
3.3.3 名字的查找顺序
1 局部名称空间--->全局名称空间--->内置名称空间 2 3 #需要注意的是:在全局无法查看局部的,在局部可以查看全局的,如下示例 4 5 # max=1 6 def f1(): 7 # max=2 8 def f2(): 9 # max=3 10 print(max) 11 f2() 12 f1() 13 print(max)
3.3.4 作用域
1 #1、作用域即范围 2 - 全局范围(内置名称空间与全局名称空间属于该范围):全局存活,全局有效 3 - 局部范围(局部名称空间属于该范围):临时存活,局部有效 4 #2、作用域关系是在函数定义阶段就已经固定的,与函数的调用位置无关,如下 5 x=1 6 def f1(): 7 def f2(): 8 print(x) 9 return f2 10 x=100 11 def f3(func): 12 x=2 13 func() 14 x=10000 15 f3(f1()) 16 17 #3、查看作用域:globals(),locals() 18 19 20 LEGB 代表名字查找顺序: locals -> enclosing function -> globals -> __builtins__ 21 locals 是函数内的名字空间,包括局部变量和形参 22 enclosing 外部嵌套函数的名字空间(闭包中常见) 23 globals 全局变量,函数定义所在模块的名字空间 24 builtins 内置模块的名字空间
3.3.5 global与nonlocal关键字
3.4 闭包函数
3.4.1 什么是闭包?
#内部函数包含对外部作用域而非全局作用域的引用 #提示:之前我们都是通过参数将外部的值传给函数,闭包提供了另外一种思路,包起来喽,包起呦,包起来哇 def counter(): n=0 def incr(): nonlocal n x=n n+=1 return x return incr c=counter() print(c()) print(c()) print(c()) print(c.__closure__[0].cell_contents) #查看闭包的元素
3.4.2 闭包的意义与应用
1 #闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域 2 #应用领域:延迟计算(原来我们是传参,现在我们是包起来) 3 from urllib.request import urlopen 4 5 def index(url): 6 def get(): 7 return urlopen(url).read() 8 return get 9 10 baidu=index('http://www.baidu.com') 11 print(baidu().decode('utf-8'))
3.5 装饰器
装饰器就是闭包函数的一种应用场景
3.5.1 为何要用装饰器
#开放封闭原则:对修改封闭,对扩展开放
3.5.2 什么是装饰器
装饰器他人的器具,本身可以是任意可调用对象,被装饰者也可以是任意可调用对象。 强调装饰器的原则:1 不修改被装饰对象的源代码 2 不修改被装饰对象的调用方式 装饰器的目标:在遵循1和2的前提下,为被装饰对象添加上新功能
3.5.3 装饰器的使用
1 import time 2 def timmer(func): 3 def wrapper(*args,**kwargs): 4 start_time=time.time() 5 res=func(*args,**kwargs) 6 stop_time=time.time() 7 print('run time is %s' %(stop_time-start_time)) 8 return res 9 return wrapper 10 11 @timmer 12 def foo(): 13 time.sleep(3) 14 print('from foo') 15 foo()
1 def auth(driver='file'): 2 def auth2(func): 3 def wrapper(*args,**kwargs): 4 name=input("user: ") 5 pwd=input("pwd: ") 6 7 if driver == 'file': 8 if name == 'egon' and pwd == '123': 9 print('login successful') 10 res=func(*args,**kwargs) 11 return res 12 elif driver == 'ldap': 13 print('ldap') 14 return wrapper 15 return auth2 16 17 @auth(driver='file') 18 def foo(name): 19 print(name) 20 21 foo('egon')
3.5.4 装饰器语法
被装饰函数的正上方,单独一行 @deco1 @deco2 @deco3 def foo(): pass foo=deco1(deco2(deco3(foo)))
3.5.5 装饰器补充:wraps
1 from functools import wraps 2 3 def deco(func): 4 @wraps(func) #加在最内层函数正上方 5 def wrapper(*args,**kwargs): 6 return func(*args,**kwargs) 7 return wrapper 8 9 @deco 10 def index(): 11 '''哈哈哈哈''' 12 print('from index') 13 14 print(index.__doc__)
3.6 练习题
一:编写函数,(函数执行的时间是随机的)
二:编写装饰器,为函数加上统计时间的功能
三:编写装饰器,为函数加上认证的功能
四:编写装饰器,为多个函数加上认证的功能(用户的账号密码来源于文件),要求登录成功一次,后续的函数都无需再输入用户名和密码
注意:从文件中读出字符串形式的字典,可以用eval('{"name":"egon","password":"123"}')转成字典格式
五:编写装饰器,为多个函数加上认证功能,要求登录成功一次,在超时时间内无需重复登录,超过了超时时间,则必须重新登录
六:编写下载网页内容的函数,要求功能是:用户传入一个url,函数返回下载页面的结果
七:为题目五编写装饰器,实现缓存网页内容的功能:
具体:实现下载的页面存放于文件中,如果文件内有值(文件大小不为0),就优先从文件中读取网页内容,否则,就去下载,然后存到文件中
扩展功能:用户可以选择缓存介质/缓存引擎,针对不同的url,缓存到不同的文件中
八:还记得我们用函数对象的概念,制作一个函数字典的操作吗,来来来,我们有更高大上的做法,在文件开头声明一个空字典,然后在每个函数前加上装饰器,完成自动添加到字典的操作
九 编写日志装饰器,实现功能如:一旦函数f1执行,则将消息2017-07-21 11:12:11 f1 run写入到日志文件中,日志文件路径可以指定
注意:时间格式的获取
import time
time.strftime('%Y-%m-%d %X')
1 #题目一:略 2 #题目二:略 3 #题目三:略 4 #题目四: 5 db='db.txt' 6 login_status={'user':None,'status':False} 7 def auth(auth_type='file'): 8 def auth2(func): 9 def wrapper(*args,**kwargs): 10 if login_status['user'] and login_status['status']: 11 return func(*args,**kwargs) 12 if auth_type == 'file': 13 with open(db,encoding='utf-8') as f: 14 dic=eval(f.read()) 15 name=input('username: ').strip() 16 password=input('password: ').strip() 17 if name in dic and password == dic[name]: 18 login_status['user']=name 19 login_status['status']=True 20 res=func(*args,**kwargs) 21 return res 22 else: 23 print('username or password error') 24 elif auth_type == 'sql': 25 pass 26 else: 27 pass 28 return wrapper 29 return auth2 30 31 @auth() 32 def index(): 33 print('index') 34 35 @auth(auth_type='file') 36 def home(name): 37 print('welcome %s to home' %name) 38 39 40 # index() 41 # home('egon') 42 43 #题目五 44 import time,random 45 user={'user':None,'login_time':None,'timeout':0.000003,} 46 47 def timmer(func): 48 def wrapper(*args,**kwargs): 49 s1=time.time() 50 res=func(*args,**kwargs) 51 s2=time.time() 52 print('%s' %(s2-s1)) 53 return res 54 return wrapper 55 56 57 def auth(func): 58 def wrapper(*args,**kwargs): 59 if user['user']: 60 timeout=time.time()-user['login_time'] 61 if timeout < user['timeout']: 62 return func(*args,**kwargs) 63 name=input('name>>: ').strip() 64 password=input('password>>: ').strip() 65 if name == 'egon' and password == '123': 66 user['user']=name 67 user['login_time']=time.time() 68 res=func(*args,**kwargs) 69 return res 70 return wrapper 71 72 @auth 73 def index(): 74 time.sleep(random.randrange(3)) 75 print('welcome to index') 76 77 @auth 78 def home(name): 79 time.sleep(random.randrange(3)) 80 print('welcome %s to home ' %name) 81 82 index() 83 home('egon') 84 85 #题目六:略 86 #题目七:简单版本 87 import requests 88 import os 89 cache_file='cache.txt' 90 def make_cache(func): 91 def wrapper(*args,**kwargs): 92 if not os.path.exists(cache_file): 93 with open(cache_file,'w'):pass 94 95 if os.path.getsize(cache_file): 96 with open(cache_file,'r',encoding='utf-8') as f: 97 res=f.read() 98 else: 99 res=func(*args,**kwargs) 100 with open(cache_file,'w',encoding='utf-8') as f: 101 f.write(res) 102 return res 103 return wrapper 104 105 @make_cache 106 def get(url): 107 return requests.get(url).text 108 109 110 # res=get('https://www.python.org') 111 112 # print(res) 113 114 #题目七:扩展版本 115 import requests,os,hashlib 116 engine_settings={ 117 'file':{'dirname':'./db'}, 118 'mysql':{ 119 'host':'127.0.0.1', 120 'port':3306, 121 'user':'root', 122 'password':'123'}, 123 'redis':{ 124 'host':'127.0.0.1', 125 'port':6379, 126 'user':'root', 127 'password':'123'}, 128 } 129 130 def make_cache(engine='file'): 131 if engine not in engine_settings: 132 raise TypeError('egine not valid') 133 def deco(func): 134 def wrapper(url): 135 if engine == 'file': 136 m=hashlib.md5(url.encode('utf-8')) 137 cache_filename=m.hexdigest() 138 cache_filepath=r'%s/%s' %(engine_settings['file']['dirname'],cache_filename) 139 140 if os.path.exists(cache_filepath) and os.path.getsize(cache_filepath): 141 return open(cache_filepath,encoding='utf-8').read() 142 143 res=func(url) 144 with open(cache_filepath,'w',encoding='utf-8') as f: 145 f.write(res) 146 return res 147 elif engine == 'mysql': 148 pass 149 elif engine == 'redis': 150 pass 151 else: 152 pass 153 154 return wrapper 155 return deco 156 157 @make_cache(engine='file') 158 def get(url): 159 return requests.get(url).text 160 161 # print(get('https://www.python.org')) 162 print(get('https://www.baidu.com')) 163 164 165 #题目八 166 route_dic={} 167 168 def make_route(name): 169 def deco(func): 170 route_dic[name]=func 171 return deco 172 @make_route('select') 173 def func1(): 174 print('select') 175 176 @make_route('insert') 177 def func2(): 178 print('insert') 179 180 @make_route('update') 181 def func3(): 182 print('update') 183 184 @make_route('delete') 185 def func4(): 186 print('delete') 187 188 print(route_dic) 189 190 191 #题目九 192 import time 193 import os 194 195 def logger(logfile): 196 def deco(func): 197 if not os.path.exists(logfile): 198 with open(logfile,'w'):pass 199 200 def wrapper(*args,**kwargs): 201 res=func(*args,**kwargs) 202 with open(logfile,'a',encoding='utf-8') as f: 203 f.write('%s %s run\n' %(time.strftime('%Y-%m-%d %X'),func.__name__)) 204 return res 205 return wrapper 206 return deco 207 208 @logger(logfile='aaaaaaaaaaaaaaaaaaaaa.log') 209 def index(): 210 print('index') 211 212 index()
四 迭代器、生成器、面向过程编程
4.1 迭代器
4.1.1 迭代的概念
#迭代器即迭代的工具,那什么是迭代呢? #迭代是一个重复的过程,每次重复即一次迭代,并且每次迭代的结果都是下一次迭代的初始值 while True: #只是单纯地重复,因而不是迭代 print('===>') l=[1,2,3] count=0 while count < len(l): #迭代 print(l[count]) count+=1
4.1.2 为何要有迭代器?什么是可迭代对象?什么是迭代器对象?
#1、为何要有迭代器? 对于序列类型:字符串、列表、元组,我们可以使用索引的方式迭代取出其包含的元素。但对于字典、集合、文件等类型是没有索引的,若还想取出其内部包含的元素,则必须找出一种不依赖于索引的迭代方式,这就是迭代器 #2、什么是可迭代对象? 可迭代对象指的是内置有__iter__方法的对象,即obj.__iter__,如下 'hello'.__iter__ (1,2,3).__iter__ [1,2,3].__iter__ {'a':1}.__iter__ {'a','b'}.__iter__ open('a.txt').__iter__ #3、什么是迭代器对象? 可迭代对象执行obj.__iter__()得到的结果就是迭代器对象 而迭代器对象指的是即内置有__iter__又内置有__next__方法的对象 文件类型是迭代器对象 open('a.txt').__iter__() open('a.txt').__next__() #4、注意: 迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象
4.1.3 迭代器对象的使用
1 dic={'a':1,'b':2,'c':3} 2 iter_dic=dic.__iter__() #得到迭代器对象,迭代器对象即有__iter__又有__next__,但是:迭代器.__iter__()得到的仍然是迭代器本身 3 iter_dic.__iter__() is iter_dic #True 4 5 print(iter_dic.__next__()) #等同于next(iter_dic) 6 print(iter_dic.__next__()) #等同于next(iter_dic) 7 print(iter_dic.__next__()) #等同于next(iter_dic) 8 # print(iter_dic.__next__()) #抛出异常StopIteration,或者说结束标志 9 10 #有了迭代器,我们就可以不依赖索引迭代取值了 11 iter_dic=dic.__iter__() 12 while 1: 13 try: 14 k=next(iter_dic) 15 print(dic[k]) 16 except StopIteration: 17 break 18 19 #这么写太丑陋了,需要我们自己捕捉异常,控制next,python这么牛逼,能不能帮我解决呢?能,请看for循环
4.1.4 for循环
1 #基于for循环,我们可以完全不再依赖索引去取值了 2 dic={'a':1,'b':2,'c':3} 3 for k in dic: 4 print(dic[k]) 5 6 #for循环的工作原理 7 #1:执行in后对象的dic.__iter__()方法,得到一个迭代器对象iter_dic 8 #2: 执行next(iter_dic),将得到的值赋值给k,然后执行循环体代码 9 #3: 重复过程2,直到捕捉到异常StopIteration,结束循环
4.1.5 迭代器的优缺点
#优点: - 提供一种统一的、不依赖于索引的迭代方式 - 惰性计算,节省内存 #缺点: - 无法获取长度(只有在next完毕才知道到底有几个值) - 一次性的,只能往后走,不能往前退
4.2 生成器
4.2.1 什么是生成器
1 #只要函数内部包含有yield关键字,那么函数名()的到的结果就是生成器,并且不会执行函数内部代码 2 3 def func(): 4 print('====>first') 5 yield 1 6 print('====>second') 7 yield 2 8 print('====>third') 9 yield 3 10 print('====>end') 11 12 g=func() 13 print(g) #<generator object func at 0x0000000002184360>
4.2.2 生成器就是迭代器
1 g.__iter__ 2 g.__next__ 3 #2、所以生成器就是迭代器,因此可以这么取值 4 res=next(g) 5 print(res)
4.2.3 练习
1、自定义函数模拟range(1,7,2)
2、模拟管道,实现功能:tail -f access.log | grep '404'
1 #题目一: 2 def my_range(start,stop,step=1): 3 while start < stop: 4 yield start 5 start+=step 6 7 #执行函数得到生成器,本质就是迭代器 8 obj=my_range(1,7,2) #1 3 5 9 print(next(obj)) 10 print(next(obj)) 11 print(next(obj)) 12 print(next(obj)) #StopIteration 13 14 #应用于for循环 15 for i in my_range(1,7,2): 16 print(i) 17 18 #题目二 19 import time 20 def tail(filepath): 21 with open(filepath,'rb') as f: 22 f.seek(0,2) 23 while True: 24 line=f.readline() 25 if line: 26 yield line 27 else: 28 time.sleep(0.2) 29 30 def grep(pattern,lines): 31 for line in lines: 32 line=line.decode('utf-8') 33 if pattern in line: 34 yield line 35 36 for line in grep('404',tail('access.log')): 37 print(line,end='') 38 39 #测试 40 with open('access.log','a',encoding='utf-8') as f: 41 f.write('出错啦404\n')
4.2.4 协程函数
1 #yield关键字的另外一种使用形式:表达式形式的yield 2 def eater(name): 3 print('%s 准备开始吃饭啦' %name) 4 food_list=[] 5 while True: 6 food=yield food_list 7 print('%s 吃了 %s' % (name,food)) 8 food_list.append(food) 9 10 g=eater('egon') 11 g.send(None) #对于表达式形式的yield,在使用时,第一次必须传None,g.send(None)等同于next(g) 12 g.send('蒸羊羔') 13 g.send('蒸鹿茸') 14 g.send('蒸熊掌') 15 g.send('烧素鸭') 16 g.close() 17 g.send('烧素鹅') 18 g.send('烧鹿尾')
4.2.5 练习
1、编写装饰器,实现初始化协程函数的功能
2、实现功能:grep -rl 'python' /etc
1 #题目一: 2 def init(func): 3 def wrapper(*args,**kwargs): 4 g=func(*args,**kwargs) 5 next(g) 6 return g 7 return wrapper 8 @init 9 def eater(name): 10 print('%s 准备开始吃饭啦' %name) 11 food_list=[] 12 while True: 13 food=yield food_list 14 print('%s 吃了 %s' % (name,food)) 15 food_list.append(food) 16 17 g=eater('egon') 18 g.send('蒸羊羔') 19 20 #题目二: 21 #注意:target.send(...)在拿到target的返回值后才算执行结束 22 import os 23 def init(func): 24 def wrapper(*args,**kwargs): 25 g=func(*args,**kwargs) 26 next(g) 27 return g 28 return wrapper 29 30 @init 31 def search(target): 32 while True: 33 filepath=yield 34 g=os.walk(filepath) 35 for dirname,_,files in g: 36 for file in files: 37 abs_path=r'%s\%s' %(dirname,file) 38 target.send(abs_path) 39 @init 40 def opener(target): 41 while True: 42 abs_path=yield 43 with open(abs_path,'rb') as f: 44 target.send((f,abs_path)) 45 @init 46 def cat(target): 47 while True: 48 f,abs_path=yield 49 for line in f: 50 res=target.send((line,abs_path)) 51 if res: 52 break 53 @init 54 def grep(pattern,target): 55 tag=False 56 while True: 57 line,abs_path=yield tag 58 tag=False 59 if pattern.encode('utf-8') in line: 60 target.send(abs_path) 61 tag=True 62 @init 63 def printer(): 64 while True: 65 abs_path=yield 66 print(abs_path) 67 68 69 g=search(opener(cat(grep('你好',printer())))) 70 # g.send(r'E:\CMS\aaa\db') 71 g=search(opener(cat(grep('python',printer())))) 72 g.send(r'E:\CMS\aaa\db')
4.2.6 yield总结
#1、把函数做成迭代器 #2、对比return,可以返回多次值,可以挂起/保存函数的运行状态
4.3 面向过程编程
#1、首先强调:面向过程编程绝对不是用函数编程这么简单,面向过程是一种编程思路、思想,而编程思路是不依赖于具体的语言或语法的。言外之意是即使我们不依赖于函数,也可以基于面向过程的思想编写程序 #2、定义 面向过程的核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么 基于面向过程设计程序就好比在设计一条流水线,是一种机械式的思维方式 #3、优点:复杂的问题流程化,进而简单化 #4、缺点:可扩展性差,修改流水线的任意一个阶段,都会牵一发而动全身 #5、应用:扩展性要求不高的场景,典型案例如linux内核,git,httpd #6、举例 流水线1: 用户输入用户名、密码--->用户验证--->欢迎界面 流水线2: 用户输入sql--->sql解析--->执行功能
ps:函数的参数传入,是函数吃进去的食物,而函数return的返回值,是函数拉出来的结果,面向过程的思路就是,把程序的执行当做一串首尾相连的功能,该功能可以是函数的形式,然后一个函数吃,拉出的东西给另外一个函数吃,另外一个函数吃了再继续拉给下一个函数吃。。。
五 三元表达式、列表推导式、生成器表达式、递归、匿名函数、内置函数
5.1 三元表达式、列表推导式、生成器表达式
5.1.1 三元表达式
name=input('姓名>>: ') res='SB' if name == 'alex' else 'NB' print(res)
5.1.2 列表推导式
1 #1、示例 2 egg_list=[] 3 for i in range(10): 4 egg_list.append('鸡蛋%s' %i) 5 6 egg_list=['鸡蛋%s' %i for i in range(10)] 7 8 #2、语法 9 [expression for item1 in iterable1 if condition1 10 for item2 in iterable2 if condition2 11 ... 12 for itemN in iterableN if conditionN 13 ] 14 类似于 15 res=[] 16 for item1 in iterable1: 17 if condition1: 18 for item2 in iterable2: 19 if condition2 20 ... 21 for itemN in iterableN: 22 if conditionN: 23 res.append(expression) 24 25 #3、优点:方便,改变了编程习惯,可称之为声明式编程
5.1.3 生成器表达式
#1、把列表推导式的[]换成()就是生成器表达式 #2、示例:生一筐鸡蛋变成给你一只老母鸡,用的时候就下蛋,这也是生成器的特性 >>> chicken=('鸡蛋%s' %i for i in range(5)) >>> chicken <generator object <genexpr> at 0x10143f200> >>> next(chicken) '鸡蛋0' >>> list(chicken) #因chicken可迭代,因而可以转成列表 ['鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4',] #3、优点:省内存,一次只产生一个值在内存中
5.1.4 声明式编程练习题
1、将names=['egon','alex_sb','wupeiqi','yuanhao']中的名字全部变大写
2、将names=['egon','alex_sb','wupeiqi','yuanhao']中以sb结尾的名字过滤掉,然后保存剩下的名字长度
3、求文件a.txt中最长的行的长度(长度按字符个数算,需要使用max函数)
4、求文件a.txt中总共包含的字符个数?思考为何在第一次之后的n次sum求和得到的结果为0?(需要使用sum函数)
5、思考题
with open('a.txt') as f: g=(len(line) for line in f) print(sum(g)) #为何报错?
6、文件shopping.txt内容如下
mac,20000,3 lenovo,3000,10 tesla,1000000,10 chicken,200,1
求总共花了多少钱?
打印出所有商品的信息,格式为[{'name':'xxx','price':333,'count':3},...]
求单价大于10000的商品信息,格式同上
1 #题目一 2 names=['egon','alex_sb','wupeiqi','yuanhao'] 3 names=[name.upper() for name in names] 4 5 #题目二 6 names=['egon','alex_sb','wupeiqi','yuanhao'] 7 names=[len(name) for name in names if not name.endswith('sb')] 8 9 #题目三 10 with open('a.txt',encoding='utf-8') as f: 11 print(max(len(line) for line in f)) 12 13 #题目四 14 with open('a.txt', encoding='utf-8') as f: 15 print(sum(len(line) for line in f)) 16 print(sum(len(line) for line in f)) #求包换换行符在内的文件所有的字符数,为何得到的值为0? 17 print(sum(len(line) for line in f)) #求包换换行符在内的文件所有的字符数,为何得到的值为0? 18 19 #题目五(略) 20 21 #题目六:每次必须重新打开文件或seek到文件开头,因为迭代完一次就结束了 22 with open('a.txt',encoding='utf-8') as f: 23 info=[line.split() for line in f] 24 cost=sum(float(unit_price)*int(count) for _,unit_price,count in info) 25 print(cost) 26 27 28 with open('a.txt',encoding='utf-8') as f: 29 info=[{ 30 'name': line.split()[0], 31 'price': float(line.split()[1]), 32 'count': int(line.split()[2]), 33 } for line in f] 34 print(info) 35 36 37 with open('a.txt',encoding='utf-8') as f: 38 info=[{ 39 'name': line.split()[0], 40 'price': float(line.split()[1]), 41 'count': int(line.split()[2]), 42 } for line in f if float(line.split()[1]) > 10000] 43 print(info)
5.2 递归与二分法
5.2.1 递归调用的定义
#递归调用是函数嵌套调用的一种特殊形式,函数在调用时,直接或间接调用了自身,就是递归调用
5.2.2 递归分为两个阶段:递推,回溯
1 #图解。。。 2 # salary(5)=salary(4)+300 3 # salary(4)=salary(3)+300 4 # salary(3)=salary(2)+300 5 # salary(2)=salary(1)+300 6 # salary(1)=100 7 # 8 # salary(n)=salary(n-1)+300 n>1 9 # salary(1) =100 n=1 10 11 def salary(n): 12 if n == 1: 13 return 100 14 return salary(n-1)+300 15 16 print(salary(5))
5.2.3 python中的递归效率低且没有尾递归优化
#python中的递归 python中的递归效率低,需要在进入下一次递归时保留当前的状态,在其他语言中可以有解决方法:尾递归优化,即在函数的最后一步(而非最后一行)调用自己,
尾递归优化:http://egon09.blog.51cto.com/9161406/1842475 但是python又没有尾递归,且对递归层级做了限制 #总结递归的使用: 1. 必须有一个明确的结束条件 2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少 3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,
栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
5.2.4 可以修改递归最大深度
1 import sys 2 sys.getrecursionlimit() 3 sys.setrecursionlimit(2000) 4 n=1 5 def test(): 6 global n 7 print(n) 8 n+=1 9 test() 10 11 test() 12 13 虽然可以设置,但是因为不是尾递归,仍然要保存栈,内存大小一定,不可能无限递归
5.2.5 二分法
想从一个按照从小到大排列的数字列表中找到指定的数字,遍历的效率太低,用二分法(算法的一种,算法是解决问题的方法)可以极大低缩小问题规模
1 l=[1,2,10,30,33,99,101,200,301,402] #从小到大排列的数字列表 2 3 def search(num,l): 4 print(l) 5 if len(l) > 0: 6 mid=len(l)//2 7 if num > l[mid]: 8 #in the right 9 l=l[mid+1:] 10 elif num < l[mid]: 11 #in the left 12 l=l[:mid] 13 else: 14 print('find it') 15 return 16 search(num,l) 17 else: 18 #如果值不存在,则列表切为空 19 print('not exists') 20 return 21 search(100,l)
1 l=[1,2,10,30,33,99,101,200,301,402] 2 3 def search(num,l,start=0,stop=len(l)-1): 4 if start <= stop: 5 mid=start+(stop-start)//2 6 print('start:[%s] stop:[%s] mid:[%s] mid_val:[%s]' %(start,stop,mid,l[mid])) 7 if num > l[mid]: 8 start=mid+1 9 elif num < l[mid]: 10 stop=mid-1 11 else: 12 print('find it',mid) 13 return 14 search(num,l,start,stop) 15 else: #如果stop > start则意味着列表实际上已经全部切完,即切为空 16 print('not exists') 17 return 18 19 search(301,l)
5.3 匿名函数
5.3.1 什么是匿名函数?
1 匿名就是没有名字 2 def func(x,y,z=1): 3 return x+y+z 4 5 匿名 6 lambda x,y,z=1:x+y+z #与函数有相同的作用域,但是匿名意味着引用计数为0,使用一次就释放,除非让其有名字 7 func=lambda x,y,z=1:x+y+z 8 func(1,2,3) 9 #让其有名字就没有意义
5.3.2 有名字的函数与匿名函数的对比
#有名函数与匿名函数的对比 有名函数:循环使用,保存了名字,通过名字就可以重复引用函数功能 匿名函数:一次性使用,随时随时定义 应用:max,min,sorted,map,reduce,filter
5.4 内置函数
#注意:内置函数id()可以返回一个对象的身份,返回值为整数。这个整数通常对应与该对象在内存中的位置,但这与python的具体实现有关,不应该作为对身份的定义,即不够精准,最精准的还是以内存地址为准。
is运算符用于比较两个对象的身份,等号比较两个对象的值,内置函数type()则返回一个对象的类型 #更多内置函数:https://docs.python.org/3/library/functions.html?highlight=built#ascii

1 #字符串可以提供的参数 's' None 2 >>> format('some string','s') 3 'some string' 4 >>> format('some string') 5 'some string' 6 7 #整形数值可以提供的参数有 'b' 'c' 'd' 'o' 'x' 'X' 'n' None 8 >>> format(3,'b') #转换成二进制 9 '11' 10 >>> format(97,'c') #转换unicode成字符 11 'a' 12 >>> format(11,'d') #转换成10进制 13 '11' 14 >>> format(11,'o') #转换成8进制 15 '13' 16 >>> format(11,'x') #转换成16进制 小写字母表示 17 'b' 18 >>> format(11,'X') #转换成16进制 大写字母表示 19 'B' 20 >>> format(11,'n') #和d一样 21 '11' 22 >>> format(11) #默认和d一样 23 '11' 24 25 #浮点数可以提供的参数有 'e' 'E' 'f' 'F' 'g' 'G' 'n' '%' None 26 >>> format(314159267,'e') #科学计数法,默认保留6位小数 27 '3.141593e+08' 28 >>> format(314159267,'0.2e') #科学计数法,指定保留2位小数 29 '3.14e+08' 30 >>> format(314159267,'0.2E') #科学计数法,指定保留2位小数,采用大写E表示 31 '3.14E+08' 32 >>> format(314159267,'f') #小数点计数法,默认保留6位小数 33 '314159267.000000' 34 >>> format(3.14159267000,'f') #小数点计数法,默认保留6位小数 35 '3.141593' 36 >>> format(3.14159267000,'0.8f') #小数点计数法,指定保留8位小数 37 '3.14159267' 38 >>> format(3.14159267000,'0.10f') #小数点计数法,指定保留10位小数 39 '3.1415926700' 40 >>> format(3.14e+1000000,'F') #小数点计数法,无穷大转换成大小字母 41 'INF' 42 43 #g的格式化比较特殊,假设p为格式中指定的保留小数位数,先尝试采用科学计数法格式化,得到幂指数exp,如果-4<=exp<p,则采用小数计数法,并保留p-1-exp位小数,否则按小数计数法计数,并按p-1保留小数位数 44 >>> format(0.00003141566,'.1g') #p=1,exp=-5 ==》 -4<=exp<p不成立,按科学计数法计数,保留0位小数点 45 '3e-05' 46 >>> format(0.00003141566,'.2g') #p=1,exp=-5 ==》 -4<=exp<p不成立,按科学计数法计数,保留1位小数点 47 '3.1e-05' 48 >>> format(0.00003141566,'.3g') #p=1,exp=-5 ==》 -4<=exp<p不成立,按科学计数法计数,保留2位小数点 49 '3.14e-05' 50 >>> format(0.00003141566,'.3G') #p=1,exp=-5 ==》 -4<=exp<p不成立,按科学计数法计数,保留0位小数点,E使用大写 51 '3.14E-05' 52 >>> format(3.1415926777,'.1g') #p=1,exp=0 ==》 -4<=exp<p成立,按小数计数法计数,保留0位小数点 53 '3' 54 >>> format(3.1415926777,'.2g') #p=1,exp=0 ==》 -4<=exp<p成立,按小数计数法计数,保留1位小数点 55 '3.1' 56 >>> format(3.1415926777,'.3g') #p=1,exp=0 ==》 -4<=exp<p成立,按小数计数法计数,保留2位小数点 57 '3.14' 58 >>> format(0.00003141566,'.1n') #和g相同 59 '3e-05' 60 >>> format(0.00003141566,'.3n') #和g相同 61 '3.14e-05' 62 >>> format(0.00003141566) #和g相同 63 '3.141566e-05'
1 字典的运算:最小值,最大值,排序 2 salaries={ 3 'egon':3000, 4 'alex':100000000, 5 'wupeiqi':10000, 6 'yuanhao':2000 7 } 8 9 迭代字典,取得是key,因而比较的是key的最大和最小值 10 >>> max(salaries) 11 'yuanhao' 12 >>> min(salaries) 13 'alex' 14 15 可以取values,来比较 16 >>> max(salaries.values()) 17 >>> min(salaries.values()) 18 但通常我们都是想取出,工资最高的那个人名,即比较的是salaries的值,得到的是键 19 >>> max(salaries,key=lambda k:salary[k]) 20 'alex' 21 >>> min(salaries,key=lambda k:salary[k]) 22 'yuanhao' 23 24 25 26 也可以通过zip的方式实现 27 salaries_and_names=zip(salaries.values(),salaries.keys()) 28 29 先比较值,值相同则比较键 30 >>> max(salaries_and_names) 31 (100000000, 'alex') 32 33 34 salaries_and_names是迭代器,因而只能访问一次 35 >>> min(salaries_and_names) 36 Traceback (most recent call last): 37 File "<stdin>", line 1, in <module> 38 ValueError: min() arg is an empty sequence 39 40 41 42 sorted(iterable,key=None,reverse=False)
1 #1、语法 2 # eval(str,[,globasl[,locals]]) 3 # exec(str,[,globasl[,locals]]) 4 5 #2、区别 6 #示例一: 7 s='1+2+3' 8 print(eval(s)) #eval用来执行表达式,并返回表达式执行的结果 9 print(exec(s)) #exec用来执行语句,不会返回任何值 10 ''' 11 None 12 ''' 13 14 #示例二: 15 print(eval('1+2+x',{'x':3},{'x':30})) #返回33 16 print(exec('1+2+x',{'x':3},{'x':30})) #返回None 17 18 # print(eval('for i in range(10):print(i)')) #语法错误,eval不能执行表达式 19 print(exec('for i in range(10):print(i)'))
1 compile(str,filename,kind) 2 filename:用于追踪str来自于哪个文件,如果不想追踪就可以不定义 3 kind可以是:single代表一条语句,exec代表一组语句,eval代表一个表达式 4 s='for i in range(10):print(i)' 5 code=compile(s,'','exec') 6 exec(code) 7 8 9 s='1+2+3' 10 code=compile(s,'','eval') 11 eval(code)
5.5 阶段性练习
1、文件内容如下,标题为:姓名,性别,年纪,薪资
egon male 18 3000
alex male 38 30000
wupeiqi female 28 20000
yuanhao female 28 10000
要求:
从文件中取出每一条记录放入列表中,
列表的每个元素都是{'name':'egon','sex':'male','age':18,'salary':3000}的形式
2 根据1得到的列表,取出薪资最高的人的信息
3 根据1得到的列表,取出最年轻的人的信息
4 根据1得到的列表,将每个人的信息中的名字映射成首字母大写的形式
5 根据1得到的列表,过滤掉名字以a开头的人的信息
6 使用递归打印斐波那契数列(前两个数的和得到第三个数,如:0 1 1 2 3 4 7...)
7 一个嵌套很多层的列表,如l=[1,2,[3,[4,5,6,[7,8,[9,10,[11,12,13,[14,15]]]]]]],用递归取出所有的值
1 #1 2 with open('db.txt') as f: 3 items=(line.split() for line in f) 4 info=[{'name':name,'sex':sex,'age':age,'salary':salary} \ 5 for name,sex,age,salary in items] 6 7 print(info) 8 #2 9 print(max(info,key=lambda dic:dic['salary'])) 10 11 #3 12 print(min(info,key=lambda dic:dic['age'])) 13 14 # 4 15 info_new=map(lambda item:{'name':item['name'].capitalize(), 16 'sex':item['sex'], 17 'age':item['age'], 18 'salary':item['salary']},info) 19 20 print(list(info_new)) 21 22 #5 23 g=filter(lambda item:item['name'].startswith('a'),info) 24 print(list(g)) 25 26 #6 27 #非递归 28 def fib(n): 29 a,b=0,1 30 while a < n: 31 print(a,end=' ') 32 a,b=b,a+b 33 print() 34 35 fib(10) 36 #递归 37 def fib(a,b,stop): 38 if a > stop: 39 return 40 print(a,end=' ') 41 fib(b,a+b,stop) 42 43 fib(0,1,10) 44 45 46 #7 47 l=[1,2,[3,[4,5,6,[7,8,[9,10,[11,12,13,[14,15]]]]]]] 48 49 def get(seq): 50 for item in seq: 51 if type(item) is list: 52 get(item) 53 else: 54 print(item) 55 get(l)
六 作业
作业需求:
模拟实现一个ATM + 购物商城程序
- 额度 15000或自定义
- 实现购物商城,买东西加入 购物车,调用信用卡接口结账
- 可以提现,手续费5%
- 每月22号出账单,每月10号为还款日,过期未还,按欠款总额 万分之5 每日计息
- 支持多账户登录
- 支持账户间转账
- 记录每月日常消费流水
- 提供还款接口
- ATM记录操作日志
- 提供管理接口,包括添加账户、用户额度,冻结账户等。。。
- 用户认证用装饰器
简易流程图:


示例代码 https://github.com/triaquae/py3_training/tree/master/atm

浙公网安备 33010602011771号