函数的定义和调用
函数能提高应用的模块性,和代码的重复利用率,学习了函数,你就可以让你的代码不在那么死板,更加结构化和过程化。那么如何创建一个函数呢?这是函数的语法格式:
def 函数名(参数列表):
函数体
在使用函数的时候你需要遵守以下几点:
函数代码块以def 关键词开头,后接函数标识符名称(函数名字)和圆括号 ()。
任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
函数必须调用才能执行,方法:函数名(参数列表)。
return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
我们创建一个简单的函数:
def fun():
"""
在pycharm中这是自动生成的,用来解释函数的说明
:return:
"""
pass
上面的就是一个简单的空函数且没有return表达式,返回值就是None。我们运行这个程序也不会报错,什么也没有做,显然它没有什么用处而言。我们往下接着学习更有趣的东西。我们重新定义一个函数,它接受一个参数并且有return表达式:
#定义一个函数
def Function(name):
print('hello world! %s '% name)
return 'Welcome to www.e1yu.com'
#运行函数
Function('鳄鱼君'+'\n----')
print(Function('鳄鱼君'))
# hello world! 鳄鱼君
# ----
# hello world! 鳄鱼君
# Welcome to www.e1yu.com
函数必须调用才能执行,调用方法就是函数名字+(参数列表),定义函数的时候有几个参数,调用的时候就需要传入对应值的参数,否则就会抛出TypeError。我们可以看到上面的代码单单执行函数是不会打印return语句的内容的,使用print方法打印函数,两者都会打印出来。那么return表达式的存在就是为了给函数加上返回值的,关于返回值你需要知道:
返回值数=0,就是没有return,返回None
返回值数=1,return一个值,返回object
返回值数>1,return多个值,返回tuple元组
返回值可以是函数,return function
函数的返回值是为了让我们更清楚函数的执行结果
函数的实参和形参
对于函数来说,形式参数简称形参,不是实际存在的,是虚拟变量。在定义函数和函数体的时候使用形参,目的是在函数调用时接受实际参数,实参与形参是一一对应的。
对于函数来说,实际参数简称实参。是指在调用函数时传入的实际的数据,这会被绑定到函数的形参上,可以是常量、变量、表达式、函数。
形参和实参两者的区别:形参是虚拟的,不占用内存空间,形参变量只有在被调用时才分配内存单元;实参是一个变量,占用内存空间,数据传送单向,实参传给形参,不能形参传给实参。简单一句话理解:定义函数时的参数为形参,调用函数时的参数为实参。我们看一个简单的例子:
def fun(a,b): #形参
"返回多个值,结果以元组形式表示"
return a,b,a+b
fun(1,2)#实参
#实参和形参必须一一对应
函数的位置参数
基于实参的顺序,调用函数时的参数,是按照先后顺序依次赋值给函数的:
#计算x的n次方
def fun(x,n):
s=1
while n>0:
n=n-1
s=s*x
return s
print(fun(2,3)) #2d3次方
print(fun(3,2)) #3的2次方
函数的默认参数
定义函数时的形参有默认值,你如过看到过Python的一些模块的话,会经常看到函数这样定义:def func(x, maxsize=0, loop=None),这就是默认参数,我们来看一个具体的列子:
#计算x的2次方 或者x的n次方
def fun(x,n=2): #n默认值=2
s=1
while n>0:
n=n-1
s=s*x
return s
print(fun(2)) #2的2次方 n有默认值为2,可以不传入n值
print(fun(2,3)) #2d3次方 #如果传入n值,就更新
默认参数必须指向不变对象!列表不能用作默认参数!
函数的可变参数
可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个,可变参数在函数调用时自动组装为一个元组tuple。我们来看一个列子:
def fun(*num):
sum=0
print(num) #是一个元组
print(type(num))
for i in num:
sum=sum+i*i
return sum
print(fun(1,2,3))
在函数内部,参数num接收到的是一个tuple,Python允许在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去:
tu=(1,2,3)
print(fun(*tu))
或者
tu=[1,2,3]
print(fun(*tu))
如果要让函数接受不同类型的实参,必须在函数定义中将接纳任意数量实参的形参放在最后。python先匹配位置实参和关键字实参,再将剩下的实参都收集到最后一个形参中:
def func(n,*args):
print('接收的位置参数为:%s'% n)
print('接收的可变参数为:', args)
func('eyujun','18','gender')
# 接收的位置参数为:eyujun
# 接收的可变参数为: ('18', 'gender')
函数的关键字实参
关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。它可以扩展函数的功能,让函数可以接受任意个参数!例子:
def fun(name,gender,**kwargs):
return {"name":name,"gender":gender,"other":kwargs}
print(fun('鳄鱼君','男'))
print(fun('鳄鱼君','男',age='18',city='河南'))
和可变参数一样,也可以先定义一个dict,然后,把该dict转换为关键字参数传进去:
def fun(name,gender,**kwargs):
return {"name":name,"gender":gender,"other":kwargs}
dic={'hobby':'Ball','weight':'120kg'}
print(fun('鳄鱼君','男',**dic))
**dic表示把dic所有key-value用关键字参数传入到函数的**kwargs参数,kwargs将获得一个dict,注意kwargs获得的dict是dic的一份拷贝,对kwargs的改动不会影响到函数外的dic。
def build_profile(name, age, **user_info):
"""创建一个字典,其中包含我们知道的有关用户的一切"""
profile = {}
profile['name'] = name
profile['age'] = age
for key, value in user_info.items():
profile[key] = value
return profile
user_profile = build_profile('eyujun', '18',
location='henan',
field='python')
print(user_profile)
函数的命名关键字参数
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如下:
def fun(name,gender,*,hobby,weight):
return (name,gender,hobby,weight)
dic={'hobby':'Ball','weight':'120kg'}
print(fun('鳄鱼君','男',hobby='Ball',weight='120kg'))
和关键字参数**kwargs不同,命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数。如果单独出现星号, * 后的参数必须用关键字传入。错误的代码不在进行演示
函数的参数组合
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是需要注意参数的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数,否则就会报错!
def fun(name,age=18,**kwargs): #关键字参数必须要放到参数组前面,否则报错
print(name,age,kwargs)
fun3('zjj',salary=12000)
fun3('zjj',33,salary=12000,age=18) #会报错,age的值给了两个
#给默认参数赋值,可以使用位置参数,关键字参数赋值
def fun2(name,age=18,*args,**kwargs):
print(name,age,args,kwargs)
fun4('zjj',19,salary=120000) #这样传值会保存,*args接受位置参数,第三个为关键字参数,它不会接收,返回空tuple
函数的强制位置参数
/用来指明函数形参必须使用指定位置参数,不能使用关键字参数的形式,可以结合命名关键字参数理解。
def f(a, b, /, c, d, *, e, f): #前面说过*单独出现,e,f为关键字参数
print(a, b, c, d, e, f)
#调用函数
f(10, 20, 30, d=40, e=50, f=60)
#形参 a 和 b 必须使用指定位置参数,c 或 d 可以是位置形参或关键字形参
f(10, b=20, c=30, d=40, e=50, f=60) # b 不能使用关键字参数的形式
f(10, 20, 30, 40, 50, f=60) # e 必须使用关键字参数的形式
全局变量和局部变量
在子程序(函数)中定义的变量是局部变量,在程序的一开始定义的变量称为全局变量。全局变量的作用域是整个程序,局部变量的作用域是定义该变量的子程序。当全局变量与局部变量重名:在定义局部变量的子程序内,局部变量起作用,在其它地方全局变量起作用
你可以简单理解为,函数内部定义的变量为局部变量,在函数外部定义的变量就是全局变量;全局变量在函数内部修改值,它只会在函数内部修改,在函数外部,也就是全局中,不会被改变。所以说在很多的函数中,我们可以定义相同的变量名字,他不会影响其他函数中的变量,也不会影响全局中的变量。
def fun1():
a=1
return a
def fun2():
a=2
return a
print(fun1())
print(fun2())
对于变量作用域,变量的访问以 L(Local) –> E(Enclosing) –> G(Global) –>B(Built-in) 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。
x = int(3.3) #. 内建作用域
g = 0 #全局作用域
def outer():
o = 1 #闭包函数外的函数中
def inner():
i = 2 #局部作用域
print(x)
inner()
outer()
#局部打印x,会从里向外找,有就打印,没有再往外找
函数内可以访问全局变量,但不能更新(修改)其值!
a = 10
def sum ( n ) :
n += a
a = 11 #不能更新(修改)其值,会报错
print ('a = ', a, end = ' , ' )
print ( 'n = ', n )
sum(3)
a = 10
def sum ( n ) :
global a #加上 global 引用以更新变量值
n += a
a = 11
print ('a = ', a, end = ' , ' )
print ( 'n = ', n )
sum ( 3 )
print ( '外 a = ', a ) #a值更新为11
def fun1(name):
print('名字改变前:',name)
name='ZJJ' #我在函数内部修改了name值:局部变量
print('名字改变后:',name) #函数内部打印改变的值,是修改过的
name='zjj'
fun1(name)
print(name) #我们在函数调用后打印名字,是没有修改过的
在函数内部修改外部的全局变量,可以采用下面的方法:
school='清华大学'
def fun1(name='zjj'):
global school #定义变量为全局变量
school='北京大学' #这样就直接把全局变量修改了
return school
f=fun1()
print(f)
特殊情况
name=['zjj','wff','xxk']
def name_change():
name[0]='zgp' #在函数里面修改name
print('name is change:%s'% name) #返回结果是修改完之后的
name_change()
print(name)
注意:
全局变量如果是字符串、数字,在函数内部是不可修改的
全局变量如果是列表(List)、字典(Dict)、集合(Set)、类(Class),在函数内部是可以修改的
元组(Tuple)是不可改变的,不管在哪都是的
一般情况下,我们是在函数内部把变量修改为全局变量,这就存在一种情况,我们创建函数是因为它可以多次调用,假设函数很多,而我们在调用一个函数时,你已经把局部变量修改为全局变量,如果过程出了错误,你就可能找不到错误的原因,程序非常乱,所以尽量不要乱用。
递归函数介绍
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。在高中的时候我们接触到过阶乘:n! = 1 x 2 x 3 x ... x n,我们来定义一个函数处理任意数的阶乘:
def fun1(n):
if n==1:
return 1
return n * fun1(n-1)
print(fun1(5))
递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
def fun1(num,):
return fun2(num,1)
def fun2(num,prodect):
if num==1:
return prodect
return fun2(num-1,num*prodect)
print(fun1(5))
可以看到,return fun2(num – 1, num * product)仅返回递归函数本身,num – 1和num * product在函数调用前就会被计算,不影响函数调用。如果不理解两个函数之间的计算方式,可以使用断点调试一步一步查看具体的效果。
尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fun1(n)函数改成尾递归方式,也会导致栈溢出。
– END –
未经允许不得转载:作者:鳄鱼君,
转载或复制请以 超链接形式 并注明出处 鳄鱼君。
原文地址:《Python中如何创建函数以及详细的使用教程 各种常用函数总结》 发布于2019-11-08