一、初识
1.为什么使用函数
有一个需求,让你计算'hello world'的长度,你怎么计算?
这个需求对于现在的你其实不难,我们一起来写一下。
s1 = "hello world"length=0for i ins1:
length= length+1
print(length)
好了,功能实现了。但现在又有了需求,要计算另一个字符串的长度,"hello ryxiong".
s1 = "hello world"length=0for i ins1:
length= length+1
print(length)
s2= "hello ryxiong"length=0for i ins2:
length= length+1
print(length)
for循环实现len功能2
这样可以实现功能,但是如果有成百上千个字符串,难道我要一个一个写一段代码去计算吗,显然不现实,这时候就可以使用定义函数来帮我们完成某一特定的功能。
定义函数后,无论有多少个字符串, 我们只要往函数里扔,就可以拿到他的长度了,下面看看如何定义和调用函数完成特定功能。
2.函数的概念
数学层面
一般的,在一个变化过程中,如果有两个变量x和y,并且对于x的每一个确定的值,y都有唯一确定的值与其对应,那么我们就把x称为自变量,把y称为因变量,y是x的函数。自变量x的取值范围叫做这个函数的定义域。
python中函数
函数是逻辑结构化和过程化的一种编程方法,就是一段有特定功能的代码的封装.
函数的作用
封装某一特定功能,组织代码结构
减少重复代码和冗余,提高可读性
便于扩展,管理和维护
3.函数的定义
函数的定义
def函数名(参数1,参数2,参数3,...):'''注释'''函数体return返回的值#函数名要能反映其意义
定义一个计算字符串长度的函数
defmylen():"""计算s1的长度"""s1= "hello world"length=0for i ins1:
length= length+1
returnlength#函数调用
ret =mylen()print(ret)
len函数定义
注意:函数在定义时,只检测语法,不执行代码,会将函数体加载到内存中。
函数的调用
函数调用方式:函数名+调用操作符()
如果函数中带参数,调用时需要传入参数。
函数需要先定义才能调用
deffoo():print('from foo')
foo()#调用函数
函数的调用
小结
函数定义
def 关键词开头,空格之后接函数名称和圆括号(),最后还有一个":"。
def 是固定的,不能变,必须是连续的def三个字母。
空格:为了将def关键字和函数名分开,必须空格(四声),当然你可以空2格、3格或者你想空多少都行,但正常人还是空1格。
函数名:函数名只能包含字符串、下划线和数字且不能以数字开头。虽然函数名可以随便起,但我们给函数起名字还是要尽量简短,并能表达函数功能
括号:是必须加的,先别问为啥要有括号,总之加上括号就对了!
注释:每一个函数都应该对功能和参数进行相应的说明,应该写在函数下面第一行。以增强代码的可读性。
函数调用:就是 函数名() 要记得加上括号
4.函数的参数
到了这里,我们知道了函数如何返回结果,也就是返回值,但是能不能将计算的字符串变成可动态控制的呢,这就需要用到函数的参数来完成。
函数参数
带参数的函数
#函数定义
defmylen(s1):"""计算s1的长度"""length=0for i ins1:
length= length+1
print(length)#函数调用
str_len = mylen("hello world")print('str_len : %s'%str_len)
这个告诉函数要计算字符串是谁的过程就叫做传递参数,简称传参。
实参和形参
我们调用函数时传递的这个“hello world”被称为实际参数,因为这个是实际的要交给函数的内容,简称实参。
而定义函数时的s1,只是一个变量的名字,被称为形式参数,在定义函数的时候它只是一个形式,表示这里有一个参数,简称形参。
形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。函数调用结束返回主调用函数后则不能再使用该形参变
实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使参数获得确定值
参数的分类
从实参角度:
位置参数:arg
动态位置参数:[1,2,3,4]
关键字参数:arg=True
动态关键字参数:{arg1:1, arg2:2...}
传参顺序:位置参数 > 动态位置参数 > 关键字参数 > 动态关键字参数
从形参角度:
位置参数:arg
动态位置参数:*args
默认参数 :arg=True
动态关键字参数:**args
接收顺序:位置参数 > 动态关键字参数 > 默认参数 > 动态关键字参数
动态位置参数
按位置传值多余的参数都由args统一接收,保存成一个元组的形式
def mysum(*args): #动态接受位置参数
the_sum =0for i inargs:
the_sum+=ireturnthe_sum
the_sum= mysum(1,2,3,4)print(the_sum)
动态关键字参数
def stu_info(**kwargs): #动态接受关键字参数
print(kwargs)print(kwargs['name'],kwargs['sex'])
stu_info(name= 'alex',sex = 'male')
5.函数的返回值
我们使用函数,需要获得函数给我们计算的结果,像刚才的函数,我们并不能拿到返回值,因为我们并没有让函数给我们返回任何值,此时需要用到return给函数返回结果。
return关键字
return的作用
用来终止函数,只要遇到return,函数后面所有的语句都不再执行,无论后面有几个return,也只运行到第一个return
返回函数运行的结果
deftest():print(1)print(2)return '函数结束了'
print(3)
test()#函数运行,执行里面的语句
ret = test() #用ret接收函数的返回值
print(test())print(ret)
函数中有return
返回一个值
#函数定义
defmylen():"""计算s1的长度"""s1= "hello world"length=0for i ins1:
length= length+1
returnlength#函数调用
str_len =mylen()print('str_len : %s'%str_len)
返回一个值
返回多个值,会放入一个元组中返回
defret_demo1():'''返回多个值'''
return 1,2,3,4
defret_demo2():'''返回多个任意类型的值'''
return 1,['a','b'],3,4ret1=ret_demo1()print(ret1)
ret2=ret_demo2()print(ret2)
返回多个值
返回多个值的接收
defret_demo2():return 1,['a','b'],3,4
#返回多个值,用一个变量接收
ret2 =ret_demo2()print(ret2)#用多个值接收返回值:返回几个值,就用几个变量接收
a,b,c,d =ret_demo2()print(a,b,c,d)
多个返回值的接收
函数中没有return
函数会默认给我们返回None
#函数定义
defmylen():"""计算s1的长度"""s1= "hello world"length=0for i ins1:
length= length+1
print(length)#函数调用
str_len =mylen()#因为没有返回值,此时的str_len为None
print('str_len : %s'%str_len)
不写return
二、函数进阶
1.函数命名空间
defmy_max(x,y):
m= x if x>y elseyreturnm
bigger= my_max(10,20)print(bigger)
当我们运行代码的时候,报错了!错误是“name 'm' is not defined”。变量m没有被定义,为什么?
在这里我们首先回忆一下python代码运行的时候遇到函数是怎么做的?
命名空间由来
从python解释器开始执行之后,就在内存中开辟了一个空间,每当遇到一个变量的时候,就把变量名和值之间的对应关系记录下来。
但是当遇到函数定义的时候解释器只是象征性的将函数名读入内存,表示知道这个函数的存在了,至于函数内部的变量和逻辑解释器根本不关心。
等执行到函数调用的时候,python解释器会再开辟另一块内存空间来存储这个函数里的代码内容,这个时候,程序才关注函数里面有哪些变量,而函数中的变量会存储在新开辟出来的内存中。函数中的变量只能在函数的内部使用,并且会随着函数执行完毕,这块内存中的所有内容也会被清空。
我们给这个“存放名字与值的关系”的空间起了一个名字——叫做命名空间。
全局命名空间和局部命名空间
代码在运行伊始,创建的存储“变量名与值的关系”的空间叫做全局命名空间,在函数的运行中开辟的临时的空间叫做局部命名空间
命名空间的本质:存放名字与值的绑定关系
命名空间分类
内置命名空间:python解释器整个环境,比如python解释器为我们提供的名字:input,print,str,list,tuple...它们都是我们熟悉的,拿过来就可以用的方法。
内置命名空间:目前来说是一个py文件没有缩进的空间
局部命名空间:python函数内部空间
加载顺序
内置命名空间(程序运行前加载)->全局命名空间(程序运行中:从上到下加载)->局部命名空间(程序运行中:调用时才加载)
调用顺序
在局部调用:局部命名空间->全局命名空间->内置命名空间
x = 1
deff(x):print(x)print(10)
在全局调用:全局命名空间->内置命名空间
x = 1
deff(x):print(x)
f(10)print(x)
2.函数作用域
相关概念
什么是作用域
1.作用域就是作用范围,按照生效范围可以分为全局作用域和局部作用域。
2.作用域在定义函数时就已经固定住了,不会随着调用位置的改变而改变
作用域分类
全局作用域:包含内置名称空间、全局名称空间,在整个文件的任意位置都能被引用、全局有效
局部作用域:局部名称空间,只能在局部范围内生效
全局变量和局部变量
全局变量:在程序的一开始没有缩进的地方定义的变量称为全局变量。作用域是整个程序
局部变量:在程序或子程序中定义的变量称为局部变量,作用域是该程序和其子程序内部
global关键字
在函数内部(局部作用域)引用全局变量,如果全局没有,则会创建一个全局变量。
如果函数中有global关键字,变量本质上就是全局的那个变量,可读取可赋值。
如果函数的内容无global关键字,优先读取局部变量,无法对全局变量重新赋值,但是对于可变类型,可以对内部元素进行操作
deffunc():global a #没有全局变量a,直接定义一个全局变量a
a = 3func()print(a) #3 打印的是定义的全局变量的a
count= 1
defsearch():global count #申明count是全局中的count变量
count = 2search()print(count) #2 打印的是被改过的全局变量
nonlocal关键字
不能修改全局变量
在局部作用域中,对最近的父级作用域(或者更外层作用域非全局作用域)的变量进行引用和修改,并且引用的哪层,从那层及以下此变量全部发生改变。
如果整个局部作用域里面没有找到nonlocal申明的变量,则会报错。
a = 1
deffunc():
a= 2
print(a)deffunc1():
nonlocal a#引用父级的变量a,此时a=2
a += 1
print(a)
func1()print(a) # 还是全局变量a
func()#2 3 1
3.函数的嵌套
函数的嵌套调用
函数内部中调用函数就是函数的嵌套调用
defmax1(x,y):
m= x if x>y elseyreturnmdefmax2(a,b,c,d):
res1= max1(a,b) #max2中调用max1函数
res2 =max1(res1,c)
res3=max1(res2,d)returnres3#max2(23,-7,31,11)
函数的嵌套定义
函数内部中定义函数就是函数的嵌套定义
deff1():deff2():deff3():print("in f3")print("in f2")
f3()print("in f1")
f2()
f1()
4.函数名的使用
函数名本质上就是一个变量,指向了函数体的内存地址。
函数名的使用
可以当做变量被引用
deffunc():print('in func')
f=funcprint(f)
可以被当作容器类型的元素
deff1():print('f1')deff2():print('f2')deff3():print('f3')
l=[f1,f2,f3]
d= {'f1':f1,'f2':f2,'f3':f3}#调用
l[0]()
d['f2']()
当做容器的元素
可以当作函数的参数和返回值
#当做参数传递
deffoo():print('来自函数foo')deffoo1(func):
func()
foo1(foo)
当参数传递
#当做返回值返回
deffoo():deffoo1():print('from foo1')returnfoo1
a=foo()
a()
当返回值返回
5.闭包
什么是闭包
闭包函数:嵌套函数中内部函数包含对外部作用域而非全剧作用域变量的引用,该内部函数称为闭包函数。
函数内部定义的函数称为内部函数
实例!
deffunc():
name= 'ryxiong'
def inner(): #这就是个闭包函数
print(name)
闭包函数的意义
闭包:返回函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,使得该函数无论在何处调用,优先使用自己外层包裹的作用域。
我们都知道函数内的变量我们要想在函数外部用,可以直接返回这个变量,那么如果我们想在函数外部调用函数内部的函数呢?
是不是直接就把这个函数的名字返回就好了?
这才是闭包函数最常用的用法
deffunc():
name= 'ryxiong'
definner():print(name)returninner
f=func()
f()#返回的函数 f 附带了一层作用域,可以使用作用域中的变量
判断闭包函数的方法__closure__
#输出的__closure__有cell元素 :是闭包函数
deffunc():
name= 'ryxiong'
definner():print(name)print(inner.__closure__)returninner
f=func()
f()#输出的__closure__为None :不是闭包函数
name = 'anni'
deffunc2():definner():print(name)print(inner.__closure__)returninner
f2=func2()
f2()
判断是否为闭包函数