1.函数定义与调用基本函数
- 函数定义语法
def 函数名([参数列表]):
...注释...
函数体
- 注意事项
- 函数形参不需要声明类型,也不需要指定函数返回值类型
- 即使该函数不需要接受任何参数,也必须保留一对空的圆括号
- 括号后面的冒号必不可少
- 函数体相对def关键字必须保持一定的空格缩进
- Python允许嵌套自定义函数
- 例:编写生成的斐波那契数列的函数并调用
def test(n): # def定义头 n是形参
a,b =1,1
while a<n:
print(a,end=' ')
a,b=b,a+b
print()
test(1000) # test(1000)调用函数 1000是实参
2.函数修饰器
- 修饰器(decorator)是函数嵌套定义的一个重要应用。修饰器本质上也是一个函数,只不过这个函数接受其他函数作为参数并对其进行一定的改造之后使用新函数替换原来的函数。
- Python面向对象程序设计中的静态方法、类方法、属性等也都是通过修饰器实现的。
- 例:
def before(func): #定义修饰器
def wrapper(*args,**kwargs):
print('Before function called.')
return func(*args,**kwargs)
return wrapper
def after(func): #定义修饰器
def wrapper(*args,**kwargs):
result =func(*args,**kwargs)
print('After function called.')
return result
return wrapper
@before
@after #同时使用两个修饰器改造函数
def test():
print(3)
test() #调用被修饰的函数
*args表示可以接受任意多个位置参数并将其放在一个tuple 元组中
**kwargs表示可以接受任意多个关键参数并存放到一个dict 字典中
(位置参数和关键参数下面有讲)
测试代码如下:
def aha(*args,**kwargs):
print ('args=',args)
print ('kwargs=',kwargs)
print('--------------------------')
aha(1,2,3)
aha(a=1,b=2,c=3)
aha(1,2,3,a=1,b=2,c=3)
aha(1,'b','c',a=1,b='b',c='c')
输出结果:
args= (1, 2, 3) #是一个元组
kwargs= {}
--------------------------
args= ()
kwargs= {'a': 1, 'b': 2, 'c': 3} #是一个字典
--------------------------
args= (1, 2, 3)
kwargs= {'a': 1, 'b': 2, 'c': 3}
--------------------------
args= (1, 'b', 'c')
kwargs= {'a': 1, 'b': 'b', 'c': 'c'}
--------------------------
3.函数的参数(定义函数时不需要声明参数类型 解释器会根据实参类型自动推断形参类型)
函数的参数分为四个类型:
- 位置参数
位置参数是比较常用的形式,调用函数时实参和形参的顺序必须严格一致,并且实参和形参的数量必须相同。
>>>def demo(a,b,c):
print(a,b,c)
>>>demo(1,2,3) #按位置传递参数
1,2,3
>>>demo(1,2,3,4) #实参与形参数量必须相同
xxxxxxxxxxxx(报错)xxxxxxxxxxxx
- 默认值参数
带有默认值参数的函数定义语法如下:
def 函数名(_,形参名=默认值):
函数体
在调用带有默认值参数的函数时,可以不用为设置了默认值的形参进行传值,此时函数将会直接使用函数定义时设置的默认值,当然也可以通过显式赋值来替换默认值。在调用函数时是否为默认值参数传递实参是可选的。
需要注意的是,在定义带有默认值参数的函数时,任何一个默认值参数右边都不能再出现没有默认值的普通位置参数,否则会提示语法错误。
代码测试:
def demo(a,b,c=2): #c是带有默认值的参数
print(a,b,c)
demo(1,2) #传递两个实参 函数将使用c参数的默认值
输出:
1 2 2
代码测试:
def demo(a,b,c=2): #c是带有默认值的参数
print(a,b,c)
demo(1,2,4) #传递三个实参 函数将使用传递过去的c参数的值
输出:
1 2 4
- 关键参数
通过关键参数可以按照参数名字传递值,明确指定哪个值传递给哪个参数,实参顺序可以和形参顺序不一致,避免了用户需要牢记参数位置和顺序的麻烦,使得函数的调用和参数传递更加灵活方便
代码测试:
def demo(a,b,c=2):
print(a,b,c)
demo(a=1,b=2,c=4)
输出:
1 2 4
代码测试:
def demo(a,b,c=2):
print(a,b,c)
demo(c=1,a=2,b=4) #实参a b c打乱顺序
输出:
2 4 1
- 可变长度参数
可变长度参数主要有两种形式:在参数名前加一个*或两个**
- *parameter用来接受多个位置参数并将其放在一个元组中
代码测试1:
def demo(*p):
print(p)
demo(1,2,3) #三个实参
输出1:
(1, 2, 3) #输出一个元组
代码测试2:
def demo(*p):
print(p)
demo(1,2) #两个实参
输出2:
(1, 2)
- **parameter用来接受多个关键字参数并存放到字典中
代码测试1:
def demo(**p):
print(p)
demo(x=1,y=2)
输出1:
{'x': 1, 'y': 2} #输出一个集合
代码测试2:
def demo(**p):
for item in p.items():
print(item)
demo(x=1,y=2)
输出2:
('x', 1)
('y', 2)
4.变量作用域
变量起作用的代码范围称为变量的作用域,不同作用域内变量名可以相同,互不影响。
在函数内部定义的普通变量只在函数内部起作用,称为局部变量。当函数执行结束后,局部变量自动删除,不再可以使用。
局部变量的引用比全局变量速度快,应优先考虑使用。
- 全局变量可以通过关键字global来定义。分为两种情况:
- 一个变量已在函数外定义,如果在函数内需要为这个变量赋值,并要将这个赋值结果反映到函数外,可以在函数内使用global将其声明为全局变量。
- 如果一个变量在函数外没有定义,在函数内部也可以直接将一个变量定义为全局变量,该函数执行后,将增加一个新的全局变量。
测试代码1:
x=4
def demo():
global x
x=3
y=8
print(x,y)
demo()
print(x)
print(y)
输出1:
3 8
3
NameError: name 'y' is not defined
注意:在某个作用域内任意位置只要有为变量赋值的操作,该变量在这个作用域内就是局部变量,除非使用global进行了声明。
测试代码2:
x=4 #x是全局变量
def demo():
print(x) #因为下面一行声明x是局部变量要先声明后引用 故该行在还未声明x时要输出x的操作是错误的
x=2 #有赋值操作 且没有使用global 因此在整个作用域内x都是局部变量
print(x)
demo()
输出会报错 (x在未声明之前被引用了)
5.lambda表达式
lambda表达式可以用来声明匿名函数,也就是没有函数名字的临时使用的小函数,尤其适合需要一个函数作为另一个函数参数饿的场合,也可以定义具名函数。
lambda表达式只可以包含一个表达式,该表达式的计算结果可以看作是函数的返回值,不允许包含复合语句,但在表达式中可以调用其他函数。
测试代码1:
f=lambda a ,b , c: a + b +c #可以给lambda表达式起名字
print(f(1,2,4)) #像函数一样调用
输出1:
7
测试代码2:
f=lambda a ,b=9 , c=4: a + b +c #参数默认值
print(f(1))
print(f(2, b=3, c=8)) #关键参数
输出2:
14
13
6.生成器函数设计要点
- 包含yield语句的函数可以用来创建生成器对象,这样的函数又叫生成器函数
- yield语句和return语句的作用相似,都是用来从函数中返回值。与return语句不同的是,return语句一旦执行会立刻结束函数的运行,而每次执行到yield语句宁返回一个值后,会暂停或挂起后面代码的执行,下次通过生成器对象的_next_()方法、内置函数next()、for循环遍历生成器对象元素或其他方法显式“索要”数据时回复执行
- 生成器具有惰性求值的特点,适合大数据处理
例题:斐波那契数列
函数:
def f():
a, b=1, 1 #序列解包 同时为多个元素赋值
while True:
yield a #暂停执行 需要时再产生一个新元素
a, b = b,a+b #序列解包 继续生成新元素
测试代码1:
a =f() #创建生成器对象
for i in range(10): #取斐波那契数列中的前10个元素
print(a.__next__(),end=" ")
输出1:
1 1 2 3 5 8 13 21 34 55
测试代码2:
for i in f(): #取斐波那契数列中第一个大于100的元素
if(i>100):
print(i,end=" ")
break
输出2:
144
测试代码3:
a=f() #创建生成器对象
print(next(a)) #使用内置函数next()获取生成器对象中的元素
print(next(a)) #每次获取新元素时 由yield语句生成
print(a.__next__()) #也可以调用生成器对象的__next__()方法
print(a.__next__())
输出3:
1
1
2
3
7.经典例题
- 编写函数模拟猜数游戏 系统随机产生一个数,玩家最多可以猜5次,系统会根据玩家的猜测进行提示,玩家则可以根据系统的提示对下一次的猜测进行适当调整。
from random import randint
def guess(maxValue=100,maxTimes=5):
value = randint (1,maxValue)
for i in range(maxTimes):
prompt='Start to Guess:' if i == 0 else 'Guess again:' #如果i=0 prompt为'Start to Guess:' 如果i!=0 prompt为'Guess again:'
try:
x=int(input(prompt))
except:
print('Must input an integer between 1 and ',maxValue) #如果输入的数转为整型失败
else:
if x==value:
print('Congratulations!')
break
elif x>value:
print('Too big')
else :
print('Too little')
else:
print('Game over .FAIL')
print('The value is ',value)
guess()
- 汉诺塔问题(三根柱子ABC num个盘子从下往上 由大到小放在A柱子上 要求一次只能移动一个盘子 每根柱子上的盘子都必须按照从下往上 由大到小放置 求将所有的盘子从A盘移动到C盘的过程)
def hannoi(num,src,dst,temp=None): #num有多少盘子 src最初放盘子的柱子 dsr目标柱子 temp临时柱子
#声明用来记录移动次数的变量为全局变量
global times
#确认参数类型和范围
assert type(num)==int,'num must be integer'
assert num>0,'num must > 0'
#只剩下最后或只有一个盘子需要移动 这也是函数递归调用的结束条件
if num == 1:
print('The {0} Times move:{1}==>{2}'.format(times,src,dst))
times +=1
else:
#递归调用函数自身
#先把除最后一个盘子之外的所有盘子移动到临时柱子上
hannoi(num-1,src,temp,dst)
#把最后一个盘子直接移动到目标柱子上
hannoi(1,src,dst)
#把除最后一个盘子之外的其他盘子从临时柱子上移动到目标柱子上
hannoi(num-1,temp,dst,src)
#用来记录移动次数的变量
times=1
#A表示最初放置盘子的柱子 C目标柱子 B临时柱子
hannoi(3,'A','C','B')
输出:
The 1 Times move:A==>C
The 2 Times move:A==>B
The 3 Times move:C==>B
The 4 Times move:A==>C
The 5 Times move:B==>A
The 6 Times move:B==>C
The 7 Times move:A==>C