函数,简单地讲就是一段可以重复使用的代码段,给这段代码起个名字就是“函数名”。在程序的任何地方都可以通过函数名来使用这段代码,这就是“函数调用”。
一、定义函数
1.在Python中可以定义一个自己想要功能的函数,以下是简单的规则:
A、函数代码块以def关键字开头,后接函数名、圆括号()、圆括号中的参数和冒号:,然后在缩进块中编写函数体,函数的返回值用return语句返回。
B、任何传入参数和自变量必须放在圆括号中间,圆括号之间用于定义参数。
C、(可选)函数第一行语句可以选择性使用文档字符串------用于存放函数说明
D、return[表达式]结束函数(即函数内部return后面的语句将不再执行),选择性地返回一个值给调用方。不带表达式的return相当于返回None,函数可以返回一个或多个值,多个值以tuple的形式返回。
返回值作用:后续程序需要根据被调用函数的返回值来执行不同的操作。
2.语法
Python中定义函数一般格式如下:
def 函数名(参数列表):
函数体
默认情况下,参数值和参数名称是按函数声明中定义的顺序匹配起来的。
3.实例
# -*- coding:utf-8 -*-
#一元二次方程求根
import math
def quadratic(a,b,c):
#参数检验,如果传入错误的参数类型,函数可以抛出一个错误
if not isinstance(a,(int,float)):
raise TypeError("a is not a number")
if not isinstance(b,(int,float)):
raise TypeError("a is not a number")
if not isinstance(c,(int,float)):
raise TypeError("a is not a number")
if a == 0:
if b == 0:
if c == 0:
return "方程根为全体实数"
else:
return "方程不成立!"
else:
x = -c / b
return "方程根为x=%s" %x
else:
k = b * b - 4 * a * c
if k >0:
x1 = (-b + math.sqrt(k)) / (2 * a)
x2 = (-b - math.sqrt(k)) / (2 * a)
return "方程根为x1=%s ,x2=%s "%(x1,x2)
elif k == 0:
x1 = x2 = (-b + math.sqrt(k)) / (2 * a)
return "方程根为x1=x2=%s" %x1
else:
return "方程无根"
a = float(input("请输入二次项系数a:"))
b = float(input("请输入一次项系数b:"))
c = float(input("请输入常数项c:"))
print(quadratic(a,b,c))
二、函数的参数
Python函数除了正常定义位置参数外,还可以使用默认参数、可变参数、关键字参数和命名关键字参数,使得函数定义出来的接口不但能处理复杂参数,还可以简化调用者的代码。
1.位置参数
#位置参数例子,计算x的n次方
def power(x,n):
s = 1
while n > 0:
n -= 1
s = s * x
return s
print(power(5,2)) #25
power(x,n)函数有两个参数:x和n,这两个参数都是位置参数,调用函数时,传入的两个值按照位置顺序依次赋给参数x和n。
2.默认参数
#默认参数例子,计算x的n次方
def power(x,n=2):
s = 1
while n > 0:
n -= 1
s = s * x
return s
print(power(5)) #25
print(power(3,3)) #27
默认参数可以简化函数调用,设置默认参数时,有几点需要注意:
①必选参数在前,默认参数在后,否则Python解释器会报错。
②如何设置默认参数?当有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。使用默认参数最大好处就是可以降低调用函数的难度。
③默认参数必须指向不变对象!
#默认参数示例
def info(id,name,grade=60,gender="M"):
print("id= ",id)
print("name= ",name)
print("grade= ",grade)
print("gender= ",gender)
info("001","zhangsan") #使用默认参数
info("002","liming",gender="F",grade=90) #不按顺序提供部分默认参数,需要把参数名写上
info(grade=80,id="003",gender="F",name="Tom") #参数顺序打乱,但不影响最终输出结果
3.可变参数
顾名思义,可变参数就是传入的参数个数是可变的,可以是0个、1个、2个甚至任意个。举个例子,给定一组数字a、b、c......,要求计算+
+
+......。要定义这个函数,就需要确定输入的参数,可是参数个数不确定,怎么办?这个时候就用到了可变参数,把函数的参数改为可变参数。
#可变参数示例
def calc(*numbers):
sum = 0
for n in numbers:
sum += n * n
return sum
print(calc()) #0
print(calc(1,2,3)) #14
观察上述代码可以发现,仅仅在参数前面加了一个 * 号 。在函数内部,参数numbers接收到的是一个元组,因此函数代码部分完全不用改动。如果已经有一个列表或者元组,要调用一个可变参数怎么做?可以这样:
L = [1,2,3]
calc(L[0],L[1],L[2])
但是这样感觉太繁琐,有没有简单的写法?python允许在元组或者列表前面加一个 * 号,把列表或元组的元素变为可变参数传进去。 *L表示把L这个列表的所有元素作为可变参数传进去,这种写法很常用。
L = [1,2,3]
calc(*L)
4.关键字参数
可变参数允许我们传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个元组。而关键字参数允许我们传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个字典。
#关键字参数示例
def info (id,name,**kw):
print('id=',id,'name=',name,'other=',kw)
info("001","zhangsan") #只传入必选参数
#id= 001 name= zhangsan other= {}
info("002","lisi",age=34,gender="M",grade=98) #除必选参数外,传入任意个关键字参数。关键字参数跟默认参数类似有参数位置限制,关键字参数后面必须都是关键字参数
#id= 002 name= lisi other= {'age': 34, 'gender': 'M', 'grade': 98}
观察上述代码发现info()除了必选参数id,name外,还多了一个关键字参数kw。关键字参数有何作用?它可以扩展函数功能。比如info()函数里,除了可以收到id和name参数外,还可以收到其他可选参数如gender、grade等。如果有一个字典,要调用关键字参数应该怎么做?可以这样:
D = {'age':24,'gender':'F','grade':90}
info("003","Jake",**D)
#id= 003 name= Jake other= {'age': 24, 'gender': 'F', 'grade': 90}
**D可以把D这个字典的所有key-value用关键字参数传入到**kw参数,kw将获得一个字典。注意kw获得的是D的一份拷贝,对kw的改动不会影响到D本身。
5.命名关键字参数
如果要限制关键字参数的名字,就可以使用命名关键字参数。如下示例,除id,name以外只接收city作为关键字参数,代码如下:
#命名关键字参数示例
def info(id,name,*,city):
print(id,name,city)
#info('001','Mark',gender='M',grade=98,city='xian') #报错,除id,name外只接收city
info('001','Mark',city='xian') #001 Mark xian
观察上述示例可以发现,和关键字参数**kw
不同,命名关键字参数需要一个特殊分隔符 *
,*
后面的参数被视为命名关键字参数。如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符 *
了:
#已有可变参数示例
def info(id,name,*args,gender,city):
print(id,name,args,gender,city)
#命名关键字参数必须传入参数名,如果没有传入参数名,调用将会报错
info('002','Jack','IT','student','M','shanghai')#报错
#命名关键字参数传入正确参数名
info('002','Jack','IT','student',gender='M',city='shanghai')#可变参数于位置参数后,否则会报错
#结果: 002 Jack ('IT', 'student') M shanghai
命名关键字参数可以有默认值,从而简化调用:
#命名关键字参数取默认值
def info(id,name,*args,gender,city='Shanghai'):
print(id,name,args,gender,city)
info('002','Jack','IT','student',gender='M')
#002 Jack ('IT', 'student') M Shanghai
注意:① 命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错。
②使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个*
作为特殊分隔符。如果缺少*
,Python解释器将无法识别位置参数和命名关键字参数。
6.参数组合
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数可以组合使用。参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
#参数组合
def f1(a,b,c=0,*args,**kw):
print('a=',a,'b=',b,'c=',c,'args=',args,'kw=',kw)
def f2(a,b,c=0,*,d,**kw):
print('a=',a,'b=',b,'c=',c,'d=',d,'kw=',kw)
f1(1,2)#a= 1 b= 2 c= 0 args= () kw= {}
f1(1,2,c=3) #a= 1 b= 2 c= 3 args= () kw= {}
f1(1,2,3,'a','b') #a= 1 b= 2 c= 3 args= ('a', 'b') kw= {}
f1(1,2,3,'a','b',x=100) #a= 1 b= 2 c= 3 args= ('a', 'b') kw= {'x': 100}
f2(1,2,d=99,ext=None) #a= 1 b= 2 c= 0 d= 99 kw= {'ext': None}
#对于任意函数,都可以通过类似func(*args,**kw)形式调用,无论其参数是如何定义的
L = (1,2,3,4)
s = (1,2,3)
D = {'d':99,'x':'y'}
f1(*L,**D) #a= 1 b= 2 c= 3 args= (4,) kw= {'d': 99, 'x': 'y'}
f2(*s,**D) #a= 1 b= 2 c= 3 d= 99 kw= {'x': 'y'}
参数总结:
①默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误
②注意可变参数和关键字参数的语法:*args是可变参数,args接收的是一个元组;**kw是关键字参数,kw接收的是一个字典。
③调用函数时如何传入可变参数和关键字参数语法:
a、可变参数既可以直接传入:func(1,2,3),又可以先组装list或tuple,再通过*args传入:func(*(1,2,3));
b、关键字参数既可以直接传入:func(a=1,b=1),又可以先组装dict,再通过**kw传入:func(**{'a':1,'b':2})。
④使用*args和**kw是Python的习惯用法,最好使用习惯用法。
⑤命名关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
⑥定义命名关键字参数在没有可变参数情况下不要忘了写分隔符*,否则定义的将是位置参数。
⑦必选参数、默认参数、可变参数、命名关键字参数和关键字参数可以组合使用,参数定义顺序为必选参数-->默认参数-->可变参数-->命名关键字参数-->关键字参数
三、递归函数
#递归求n阶乘
def fact(n):
if n == 1: #跳出递归条件
return 1
return n * fact(n-1)
print(fact(1)) #1
print(fact(5)) #120
#汉诺塔
'''
move(n,a,b,c)各个参数含义:n表a,b,c三个柱子中第一个柱子a上面盘子数量,然后打印所有盘子从a借助b移动到c的方法.
思路:move(n,起点,缓冲区,终点),先把n-1个盘子搬到缓冲区move(n-1,a,c,b),然后把最底下那个大盘子搬到终点move(1,a,b,c),
最后缓冲区变为起点,起点变为缓冲区,其余的盘子搬到终点move(n-1,b,a,c)
'''
def move(n,a,b,c):
if n == 1:
print(a,'-->',c)
else:
move(n-1,a,c,b)
move(1,a,b,c)
move(n-1,b,a,c)
move(1,'A','B','C') #A --> C
move(2,'A','B','C') #A --> B,A --> C,B --> C
move(3,'A','B','C') #A --> C,A --> B,C --> B,A --> C,B --> A,B --> C,A --> C
递归优点:定义简单,逻辑清晰。缺点:过深调用导致栈溢出,解决递归调用栈溢出方法是通过尾递归优化。
递归特性:
A:必须有一个明确的结束条件
B:每次进入更深一层递归时,问题规模相比上次递归都应有所减少。
C:递归效率不高,递归层次过多会导致栈溢出。
四、匿名函数
Python使用lambda来创建匿名函数。匿名,即不再使用def这样的标准形式定义函数。
A、lambda只是一个表达式,函数体比def简单的多,不用写return,返回值就是该表达式的结果。
B、lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限逻辑。
C、lambda函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
D、虽然lambda函数看起来只有一行,却不等同于C或C++的内联函数。
语法
lambda函数的语法只包含一个语句,格式如下,其中冒号前面[arg1 [,arg2,.....argn]]表示函数参数。
lambda [arg1 [,arg2,.....argn]]:expression
#匿名函数
sum = lambda x,y:x + y
print("两数相加和为:",sum(1,2)) #两数相加和为: 3
五、变量作用域
变量作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python作用域一共有4种,分别是:
L (Local) 局部作用域
E (Enclosing) 闭包函数外的函数中
G (Global) 全局作用域
B (Built-in) 内置作用域(内置函数所在模块的范围)
以 L –> E –> G –>B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内置中找。内置作用域是通过一个名为builtin的标准模块实现的,但是这个变量名自身没有放入内置作用域内,所以必须导入这个文件才能使用它import builtins.
g_count = 0 #全局作用域
def outer():
o_count = 1 #闭包函数外的函数中
def inner():
i_count = 2 #局部作用域
1.全局变量和局部变量
定义在函数内部的变量拥有一个局部作用域,定义在函数外部的拥有全局作用域。局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。
#局部变量与全局变量
total = 0 #全局变量
def sum(x,y):
total = x + y #total为局部变量
print("这里是函数内部局部变量:",total)
return total
sum(10,20) #这里是函数内部局部变量: 30
print("这里是函数外部全局变量:",total) #这里是函数外部全局变量: 0
2.global和nonlocal关键字
当内部作用域想修改外部作用域时,就要用到global和nonlocal关键字了。
#修改全局变量--global
num = 1
def fun1():
global num #global关键字声明,否则打印num会报错
print(num)
num = 10
print(num)
fun1() #1,10
print(num) #10
#修改嵌套作用域(enclosing作用域,外层非全局作用域)中的变量--nonlocal
def outer():
num = 1
def inner():
nonlocal num #nonlocal关键字
print(num) #1
num = 10
print(num) #10
inner()
print(num) #10
outer()