一.函数的基本使用
接触一个未知的知识都要先从三个方面入手
1、什么是函数
在程序具备某一功能的工具=》函数
事先准备好工具=》函数的定义
遇到应用场景、拿来就用=》函数的调用
分为两大类:
01.内置函数
02.自定义函数
2、为何要用函数
01.代码冗余
02.程序的组织结构不清晰,可读性差
03.扩展性差
3、如何用函数
函数的使用必须遵循一个原则:
01. 先定义
定义语法:
def 函数名(参1,参2,参3,...):
"""
文档注释
"""
代码1
代码2
代码3
...
return 值
02. 后调用
函数名()
4.定义阶段: 只检测语法,不执行代码
def func():
print('from func 1')
print('from func 2')
print('from func 3')
print('from func 4')
5. 调用阶段: 开始执行函数体代码
func()
示范一:
def foo():
print('from foo')
bar()
foo()#'bar'没有被定义(define),所以会报错
示范二:
def bar():
print('from bar')
def foo():
print('from foo')
bar()
foo()#会打印出from foo
from bar
示范三:
定义
def foo():
print('from foo')
bar()
def bar():
print('from bar')
调用
foo()
6.定义函数的三种形式
01.无参函数
def func():
print('from func')
func()
02.有参函数
def max2(x,y):
x=1
y=2
if x > y:
print(x)
else:
print(y)
max2(1,2)
max2(3,4)
03.空函数
def register():
pass
7.调用函数的三种形式
01.语句形式
def func():
print('from func')
func()
02.表达式形式
def max2(x,y):
if x > y:
return x
else:
return y
res=max2(1000,2000) * 12#此处需要的是返回值所以用return,不然不可以运算
print(res)
03.函数的调用可以当作另外一个函数的参数传入
比较1,2,3的大小
def max2(x,y):
if x > y:
return x
else:
return y
res=max2(max2(1,2),3)#用函数依次比较大小
print(res)
二.函数的返回值
1.什么是函数的返回值
返回值是函数体代码的运行成果
2.为何要有返回值
需要拿到函数的处理结果做进一步的处理,则函数必须有返回值#如同上方的表达式形式需要用到返回的值的话就需要用到返回值
3.如何用
return 返回值的特点:
01.返回的值没有类型限制,也没有个数限制
I: return或者函数没return:返回值None
II: return 值:返回的就是该值本身
III: return 值1,值2,值3:返回元组(值1,值2,值3)
def func():
return 1,1.2,'aaa',[1,2,3]
pass#占位符,没有作用
res=func()
print(res)
02.return是函数结束运行的标志,函数内可以有多个return,但只要执行一次函数就立即结束,并且将return后的值当作本次调用的结果返回
def func():
print('aaaa')
return 1
print('bbb')
return 2
print('ccc')
return 3
func()#此处就只会返回 aaa
三.函数参数的使用
函数的参数分为两大类
1.形参:在定义函数是括号内制定的参数(变量名),称之为形参
2.实参:在调用函数时括号内传入的值(变量值),称之为实参
二者的关系:在调用函数时,实参数(变量值)会传给形参(变量名),这种绑定关系在调用函数时生效,调用结束后解除绑定。
函数参数详解
1.形参:
1.1 位置形参:在定义阶段,按照从左到右的顺序依次定义的形参
特点:必须被传值
def func(x,y,z):
print(x,y,z)
func(1,2,3)#这边需要注意的是必须一一对应,对一个少一个都不可以.
1.2 默认参数:在定义阶段,就已经为莫格形参赋值该形参称之为默认形参
特点:在定义阶段就已经有值,意味着调用阶段可以不用为其传值
注意点:位置形参必须放在默认形参的前面
def func(x,y=2):#要注意这边绝不可以是 func(x=2,y)
print(x,y)
func(1)#这边输出的就是 1 2
func(1,3333333)#这边输出的就是 1 3333333
1.3 形参中*与**的用法
形参中带*: *会将溢出的位置实参存在元组的形式然后赋值给其后变量名
def func(x,y,*args):
print(x,y,args)
func(1,2,3,4,5,6)#输出 1 2 (3, 4, 5, 6)
形参中带* *: **会将溢出的关键字实参存成字典的格式然后赋值给其后变量名
def func(x,y,**kwargs):
print(x,y,kwargs)
func(1,y=2,z=3,m=1,n=2)#输出 1 2 {'z': 3, 'm': 1, 'n': 2}
2:实参
2.1 位置实参:在调用阶段,按照从左到右的顺序依次传入的值
特点:这种传值方式会与形参一一对应 #就是上面所说的形参一一对应
2.2 关键字实参: 在调用阶段,按照key=value的格式传值
特点:可以完全打乱位置,但仍然能为指定的形参传值
注意:可以混用关键字实参与位置实参,但是 1.同一个形参只能被赋值一次
2.位置实参必须跟在关键字实参的前面
2.3 实参中*与**的用法
实参中带*:先将实参打散成位置参数,然后在与形参做对应
def func(x,y,z):
print(x,y,z)
func(*[1,2,3]) #func(1,2,3)
func(*'hel') #func('h','e','l')
实参中带**: 先将实参打散成关键字实参,然后在与形参做对应
def func(x,y,z):
print(x,y,z)
func(**{'x':1,'z':3,'y':2}) #func(z=3,y=2,x=1)
四.函数对象
就是把函数的内存地址当作一种变量值去使用
那么具体体现在哪
1.函数可以被引用
x=10
y=x #这是我们所熟悉的变量值的使用
def func1():
print('from func')
# func1=函数的内址
func2=func1
func2()#这边就是可以把函数的内存地址当作变量值使用
2.函数可以作为函数的参数
def func1():
print('from func')
def bar(xxx):
print(xxx)
xxx()
bar(func1)#此时就是把func这函数当成xxx传入bar函数,同时这个xxx()就相当于func(),也就相当于函数的引用.此时输出的的就是 func的内存地址 与 from func
3.函数可以作为函数的返回值
def func1():
print('from func')
def bar():
return func1
f=bar()
f()#这里就是func函数作为bar函数的返回值
4可以被存储到容器类型中,也就是当作容器类型的元素
def func1():
print('from func')
l=[func1,]
print(l)
l[0]()
这边是一个具有一些小功能的简易购物车,函数下面就可以加上你想要写的功能
def register():
print('注册')
def login():
print('登陆')
def shopping():
print('购物')
def pay():
print('支付')
func_dic={
'1':register,
'2':login,
'3':shopping,
'4':pay
}
while True:
print("""
0 退出
1 注册
2 登陆
3 购物
4 支付
""")
choice=input('>>>: ').strip()
if choice == '0':break
if choice in func_dic:
func_dic[choice]()
else:
print('输入错误的指令')
五.函数的嵌套
1.函数嵌套定义:它主要就是在一个函数中定义了另外一个函数。
要注意的是:定义在函数内的函数 只能在函数内使用 外界不能访问。
def outter():
def inner():
pass#这就是在oytter函数中定义了一个inner函数
2.函数的嵌套调用:提供了一种解决问题的思路,如果有一个大问题,可以细分成很多小问题,然后单独实现小问题,最后在大的函数中调用小函数,就比较容易解决问题
比如比较4个值的大小就可以
def max2(x,y):
if x > y:
return x
else:
return y
def max4(a,b,c,d):
res1=max2(a,b)
res2=max2(res1,c)
res3=max2(res2,d)
return res3
a=max4(1,2,3,4)
print(a)
六.名称空间与作用域
1.名称空间:就是存放名字与值内存地址绑定关系的地方
2.名称空间的分类
内置名称空间:存储解释器自带的一些名称与值的对应关系(python解释器启动时创建 所有代码全部执行完毕 关闭解释器时 销毁) print len max min 等等这些
全局名称空间:那些数据会存储在全局空间(执行py文件创建全局名称空间 关闭解释器时 销毁)
文件级别的文件 只要你的名字的定义是顶着最左边写的就在全局空间
除了内置的函数内的 都在全局中
局部名称空间:只要时函数内的名称就是局部的(调用函数时创建 函数执行完毕就销毁)
名称空间的加载顺序
内置的-->全局的-->局部的
名称的查找顺序
局部的--> 全局的-->内置的
总结:01.查找名字的顺序是从当前位置往外查找
02.名称关系的嵌套关系是在函数定义阶段就固定死的,与调用关系无关
def outter():
x=11111111
def inner():
print(x)
return inner
f=outter()
def foo():
x=222222222222
f()
foo()#此处输出的就是11111111,定义阶段就固定死了所以与foo()下面的x无关
3作用域(作用范围)
域 指的是区域 范围的的意思
全局的名称空间和内置的名称空间 在使用上没什么区别
局部的和全局的内置的 就区别了 局部定义的只能在局部使用
给三个空间划分范围
全局的和内置可以划分为同一个范围
global 表示的全局范围 就是所谓的全局作用域
局部的单独划分为一个范围
local 局部作用域
globals()
locals()
闭函数:该函数一定是定义在函数内的函数
包函数:爱函数包含对外层函数作用域名字的引用
age = 20
# 如下就是一个闭包函数
def fun1():
age = 18
def inner():
print("hello")
print(age)#这是对上面age进行了引用
# 在返回这个内部的函数时 不是单纯的返回函数 还把函数中访问到的局部名称一起打包了
# 相当于将内部函数与 访问的数据打包在一起了 闭包这个名字就是这么得来的
return inner
f = fun1() # f 就是innerr
f()
函数的作用域在定义时就固定了 与调用的位置没有关系
为函数传值有二种方案:
1 直接传参
def f(x):
print(x)
f(10)
f(11)
2 闭包函数形势下传参
def outter(x):
def f():
print(x)
return f
f=outter(10)
f()
八.装饰器
1 装饰器指的是为被装饰对象添加新功能的工具
装饰器本身可以是任意可调用对象
被装饰对象本身也可以是任意可以调用对象
2 装饰器要遵循开放封闭原则(对修改封闭,对扩展开放)
装饰器的实现原则
01 不能修改被装饰对象的源代码
02 不能修改被装饰对象的调用方式
装饰器的目的
就是在遵循原则01和02的前提为被装饰对象添加新功能
装饰器的模板是
#不写这个也完全没有问题,只是更加严谨一点
from functools import wraps#导入装饰器,为wraper添加新功能,使被装饰对象的一些内置属性赋值给wrapper
def outter(func):
#加在内层函数最上方
@wraps(func)#是func下面所有的杠杠开头(_name_ ,_doc_等)的赋值给wrapper
def wrapper(*args,**kwargs):
res=func(*args,**kwargs)
return res
return wrapper
统计运行时间装饰器
import time
def outter(func):
# func=最原始那个index的内存地址
def wrapper(*args,**kwargs):
start=time.time()
res=func(*args,**kwargs) #最原始那个index的内存地址()
stop=time.time()
print('run time is %s' %(stop -start))
return res
return wrapper
#装饰器的语法糖,相当于index=outter(index)
@outter #index=outter(index) #inex=outter(最原始那个index的内存地址) # index=wrapper的内存地址
def index():
time.sleep(1)
print('welcome to index page')
return 1234
@outter #home=outter(home)
def home(name):
time.sleep(1)
print('welcome %s to home page' %name)
res=index() #wrapper的内存地址()
# print('返回值',res)
home('egon')
认证功能装饰器
import time
def auth(func):
def wrapper(*args,**kwargs):
name=input('name>>>: ').strip()
pwd=input('pwd>>>: ').strip()
if name == 'egon' and pwd == '123':
print('login successfull')
res=func(*args,**kwargs)
return res
else:
print('user or pwd error')
return wrapper
@auth
def index():
time.sleep(1)
print('welcome to index page')
return 1234
res=index()
print(res)
3.叠加多个装饰器
01.加载顺序(outter函数的调用顺序):自下而上
02.执行顺序(wrapper函数的执行顺序):自上而下
def outter1(func1): #func1=wrapper2的内存地址
print('加载了outter1')
def wrapper1(*args,**kwargs):
print('执行了wrapper1')
res1=func1(*args,**kwargs)
return res1
return wrapper1
def outter2(func2): #func2=wrapper3的内存地址
print('加载了outter2')
def wrapper2(*args,**kwargs): 噩
print('执行了wrapper2')
res2=func2(*args,**kwargs)
return res2
return wrapper2
def outter3(func3): # func3=最原始的那个index的内存地址
print('加载了outter3')
def wrapper3(*args,**kwargs):
print('执行了wrapper3')
res3=func3(*args,**kwargs)
return res3
return wrapper3
@outter1 # outter1(wrapper2的内存地址)======>index=wrapper1的内存地址
@outter2 # outter2(wrapper3的内存地址)======>wrapper2的内存地址
@outter3 # outter3(最原始的那个index的内存地址)===>wrapper3的内存地址
def index():
print('from index')
print('======================================================')
index()
'''
加载了outter3
加载了outter2
加载了outter1
======================================================
执行了wrapper1
执行了wrapper2
执行了wrapper3
from index
'''
4.有参装饰器
有参认证功能装饰器加使用
import time
current_user={'user':None}#定义一个当前的文件名
def auth(engine='file'):
def outter(func):
def wrapper(*args,**kwargs):
if current_user['user'] is not None:#判断当前文件名字是不是为空,如果不是空就没必要在进行认证了
res=func(*args,**kwargs)
return res
user=input('username>>>: ').strip()
pwd=input('password>>>: ').strip()
if engine == 'file':
# 基于文件的认证
if user == 'egon' and pwd == '123':
print('login successfull')
current_user['user']=user#认证之后它会存到当前文件去
res=func(*args,**kwargs)
return res
else:
print('user or password error')
elif engine == 'mysql':
# 基于mysql的认证
print('基于mysql的认证')
elif engine == 'ldap':
# 基于ldap的认证
print('基于ldap的认证')
else:
print('不知道engine')
return wrapper
return outter
@auth('ldap') #@outter #index=outter(index) # index=wrapper
def index():
time.sleep(1)
print('from index')
@auth('mysql') #@outter # home=outter(home) #home=wrapper
def home(name):
print('welcome %s' %name)
index()
home('egon')
就像通常我们比较2个值大小
def max2(x,y):
if x > y:
return x
else:
return y
res=max2(10,20)
print(res)
现在直接可以用
res=x if x > y else y
print(res)
二.生成式
优点:方便,改变了编程习惯,可称之为声明式编程
#列表生成式
l=[]
for i in range(10):
if i > 4:
l.append(i**2)#我们一开始用的方法
l=[i**2 for i in range(10) if i > 4]#列表生成式
print(l)
# 字典生成式
res={i:i**2 for i in range(10) if i > 3}
print(res)
三.匿名函数
它主要是之定义了一个函数的内存地址,主要用于临时使用一次的场景
func=lambda x,y:x+y
print(func)#<function <lambda> at 0x0000028897231E18>
print(func(1,2))#3
res=(lambda x,y:x+y)(1,2)
print(res)#3
l=[4,2,3]
l_new=sorted(l)#默认从小到大排序
#l_new=sorted(l,reverse=True)#默认从大到小排序
print(l_new)
四.迭代器和生成器
1.什么是迭代?
迭代是一个重复的过程,但是每次重复都是基于上一次重复的结果而继续
#下列循环知识单纯的重复
while True:
print(1)
# 基于索引的迭代取值
l=['a','b','c']
i=0
while i < len(l):
print(l[i])
i+=1
迭代器就是迭代取值的工具
2.为什么要用迭代器
优点:01 提供一种不依赖索引的迭代取值方法
02 更节省内存
缺点:01 不如按照索引的取值方式灵活
02 取值一次性的,只能往后取,无法预测值的个数
3.如何用迭代器
可迭代的对象:str\list\tuple\dict\set\文件对象
但凡内置有iter方法的对象都称之为可迭代对象
迭代器对象:文件处理
即内置有iter方法又内置有next方法的对象都称之为 迭代器对象
调用可迭代对象下iter方法,会有一个返回值,该返回值就是内置的迭代器对象
字典的迭代器,不依赖索引取值
d={'k1':111,'k2':222,'k3':333}
iter_d=d.__iter__()
try:#检测代码
print(iter_d.__next__())
print(iter_d.__next__())
print(iter_d.__next__())
print(iter_d.__next__())#一个一个的取值,并不知道里面又几个值,所以要try来盯这段代码
except StopIteration:#捕捉异常,结束循环
print('取值完毕')
d={'k1':111,'k2':222,'k3':333}
# d={1,2,3,4,5}
# d=[1,2,3,4]
iter_d=d.__iter__()#把可迭代对象装换为迭代器对象
while True:
try:
v=iter_d.__next__()#需要去掉迭代器对象__next__
print(v)
except StopIteration:#需要自己检测迭代器异常
break
#所以这就比较麻烦
所以这里用到for循环
for k in d:
print(k)
for循环的底层原理:
1. 调用in后面那个值/对象的__iter__方法,拿到一个迭代器对象iter_obj
2. 调用迭代器对象iter_obj.__next__()将得到的返回值赋值变量名k,循环往复直到取值完毕抛出异常StopIteration
3. 捕捉异常结束循环
4.生成器
01 生成器其实就是一种自定义的迭代器
如何得到生成器?
但凡函数内出现yield关键字,再去调用函数不会立即执行函数体代码,会得到一个返回值,该返回值就是生成器对象,即自定义的迭代器.
def func():
print('first')
yield 1
print('second')
yield 2
print('third')
yield 3
g=func()
res1=next(g)
print(res1)#第一次打印,然后暂停
res2=next(g)
print(res2)#第二次打印,然后暂停
res3=next(g)
print(res3)#第三次打印,然后暂停
next(g)#没有值会出现StopIteration
5.生成器表达式
这个和列表表达式不一样的就是将中括号改为小括号
g=(i for i in range(10) if i > 5)
print(next(g))#这里需要注意的是一次只能打印一个数就会暂停,由于是生成器
读取文件中字符的个数
with open('a.txt',mode='rt',encoding='utf-8') as f:
#print(len(f.read()))#这样一次性全读出来占用内存
#res=sum[len(line) for line in f]#行数过多的话还是会占用内存
res = sum(len(line) for line in f)#生成器里面是不是值,所以不论多少并不占用内存
print(res)
小练习:
自己生成一个my_range,使之跟默认range有相同的效果
def my_range(start,stop,step=1):
while start < stop:
yield start
start+=step
#range(1,5,2) # 1 3
for i in my_range(1,5,2):
print(i)
1.函数的递归调用:
在调用一个函数的过程又直接或者间接地调用该函数本身,称之为递归调用
递归调用必须满足2个条件:
01 每进入下一次递归调用,问题的规模都应该有所减少
02 递归必须又一个明确的结束条件
递归有两个明确的阶段
01 回溯
02 递推
就比方说想知道一个人的年龄的话,问他他说比另外一个人大2岁,以此往复,直到第五个人说出了自己的年龄18岁,那么我们可以知道
age(5)=age(4)+2
age(4)=age(3)+2
age(3)=age(2)+2
age(2)=age(1)+2
age(1)=18#那么这个过程中重上往下推理的过程就叫回溯,然后拿到最后一个人的年龄推导出第一个的年龄就叫逆推
age(n)=age(n-1)+2 # n > 1
age(1)=18 # n = 1
def age(n):
if n == 1:
return 18
return age(n-1)+2
print(age(5))
#这边是一个列表,要求将当中的所有值都取出来
#递归函数的运用
l=[1,[2,[3,[4,[5,[6,[7,[8,[9,]]]]]]]]]
def func(list1):
for item in list1:#循环从list1里面取值
if type(item) is not list:#判定是不是列表,不是的话打印
print(item)
else:
# 如果是列表,应该继续走这个函数,直到全部取空这个列表
func(item)
func(l)
2.二分法(算法)
比如有这样nums=[3,5,7,11,13,23,24,76,103,111,201,202,250,303,341]一个列表,需要取判断一个值在不在这个列表当中
比如我们find_num=202,最开始想到的就是取出这个了列表中所有的值,看有没有和202相等的就可以了
for num in nums:
if num == find_num:
print('find it')
break
else:
print('not exists')
#find it
#这样就可以发现202是在这个列表中的
但是这样你会发现要是这个列表里面的元素非常多,这样会很占用内存
我们可以想象是不是可以从中间开始找呢,一直切2部分找,这样是不是更合理一点嗯
nums=[3,5,7,11,13,23,24,76,103,111,201,202,250,303,341]
def binary_search(list1,find_num):先定义一个binary_search函数
print(list1)
if len(list1) == 0:判断一下列表是不是空列表,列表一直中间切片,如果到空列表还没找到的话,就不存在
print('not exist')
return
mid_index=len(list1) // 2#取出中间的一个数等待和你想找的值比大小
if find_num > list1[mid_index]:#如果找的值比中间的值大的话,那么就可以从中间这个值向后切片重新分割寻找
binary_search(list1[mid_index + 1:],find_num)
elif find_num < list1[mid_index]:#同上,如果小于则反之
binary_search(list1[:mid_index],find_num)
else:
print('find it')#如果刚好等于的话就直接找到这个值了
binary_search(nums,203)