一、函数基础
1、为什么要用函数
# 增强程序的组织结构,可读性高
# 减少代码冗余
# 扩展性强
2、什么是函数
# 函数时组织好的,可以重复使用的的,用来实现单一,或相关联功能的代码段
# 函数能提高应用的模块性,和代码的重复利用率
# 在程序中,函数必须遵守:先定义,再调用
3、函数的分类
# 内置函数
为了 方便程序开发,针对一些简单的功能,Python解释器已经定义好了的函数即内置函数,对于内置函数我们可以拿来就用而无需事先定义,如len() sum() max()
# 自定义函数
很明显内置函数所能提供的功能有限,这就需要我们自己根据需求,事先定义好我们自己的函数来实现某个功能,以后在遇到应用场景时,调用自定义函数即可
4、定义函数
# 函数代码块以def关键字开头,后接函数名和小括号
# 任何传入的参数和自身变量必须放在小括号里面,参数名称可以多个
# 函数的第一行需要写函数说明
# 函数内容以冒号起始,并且缩进
# return [表达式]结束函数,选择性的返回一个值给调用方,不带表达式的return ,返回值为None
5、有参函数、无参函数、空函数
# 有参函数
def userinfo(name,age,): # 定义时有参
'''
:param name: 姓名
:param age: 年龄
:return:
'''
print('姓名:%s 年龄:%s' %(name,age))
userinfo('fred','18') # 调用时必须传参
# 无参函数
def func(): # 定义时无参
'''
:return:
'''
print('this is No Reference function')
func() # 调用时不需要传参
# 空函数
def pay():
pass
# 空函数的作用:在项目立项阶段,使用空函数把此项目的大体功能干先列出来,后续只需要一个个实现小功能就可以
6、函数的返回值
return 是函数结束的标志,
函数内可以有多个return,但只要执行一个,函数就立即结束,并且把return后的值当做本次调用的结果返回
注意:
1、返回值没有类型限制
2、返回值没有个数限制,可以用逗号分开多个值一次返回
3、可以没有return,默认返回None
什么时候该有返回值?
调用函数,经过一系列的操作,最后拿到一个明确的结果,则必须要有返回值
通常有参数需要返回值,输入参数,经过计算,得到一个最终结果
什么时候不需要返回值?
调用函数,仅仅只是执行一系列的操作,最后不需要得到什么结果,则无需有返回值
通常无参函数不需要有返回值
7、函数调用:
# 语句形式:userinfo()
# 表达式形式:3*len('userinfo')
# 当做其他函数的参数:range(len('userinfo'))
8、函数基础阶段总结:
1、函数的使用必须遵守:先定义后调用的原则
2、在定义阶段,只检测函数命名语法以及函数体的语法
3、在调用阶段,通过函数名找到函数的内存地址,然后执行函数体代码
4、函数名;是用来访问到函数的内存地址的,拿到函数的内存地址加括号就可以触发函数体代码的执行
5、函数参数:是外界调用者为函数传值的媒介
6、函数体代码:是函数功能的具体实现
7、return是函数体执行的成果
二、函数的参数
1、实参与形参
函数的参数可以分为两大类:形参与实参
形参:函数定义阶段,括号内指定的参数,可以理解为变量名
实参:函数调用阶段,括号内指定的参数,可以理解为变量名对应的值
只有在调用函数时才会在函数体内发生实参(值)与形参(变量名)的绑定关系
该绑定关系仅在调用函数时临时生效,调用结束后就解除绑定
2、位置参数
'''
位置形参:函数定义阶段,按照从左到右的顺序依次定义的形参,称之为位置形参
位置实参:函数调用阶段,按照从左到右的顺序依次传入的值,称之为位置实参
'''
def userinfo(name,age):
print('姓名:%s 年龄:%s' %(name,age))
userinfo('fred',18) # 此时就相当于格式化输出中的%s
姓名:fred 年龄:18 # 从左到右 name=fred age=18
# userinfo('fred',18,'sa')
typeError: userinfo() takes 2 positional arguments but 3 were give
# 值传多了
userinfo('fred')
userinfo() missing
1 required positional argument:
'age' # 没有给age传值
userinfo(20,'fred')
姓名:20 年龄:fred # 由于严格按照位置传值,在不了解参数具体功能情况下,就会出现这种结果
# 总结:
# 但凡是按照位置定义的形参,在调用时必须为其传值,多一个不行,少一个也不行
# 在传值时,按顺序与形参一一对应
3、关键字实参
'''
关键字实参:在函数调用阶段,按照key=value的形式定义的实参,称之为关键字实参
注意:
1、关键字实参可以完全打乱顺序,但仍然可以指名道姓的为指定形参传值
2、可以在调用函数时,关键字实参和位置实参混合使用
3、关键字实参要跟在位置实参的后边,并且不能为一个形参传多个值
'''
def userinfo(name,age,company):
print('姓名:{name} 年龄:{age} 公司:{company}'.format(name=name,age=age,company=company))
userinfo('fred',company='9you',age=20)
# 姓名:fred 年龄:20 公司:9you
userinfo(age=20,company='9you','fred')
SyntaxError: positional argument follows keyword argument # 位置参数不能跟在关键字参数右边
4、默认形参
'''
默认参数:在定义函数时,就已经为某些参数绑定值,称之为默认形参
注意:
1、在定义阶段就已经有值,意味着在调用阶段可以不用为其传值
2、默认形参必须放到位置形参后面
3、默认形参的值只在定义阶段生效一次,在函数定义之后发生的改动无效
4、默认形参的值通常应该为不可变类型
什么时候应该定义默认形参:
大多数情况下都一样的,比如民族
什么时候应该定义位置形参
大多数情况是不一样的,比如name
'''
def userinfo(name,age,company,sex='male'):
print('姓名:{name} 年龄:{age} 公司:{company} 性别:{sex}'.format(name=name, age=age, company=company,sex=sex))
userinfo('fred',age=18,company='9you') # 可以不用为默认形参传值
姓名:fred 年龄:18 公司:9you 性别:male
userinfo('jerry',age=18,company='9you',sex='female')
姓名:jerry 年龄:18 公司:9you 性别:female # 也可为默认形参传值,以传的值为准
def userinfo(name,age,sex='male',company):
print('姓名:{name} 年龄:{age} 公司:{company} 性别:{sex}'.format(name=name, age=age, company=company,sex=sex))
SyntaxError: non-default argument follows default argument # 默认形参不能在位置形参左边
5、可变长参数
'''
可变长参数是在调用函数时,函数的参数个数是不固定的
然而实参终究要为形参传值的,针对两种形式的参数不固定,对应着形参也必须有两种解决方案,来分别处理溢出的位置实参与关键字实参
python中约定俗成:
*args # 位置形参
*kwargs # 关键字形参
* 会把溢出的实参存成元组,然后赋值给紧跟其后的变量名
** 会把溢出的关键字实参存成字典,然后赋值给紧跟其后的变量名
'''
1)形参中带*
def userinfo(username,passwd,*args):
print('用户名:%s' %username)
print('密码:%s' %passwd)
print('其他:',args)
userinfo('root','x', '0', '0', 'root', '/root', '/bin/bash')
用户名:root
密码:x
其他: ('0', '0', 'root', '/root', '/bin/bash')
2)形参中带* && 实参中带*
'''
实参中* 小窍门:
但凡实参中*,都先将其打散成位置实参,然后考虑传值
'''
def userinfo(username,passwd,*args):
print('用户名:%s' %username)
print('密码:%s' %passwd)
print('其他:',args)
userinfo('root',*('x', '0', '0', 'root', '/root', '/bin/bash')) #此处,只传一个位置实参,然后*后面跟了元组
用户名:root
密码:x
其他: ('0', '0', 'root', '/root', '/bin/bash') #处理过程:先将元组打散成位置实参,然后将x传给passwd,剩下的整体传给*,并绑定给args
3)、实参中带*
'''
实参中* 小窍门:
但凡实参中*,都先将其打散成位置实参,然后考虑传值
'''
def userinfo(username,passwd,desc):
print('用户名:%s' %username)
print('密码:%s' %passwd)
print('描述:%s' %desc)
l=['fred','1234','this is fred']
userinfo(*l) #实参中带*,形参中没带*的情况,要严格遵守位置形参的规则,多一个不行,少一个也不行
4)、形参中带**
'''
形参中带** 溢出的关键字实参会存成字典,然后赋值给紧跟其后的变量名
'''
def userinfo(username,passwd,**kwargs):
print('用户名:%s' %username)
print('密码:%s' %passwd)
print('其他:',kwargs)
userinfo(username='root',passwd='x',uid='0',gid='0',desc='root') # 溢出关键字实参被**存成字典,赋值给kwargs
用户名:root
密码:x
其他: {'uid': '0', 'gid': '0', 'desc': 'root'}
5)、形参带**,实参中带**
'''
形参中带** 溢出的关键字实参会存成字典,然后赋值给紧跟其后的变量名
实参中带** 但凡实参中带** ,先将其打散成关键字实参,然后再考虑传值
'''
def userinfo(username,passwd,**kwargs):
print('用户名:%s' %username)
print('密码:%s' %passwd)
print('其他:',kwargs)
userinfo('root',**{'passwd':'x','uid':'0','gid':'0','desc':'root'}) # 实参中带** 先将打散成关键字实参,passwd=x 其他给了kwargs
用户名:root
密码:x
其他: {'uid': '0', 'gid': '0', 'desc': 'root'}
6)、实参中带**
'''
实参中带** 但凡实参中带** ,先将其打散成关键字实参,然后再考虑传值
'''
def userinfo(username,passwd):
print('用户名:%s' %username)
print('密码:%s' %passwd)
userinfo(**{'username':'fred','passwd':'1234','desc':'this is fred'})
TypeError: userinfo() got an unexpected keyword argument 'desc' # 不能传多也不能传少
userinfo(**{'username':'fred','passwd':'1234'})
用户名:fred密码:1234
7、练习
'''
1、写函数,,用户传入修改的文件名,与要修改的内容,执行函数,完文件修改操作
'''
def replance_file(file_name,old,new):
'''
修改文件内容
:param file_name:要修改的文件
:param old: 修改前的的内容
:param new: 修改后的内容
:return:
'''
import os
with open(file_name, mode='r', encoding='utf8') as f_r,
open('.%s.swap' % file_name, mode='w', encoding='utf8') as f_w:
for line in f_r:
if line.startswith(old):
line = line.replace(old,new)
f_w.write(line)
else:
f_w.write(line)
os.remove(file_name)
os.rename('.%s.swap' %file_name,file_name)
return old,new
res=replance_file('src.txt','name','姓名')
print(res)
'''
2、写函数,计算传入字符串中【数字】、【字母】、【空格] 以及 【其他】的个数
'''
def result(s):
counts={
'alpha':0,
'digit':0,
'space':0,
'other':0,
}
for i in s:
if i.isalpha(): # 判断是否为字母
counts['alpha']+=1
elif i.isdigit(): # 判断是否为数字
counts['digit']+=1
elif i.isspace(): # 判断是否为空格
counts['space']+=1
else:
counts['other']+=1
return counts
res=result('hello 18 age name %%%%&&&')
print(res)
'''
3、写函数,判断用户传入的对象(字符串、列表、元组)长度是否大于5。
'''
def judge(*args):
result=[]
for i in args:
if len(i) > 5:
result.append(i)
return result
res=judge('fred_li',[18,'9you','sa'],('read ','play'))
print(res)
'''
4、写函数,检查传入列表的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者。
'''
def new_list(l):
if len(l) > 2:
return l[0],l[1]
else:
return l
res=new_list(['fred','18','read','play'])
print(res)
'''
5、写函数,检查获取传入列表或元组对象的所有奇数位索引对应的元素,并将其作为新列表返回给调用者。
'''
def fun(l):
return l[::2]
res=fun(['root','x','/home','this is root','/sbin/nologin'])
print(res)
'''
6、写函数,检查字典的每一个value的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者。
dic = {"k1": "v1v1", "k2": [11,22,33,44]}
PS:字典中的value只能是字符串或列表
'''
def func(dic):
d={}
for k,v in dic.items():
if len(v) > 2:
d[k]=v[0:2]
else:
d[k]=v
return d
print(func({'username':'fred','dept':('sa','ceo','cto','coo'),'hobby':['read','music','play','game'],'desc':('friend','father')}))
三、函数对象
'''
函数是第一类对象,意味着函数可以当做数据去使用
'''
# 函数身为一个对象,拥有对象的三个通用属性:id type value
def userinfo():
print('from userinfo')
print(userinfo)
<function userinfo at 0x0387AA98>
print(type(userinfo))
<class 'function'>
print(id(userinfo))
59222680
'''
可以被引用,即函数可以赋值给一个变量
'''
def userinfo():
print('from userinfo')
func=userinfo
print(func,userinfo)
<function userinfo at 0x051DBA98>
<function userinfo at 0x051DBA98>
'''
可以当做参数传给另外一个函数
'''
def f1():
print('from f1')
def f2(func):
print(func)
f1()
f2(f1) # f1是当做参数传给f2 ,f2内部调用了f1
<function f1 at 0x036CBA98>
from f1
'''
可以当做函数的返回值
'''
def f1():
print('from f1')
def f2():
return f1
res=f2()
print(res)
<function f1 at 0x0369BA98>
res()
from f1
'''
可以当做容器类型(可以存多个值)的元素
容器对象(list、dict、set等)中可以存放任何对象,包括整数、字符串,函数也可以作存放到容器对象中
'''
def auth():
print('from auth')
def pay():
print('from pay')
func_dic={
'1':auth,
'2':pay,
}
func_dic['1']()
from auth
func_dic['2']()
from pay
四、函数嵌套
1、函数的嵌套调用
'''
函数的嵌套调用:在调用一个函数时,其内部的代码又调用了其他的函数
'''
def sum(x,y):
sum=x+y
return sum
def sums(a,b,c,d):
res=sum(a,b)
res1=sum(c,d)
res2=res+res1
return res2
print(sums(10,20,30,40))
2、函数的嵌套定义
'''
函数的嵌套定义:在一个函数的内部又定义了另外一个函数
'''
def f2():
x=1
def f1():
print('from f1')
print(x)
f1()
f2()
五、名称空间与作用域
1、名称空间
'''
名称空间:是名称与对象之间的关系,可以将命名空间看做是字典,其中的键是名称,值是对象
'''
'''
内置名称空间:存放Python解释器自带的名称,如len max sum
生命周期:在解释器启动时产生,关闭时回收
'''
print(len)
<built-in function len>
'''
全局名称空间:除了内置的与局部的名字之外的都属于全局名称空间
生命周期:在程序文件执行时,立刻产生,程序执行完毕后就回收
'''
name='root'
'''
局部名称空间:存放函数内部定义的名字
生命周期:在调用函数时临时生效,函数调用结束后立即回收
'''
def user():
age=18
return age
print(user())
print(age)
name 'age' is not defined # 函数调用已结束,所以age和18的绑定关系也被回收了
'''
加载顺序:内置名称空间———全局名称空间————局部名称空间
查找顺序:基于当前名称空间查找 当前————上一级————上上级
假设目前在局部名称空间:查找顺序为:局部名称空间————全局名称空间————内置名称空间
强调::::: 函数的形参属于局部名称空间
'''
def foo():
x=4
print(locals()) # locals()方法是打印出当前局部名称空间中的名字与value的对应关系
foo()
{'x': 4} # 以字典方式存储
2、作用域
'''
作用域:指的是名字的作用范围
分为:全局作用域和局部作用域
'''
'''
全局作用域:包含内置名称空间与全局名称空间中的名字
特点:全局有效,全局存活
'''
'''
局部作用域:只包含局部名称空间的名字
特点:局部有效,临时存活
'''
x=3333
def area():
x=222
def area1():
x=111
print('from area1 %s' %x)
area1()
print('from area %s' %x)
area() # 次函数为嵌套函数
from area1 111 # 在area1函数内部,x与111绑定,area1被调用时,area1内部有x的名字与值对应的关系,所以x此时等于111
from area 222 # area1调用结束后,x=111的绑定关系也随着解除,在area内部,x与222绑定,area被调用,所以此时x=222
print(x)
3333 # area调用结束,x与222的绑定关系也随之解除,所以此时是在全局作用域里查找x对应的valve,所以此时x=3333
'''
总结:
函数的作用域关系是在函数定义阶段就已经固定死的,与函数的调用位置无关
即在调用函数时一定要到定义函数的位置寻找作用域关系
'''
3、global和nonlocal
'''
global:在局部名称空间声明其后的变量是作用于全局
'''
x=3333
def func():
x=2222
def func1():
global x
x=4444
print('from func1 %s' %x)
func1()
print('from func %s' %x)
func()
from func1 4444 # 由于func1中声明了x是全局名称空间中的名字,所以它作用于本层局部作用域和全局作用域,
from func 2222 # 但不影响上层局部作用域
print(x)
4444 # 此时就算全局名称空间中定义了x=3333,也以global所在的局部名称空间中定义的为准
'''
nonlocal 在局部名称空间声明其后的变量作用于上层
'''
x=1111
def func():
x=4444
def func1():
nonlocal x
x=3333
print('from func1 %s' %x)
func1()
print('from func %s' %x)
func()
from func1 3333 # 由于func1中nonlocal声明了x=3333
from func 3333 # 所以就算func中定义了x=4444,也不生效
print(x)
1111 # nonlocal并未影响全局名称空间
六、闭包函数
'''
大前提:函数的作用域关系是在函数定义时就已经固定死的,与调用位置无关
闭包函数:
1、闭指的是:定义在函数内部的函数
2、包指的是:该内部函数包含对其外层函数作用域名字的引用
闭包函数通常需要结合函数对象的概念,打破层级限制,将闭包函数返回到外部使用
'''
import requests # 要事先安装requests模块
def outter(url):
def get():
response=requests.get(url)
print(len(response.text))
return get # 返回get函数的内存地址
jd=outter('http://www.jd.com') # 调用outter函数,实际是调用内部的get
jd()
baidu=outter('https://www.baidu.com')
baidu()
'''
总结:
1、闭包函数传一次值,多次调用
2、通过外部函数把值传给内部函数
3、闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得无论在何处调用,都优先使用自己外层包裹的作用域
'''
七、装饰器
'''
装饰器是闭包函数一种应用场景
装饰器:
装饰指的在不修改被装饰对象的前提下,为被装饰对象添加新功能
器指的是工具
装饰器本身可以为任意可调用的对象,被装饰的对象也可以是任意可调用的对象
原则:
写一个函数给另外一个函数添加新功能,需要遵守开放封闭的原则(对修改是封闭的,对扩展是开放的)
1、不修改被装饰对象的源代码
2、不修改被装饰对象的调用方式
'''
1、无参装饰器之装饰无参对象
'''
无参装饰器
'''
import requests
import time
def get():
response=requests.get('http://www.baidu.com')
print('The url have %s' %(len(response.text)))
# 为get函数加上统计时间的功能
def timmer(func):
start_time=time.time()
func()
stop_time=time.time()
print('run time is %s' %(stop_time - start_time))
# timmer(get) # 此方法虽然能实现需求,但是其改变了原对象的调用方式,pass掉
# 只能考虑使用闭包函数咯
def wrapper(func): # func为最原始的get
def timmer(): # 此处一定不能传值,如果传值就失去了闭包的意义
start_time=time.time()
func() #此处的的func是由包在外层的wrapper传进来的
stop_time=time.time()
print('run time is %s' %(stop_time - start_time))
return timmer
get=wrapper(get) # 此时(get)为最原始的get函数,传给了func,然后将wrapper(get)重新赋值get
get() # 此处get被伪装了,站在用户的角度上,还以为调用的是最原始的get函数,其实get调用的是wrapper,在wrapper内部为get加了统计时间的功能
# 到此为止,新功能实现了,并且遵守了开放封闭原则(未修改被装饰对象的源代码和调用方式)
'''
装饰器语法
在被调用对象的正上方使用"@装饰器名"
@装饰器名 == get=wrapper(get)
'''
# 上面的功能可以改写为:
def wrapper(func): # func为最原始的get
def timmer(): # 此处一定不能传值,如果传值就失去了闭包的意义
start_time=time.time()
func() #此处的的func是由包在外层的wrapper传进来的
stop_time=time.time()
print('run time is %s' %(stop_time - start_time))
return timmer
@wrapper #get=wrapper(get)
def get():
response=requests.get('http://www.baidu.com')
print('The url have %s' %(len(response.text)))
get()
The url have 2381
run time is 0.034737348556518555
2、无参装饰器之装饰有参对象
'''
有参装饰器
接上面的例子,只能统计http://www.baidu.com的时间统计,功能被写死了
此时就得给装饰器传参了
'''
import requests
import time
def wrapper(func): # 最原始的get
def timmer(*args,**kwargs): #给被调用对象传参 'http://www.jd.com'
start_time=time.time()
func(*args,**kwargs) #get('http://www.jd.com') / home()
stop_time=time.time()
print('run time is %s' %(stop_time - start_time))
return timmer
@wrapper
def get(url):
response=requests.get(url)
print('The url have %s' %(len(response.text)))
@wrapper(‘http://www.jd.com’)
def home():
time.sleep(1)
print('welcome access home page')
# 过程分析:
# get=wrapper(get) # 首先将get传给wrapper,也就间接传给timmer,拿到timmer的返回值,即wrapper(get)
# print(get)
# <function wrapper.<locals>.timmer at 0x051AFDB0>
get() # 第二步,此时是给最原始的get传参,由timmer接收,并传给内部的get函数,完成新加功能并且未修改被装饰对象的源代码和调用方式
home()
# 到此为止,可实现即可装饰有参对象和无参对象
'''
完整版装饰器语法
'''
import requests
import time
def wrapper(func):
def timmer(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs)
stop_time=time.time()
print('run time is %s' %(stop_time - start_time))
return res # 返回源对象的返回值
return timmer
@wrapper
def get(url):
response=requests.get(url)
# print
return ('The url have %s lines' %(len(response.text))) # 源对象有返回值
print(get('https://www.baidu.com'))
示例:
'''
为被装饰对象加上认证功能'''
import requests
import time
current_userinfo={'user':None} # 存储当前登录用户的信息
def auth(func):
def wrapper(*args,**kwargs):
if current_userinfo['user']: # 如果current_userinfo['user']的值非空,则证明当前用户已登录,不用重复登录
func(*args,**kwargs)
return print(func(*args,**kwargs))
else: # 否则,从新登录
username=input('请输入账号:').strip()
passwd=input('请输入密码:').strip()
if username == 'fred' and passwd == '123':
print('welcome to login')
current_userinfo['user']=username
func(*args,**kwargs)
return print(func(*args,**kwargs))
else:
print('用户或密码错误')
return wrapper
@auth
def get(url):
response=requests.get(url)
return ('The url have %s' %(len(response.text)))
@auth
def home():
time.sleep(1)
return ('welcome access home page')
get('http://www.jd.com')
home()
3、多个装饰器执行顺序
'''
为一个对象添加多个装饰器
'''
import requests
import time
current_userinfo={'user':None} # 存储当前登录用户的信息
def auth(func):
def wrapper(*args,**kwargs):
if current_userinfo['user']:
func(*args,**kwargs)
return print(func(*args,**kwargs))
else:
username=input('请输入账号:').strip()
passwd=input('请输入密码:').strip()
if username == 'fred' and passwd == '123':
print('welcome to login')
current_userinfo['user']=username
func(*args,**kwargs)
return print(func(*args,**kwargs))
else:
print('用户或密码错误')
return wrapper
def timmer(func): # 最原始的get
def wrapper1(*args,**kwargs):
start_time=time.time()
res=func(*args,**kwargs) # wrapper(get)
stop_time=time.time()
print('run time is %s' %(stop_time - start_time))
return res
return wrapper1
@timmer
@auth
def get(url):
response=requests.get(url)
return ('The url have %s' %(len(response.text)))
get('http://www.jd.com')
'''
分析:
1、@auth === 最原始的get 》》》 wrapper(get) 即get=wrapper(get)
2、@timmer == 不是最原始get 》》 wrapper1(wrapper(get)) 即get=wrapper1(wrapper(get))
执行顺序:
1、先执行timmer,就是wrapper1(wrapper(get)), 即执行auth
2、等auth执行完,再回到timmer,执行res=func(*args,**kwargs)之后的代码
3、整个装饰器执行完成,本次时间统计是从timmer开始到auth结束,查看结果
请输入账号:fred
请输入密码:1234
用户或密码错误
run time is 5.6903979778289795
结论:多个装饰器的情况下,自上而下一次执行
'''
4、有参装饰器
'''
有参装饰器:本质就是在无参装饰器外又包了一层
'''
import requests
import time
current_userinfo={'user':None} # 存储当前登录用户的信息
def multi_auth(engine):
def outter(func):
def wrapper(*args,**kwargs): # engine是关键字实参传进来的,所以file一定会传给engine
if engine == 'file':
if current_userinfo['user']:
func(*args,**kwargs)
return print(func(*args,**kwargs))
else:
username=input('请输入账号:').strip()
passwd=input('请输入密码:').strip()
if username == 'fred' and passwd == '123':
print('welcome to login')
current_userinfo['user']=username
func(*args,**kwargs)
return print(func(*args,**kwargs))
else:
print('用户或密码错误')
elif engine == 'mysql':
print('mysql认证机制')
elif engine == 'ldap':
print('ldap认证机制')
else:
print('不支持改认证机制')
return wrapper
return outter
@multi_auth(engine='file') # outter(get)
# @multi_auth(engine='mongdb')
def get(url):
response=requests.get(url)
return ('The url have %s' %(len(response.text)))
get('http://www.jd.com')
5、完全装饰被装饰对象(继承被装饰对象的属性)
'''
wraps装饰器:获取被装饰对象的属性(函数名、帮助信息等)
Help on function wraps in module functools:
wraps(wrapped, assigned=('__module__', '__name__', '__qualname__', '__doc__', '__annotations__'), updated=('__dict__',))
'''
import requests
import time
from functools import wraps
current_userinfo={'user':None} # 存储当前登录用户的信息
def multi_auth(engine):
def outter(func):
@wraps(func) # 将最原始的get对象的属性转嫁给wrapper
def wrapper(*args,**kwargs): # engine是关键字实参传进来的,所以file一定会传给engine
if engine == 'file':
if current_userinfo['user']:
func(*args,**kwargs)
return print(func(*args,**kwargs))
else:
username=input('请输入账号:').strip()
passwd=input('请输入密码:').strip()
if username == 'fred' and passwd == '123':
print('welcome to login')
current_userinfo['user']=username
func(*args,**kwargs)
return print(func(*args,**kwargs))
else:
print('用户或密码错误')
elif engine == 'mysql':
print('mysql认证机制')
elif engine == 'ldap':
print('ldap认证机制')
else:
print('不支持改认证机制')
return wrapper
return outter
@multi_auth(engine='file')
def get(url):
'''
获取指定url的response.text的长度
:param url:
:return:
'''
response=requests.get(url)
return ('The url have %s' %(len(response.text)))
# get('http://www.jd.com')
print(help(get)) # 获取函数的帮助信息(注释)
Help on function get in module __main__:
get(url)
获取指定url的response.text的长度
:param url:
:return:
None
八、三元表达式、列表生成式、字典生成式、二分法、函数与的递归调用
1、三元表达式
'''
三元表达式:
语法:
条件成立的返回值 if 条件 else 条件不成立的返回值
'''
x=10
y=20
# res=x if x > y else y
res=True if x > y else False
print(res)
2、列表生成式
'''
列表生成式:和for循环搭配
语法:l=[i for i in 序列 条件(可以没有条件)] 条件只能跟一个
'''
l=[i for i in range(5)]
print(l)
# 等同于
for_l=[]
for i in range(5):
for_l.append(i)
print(for_l)
l1=[i for i in range(5) if i > 2]
print(l1)
# 等同于
for_l1=[]
for i in range(5):
if i > 2:
for_l1.append(i)
print(for_l1)
str='root:x:0:0:root:/root:/bin/bash'
l2=str.split(':')
l2=[i.upper() for i in l2 if i == 'root']
print(l2)
# 等同于
l3=[]
for i in str.split(':'):
if i == 'root':
l3.append(i.upper())
print(l3)
3、字典生成式
'''
字典生成式:
语法:d1={k.upper():v for k,v in d1.items() if } 可以不加条件
'''
d1={
'name': 'fred',
'age': 18,
'commany': '9you',
'weight': 50.4
}
print({k.upper():v for k,v in d1.items() if k == 'weight'})
# 等同于
d2={}
for k,v in d1.items():
d2[k.upper()]=v
print(d2)
4、函数的递归调用
'''
函数的递归调用:
在调用一个函数的过程中又直接或间接调用了自己,称之为函数的递归调用
本质就是一个重复的过程,必须有两个明确阶段:
1、回溯:一层一层的递归下去调用下去,每进入下一层问题的规模都应该有所减少
2、递推:往回一层一层的结束调用
递归必须要有一个明确的结束条件,在满足该条件的情况下会终止递归
递归调用的分类:
直接调用:自身调用
def foo():
foo()
间接调用:通过别的函数间接调用自身
def bar()
foo()
递归 VS while循环
递归只需要把控结束或进入递归的条件即可,至于循环次数或者嵌套层数无须考虑
调用函数会产生局部的名称空间,占用内存,因为上述这种调用会无需调用本身,
python解释器的内存管理机制为了防止其无限制占用内存,对函数的递归调用做了最大的层级限制
import sys
sys.getrecursionlimit() 查看当期最大层级限制
sys.setrecursionlimit(2000) 修改最大层级限制
'''
# 示例:
# age(5) = age(4) + 2
# age(4) = age(3) + 2
# age(3) = age(2) + 2
# age(2) = age(1) + 2
# age(1) = 18
#推算出age(5)的值
def age(n): # n从高到底,一步一步逼近1
if n == 1:
return 18
return age(n-1)+2
print(age(2))
# 取出l中的每个元素
l=[1,[2,[3,]]]
def foo(l):
for item in l:
if type(item) is list:
foo(item)
else:
print(item)
foo(l)
1
2
3
#总结递归的使用:
1. 必须有一个明确的结束条件
2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
5、二分法
'''
二分法是一种快速查找的方法,时间复杂度低,逻辑简单易懂,总的来说就是不断的除以2除以2...
分法查找非常快且非常常用,但是唯一要求是要求数组是有序的
'''
# 查找次序列中有没有43
nums=[3,11,13,15,23,27,43,51,72,81,93,101]
def binary_search(num,nums):
print(nums)
if len(nums) == 0:
print('%s is not found' %num)
return
#
mid_index=len(nums) // 2
if num > nums[mid_index]:
# in right
nums=nums[mid_index+1:] # 从mid_index索引号加一位(如果不加1,则取到mid_index)开始-数组最后
binary_search(num,nums) #重复调用本身逻辑,传入新的nums
elif num < nums[mid_index]:
# in left
nums=nums[:mid_index] #从数组索引0开始取-mid_index
binary_search(num,nums) # 重复调用本身逻辑,传入新的nums
else:
print('find it')
binary_search(94,nums)
[3, 11, 13, 15, 23, 27, 43, 51, 72, 81, 93, 101]
[51, 72, 81, 93, 101]
[93, 101]
[93]
[]
94 is not found
九、匿名函数
'''
匿名函数:即没有名字的函数,与函数有相同的作用域,但是匿名函数意味着引用计数为0,使用一次就释放,除非让其有名字
定义匿名函数就是定义出了一个函数的内存
语法:
lambda 实参 函数体代码
lambda 只是一个表达式,函数体比 def 简单很多。
lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
'''
func=(lambda name,age:print('my name is %s my age is %s' %(name,age)))('name',20)
print(func)
'''
示例
文件内容标题为name,age,work,commany
fred|20|SA|haymarker
lck|30|IT|u9
取出每一行,按如下格式存储
[{'name':'fred','age':'20','work':'SA','commany':'haymarker'}]
'''
# 字典生成式的另一种应用场景
with open('info.txt','rt',encoding='utf8') as info:
items=[line.strip().split('|') for line in info] # 列表生成式将文件内容按行转换成列表
# 字典生成式将items的值传给字典里的key
userinfo=[{'name':name,'age':age,'salary':salary,'work':work,'commany':commany} for name,age,salary,work,commany in items]
# print(userinfo)
# 取出年龄最大的成员的信息
def maxage(dic):
return dic['age'] # 返回age对应的value
for dic in userinfo:
res=maxage(dic) # 循环取出最大age的值
print(max(userinfo,key=maxage)) # 调用maxage函数,取出age最大用户信息,由于循环的是userinfo列表,所以返回最大age所在的列表
print(max(userinfo,key=lambda dic:dic['age'])) #dic是参数,
{'name': 'lck', 'age': '30', 'salary': '1000', 'work': 'IT', 'commany': 'u9'}
# 取出薪资最高的成员信息
print(max(userinfo,key=lambda dic:dic['salary']))
{'name': 'fred', 'age': '20', 'salary': '3000', 'work': 'SA', 'commany': 'haymarker'}
# 将成员信息映射成首字母大写的形式
def capital(dic):
return {'name':dic['name'].capitalize(),
'age':dic['age'],
'salary':dic['salary'],
'work':dic['work'],
'commany':dic['commany']}
for dic in userinfo:
res=capital(dic)
user=map(capital,userinfo)
print(user)
<map object at 0x04CD1ED0>
print(list(user))
[{'name': 'Fred', 'age': '20', 'salary': '3000', 'work': 'SA', 'commany': 'haymarker'}, {'name': 'Lck', 'age': '30', 'salary': '1000', 'work': 'IT', 'commany': 'u9'}]
users=map(lambda dic:{
'name':dic['name'].capitalize(),
'age':dic['age'],
'salary':dic['salary'],
'work':dic['work'],
'commany':dic['commany']
},userinfo)
print(list(users))
十、迭代器、生成器、生成器表达式
1、迭代器
'''
什么是迭代器
迭代器就是一个迭代取值的工具
迭代是一个重复的过程,但是每一次重复都是基于上一次的结果而进行
为何要用迭代器
针对没有索引的数据类型,比如字典、集合、文件,要想迭代取出其中包含的一个个的值
Python解释器必须提供一种能够不依赖与索引的迭代取值工具
什么是可迭代对象
内置有__iter__()方法的都是可迭代对象
以下数据类型都是可迭代对象:
<str_iterator object at 0x04AB24F0>
<list_iterator object at 0x04AB2970>
<dict_keyiterator object at 0x04AB38A0>
<set_iterator object at 0x04AB85D0>
<_io.TextIOWrapper name='info.txt' mode='r' encoding='utf8'>
int和float不是可迭代对象
迭代器对象
1、内置有__iter__()
2、内置有__next__()
文件既是可迭代对象,又是迭代器对象
f.__iter__()
f.__next__()
对于可迭代对象来说,调用__iter__()方法,得到就是迭代器对象
注意:
迭代器对象一定是可迭代对象
可迭代对象不一定是迭代器对象'''
'''
迭代器对象的使用
'''
info=['adm','/var/adm','/sbin/nologin']
obj=info.__iter__()
print(obj.__next__()) # obj.__next__() 等同于next(obj)
print(next(obj))
print(next(obj))
print(next(obj)) # next()方法抛出StopIteration异常时,标志着迭代器对象里的值已经被取完了
StopIteration
# 使用while循环来取值
while True:
try:
print(next(obj),end=' ')
except StopIteration: # except捕捉到StopIteration异常,则break掉
break
'''
try...except:
try的工作原理是,当开始一个try语句后,python就在当前程序的上下文中作标记
这样当异常出现时就可以回到这里,try子句先执行,接下来会发生什么依赖于执行时是否出现异常。
如果当try后的语句执行时发生异常,python就跳回到try并执行第一个匹配该异常的except子句,异常处理完毕,控制流就通过整个try语句(除非在处理异常时又引发新的异常)。
如果在try后的语句里发生了异常,却没有匹配的except子句,异常将被递交到上层的try,或者到程序的最上层(这样将结束程序,并打印缺省的出错信息)。
如果在try子句执行时没有发生异常,python将执行else语句后的语句(如果有else的话),然后控制流通过整个try语句。
'''
2、for循环的工作原理
'''
基于for循环就可以不依赖与索引取值
工作原理:
1.执行 in后对象的dic.__iter__()方法,得到此对象的迭代器对象
2.执行next(dic.__iter__()),将得到的值赋值给k
3.重复过程2,直到捕获StopIteration,结束循环
'''
dic={
'name':'fred',
'age':19,
'commany':'haymarker',
}
for k in dic:
print(k)
'''
迭代器的优缺点:
优点:
提供了一种不依赖与索引的、通用的取值方法
节省内存,同一时间在内存中只有一个值(对象元素过大,迭代器可以更节省内存)
缺点:
针对同一个迭代器对象只能取完一次,不能重复取,不如按照索引或者key取值灵活
无法预测迭代器对象所包含值的个数
'''
3、生成器
'''
什么是生成器
在函数内但凡出现yield关键字,再调用函数就不会触发函数体代码的执行了
generator() 直接调用不会触发函数体代码的执行
会得到一个返回值,该返回值就是一个生成器对象
def generator():
print('this is first')
yield
print(generator())
<generator object generator at 0x04F52870>
而生成器对象本身就是迭代器
g.__next__()
g.__iter__()
为什么要用生成器
生成器是自定义的迭代器
'''
def generator():
print('this is first')
yield
g=generator()
print(g.__next__())
# 既然g是迭代器,那么就可以通过迭代器规则通过next()方法取值
# 即触发g对应的函数体代码的执行,直到碰到一个yield就暂停住,该yield后的值就当做本次next返回值
# 如果yield后面没值,则返回None
print(g.__next__()) # 依次取值,直到遇到抛出StopIteration,说明该对象中的值已经被取完了
'''
总结yield功能:
1、提供了一种自定义迭代器的方式
2、可以用于返回值
yield与return的区别:
相同点:都可以用于返回值,个数及类型都没有限制
不同点:yield可以返回多次值,return只能返回一次,整个函数就结束了
3、函数暂停以及继续执行的状态是由yield保存的
'''
# 练习:自定义生成器,模拟range方法
def my_range(start,stop,step=1):
while True:
if start >= stop:
break
yield start
start+=step
for item in my_range(1, 5, 2):
print(item)
十一、面向过程编程
''''
面向过程编程
1、首先强调:面向过程编程绝对不是用函数编程这么简单,面向过程是一种编程思路、思想,而编程思想是不依赖于具体的语言或语法。言外之意即是我们不依赖于函数,也可以基于面向过程的思想编写程序
2、定义
面向过程的核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么
基于面向过程设计程序就好比在设计一条流水线,是一种机械式的思维方式
3、优点:复杂的问题流程化,进而简单化
4、缺点:可扩展性差,修改流水线的任意一个阶段,都会牵一发而动全身
5、应用:扩展性要求不高的场景,典型案例如Linux内核。git,httpd
'''