函数
一. 函数的定义与调用
1.1 函数的定义
函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
函数能提高应用的模块性,和代码的重复利用率。你已经知道Python提供了许多内建函数,比如print()。但你也可以自己创建函数,这被叫做用户自定义函数。
定义一个函数
你可以定义一个由自己想要功能的函数,以下是简单的规则:
- 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()。
- 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
- 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
- 函数内容以冒号 : 起始,并且缩进。
- return [表达式] 结束函数,选择性地返回一个值给调用方,不带表达式的 return 相当于返回 None。
语法格式如下:
def 函数名(参数列表):
函数体
1.2 函数的调用
语法格式如下:
func() # 函数名 + 括号
下面我们简单实现几个函数实例并对齐进行调用:
程序示例:
1.打印一段文本
def hello() : #函数定义
print("Hello World!")
hello() #函数调用
运行结果:
Hello World
2.计算字符串长度
def my_len(): #函数定义
s1 = "hello world"
length = 0
for i in s1:
length = length+1
print(length)
my_len() #函数调用
运行结果:
11
3.判断一串数字是否为电话号码:
phone = '13345671234'
def func(phone): #函数定义
if phone.isdigit() and len(phone) == 11:
print('这是一个电话号码')
else:
print('这不是一个电话号码')
func(phone) #函数调用
运行结果:
这是一个电话号码
二 . 函数的返回值
关键字return的作用
1、返回一个值
2、终止一个函数的继续
程序实例:
def my_len(): # 函数名的定义
s1='hello world'
length=0
for i in s1:
length=length+1
return length # 函数的返回值
str_len=my_len() #函数的调用以及返回值的接收
print(str_len)
运行结果:
11
在没有返回值的时候:
1、不写return与写入return None的效果相同,返回的只都是None
2、只写一个return后面不加任何东西的时候与写return None的效果一样
返回多个值:
1.当用一个变量接收返回值的时候,收到的是一个元组。这是因为在python中把用逗号分割的 多个值认为是一个元组。
程序实例:
def func():
return "a" , "b" #返回多个值时接收到的是一个元组
c = func() # c接收的是一个元组
print(c)
运行结果:
('a', 'b')
2.当返回值有多个变量接收,那么返回值的个数应该和接收变量的个数完全一致。
程序实例:
#返回多个值,用多个变量接收(接收的变量数与返回值的个数要一致)
def func():
return "a" , "b" , "c"
d , e , f = func()
print(d , e , f)
def func():
return [1,2,3]
a , b , c = func() #列表和元组是可以解包的
print(a,b,c)
#返回的字典类型有点意外:
def func():
return {"name":"span"}
dic = func() #需要字典类型来接收,而不能直接用k,v,字典解包解出来的只是键
print(dic)
运行结果:
a b c
1 2 3
{'name': 'span'}
3. return还有一个特殊的用途,一旦执行到return,后面的语句就不在执行了(结束一个函数)。(和break类似但有区别,break是跳出循环,如果循环后有代码则继续执行。return是结束整个函数)
4.如果在函数中有多个return,只执行第一个return。
三. 函数的参数
以下是调用函数时可使用的正式参数类型:
- 必备参数
- 关键字参数
- 默认参数
- 不定长参数
3.1 必备参数
必备参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样。
程序示例1:
def printme(str):
print(str)
return
printme('hello world') #调用printme()函数,你必须传入一个参数,不然会出现语法错误:
运行结果:
hello world
程序示例2:
def my_sum(a,b):
res = a + b
return res
ret = my_sum(1,2) #有两个参数就该传两个参数,1对应a;2对应b
print(ret)
运行结果:
3
3.2 关键字参数
关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。
使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
程序示例1:
def printme(str):
print(str)
return
printme( str = "My string") #调用printme函数
运行结果:
My string
程序示例2:
def my_sum(a,b):
print(a,b)
res = a + b
return res
ret = my_sum(b=2,a=1) #注意此处与参数顺序无关
print(ret)
运行结果:
1 2
3
3.3 默认参数
是可以不传的参数,在不传参数的情况下可以使用默认值;如果传了,就会使用传的值
程序示例1:
def printinfo(name, age = 35):
print ("Name: ", name)
print ("Age ", age)
return
#调用printinfo函数
printinfo(age=50, name="miki") #传入age参数
printinfo(name="miki") #不传入age参数
运行结果:
Name: miki
Age 50
Name: miki
Age 35
程序示例2:
默认参数 魔性用法:默认参数尽量避免使用可变数据类型(默认参数的陷阱)
def func(L=[]): # 相当于在def之前先定义了一个str=[],再将str赋值给L,如果不传参数就共用这个数据类型
L.append(2)
print(L)
func()
func()
func()
func()
运行结果:
[2]
[2, 2]
[2, 2, 2]
[2, 2, 2, 2]
3.4 不定长参数
你可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数
语法如下:
def function_print(*args,**kwargs): # 传递不定长参数,即参数个数不固定
print(args)
print(kwargs)
function_print()
输出结果:
()
{}
代码分析:由输出结果可以看出来,第一个形参*args 是元组tuple类型,第二个形参**kwargs是字典dict类型.
1.*args的使用方法 :
a.形参 *args 类型是元组tuple,外部调用函数时传递的参数不论是整数还是BOOL值或者是字符串str,实际上传递的都是元组数据;
b.如果函数形参是不定长参数,外部调用函数传递多个参数时,默认按顺序实参匹配形参,剩余的参数全部作为(元组)不定长参数传递;
c.如果没有为函数的不定长参数传递参数,默认为空元组();
程序示例:
注意:这里代码要一块一块的运行
#普通形参
def function_print1(arg):
print("普通形参 : arg=",arg)
#不定长形参
def function_print2(*args):
print("不定长形参 : args=",args)
#普通形参 + 不定长形参
def function_print3(arg,*args):
print("普通形参 + 不定长形参 : arg=",arg)
print("普通形参 + 不定长形参 : args=",args)
function_print1(False)
function_print1("hello world")
print("***"*20)
function_print2(False)
function_print2("hello world")
print("***"*20)
function_print3(False) # 只为函数传递了一个形参,不定长参数的形参默认为空元组()
function_print3("hello world")
print("***"*20)
# 如果函数的形参是不定长参数,当外部调用函数传递多个参数时,默认按顺序匹配形参,剩余的参数全部作为不定长参数传递
function_print3(False,1,23,4,5)
function_print3("hello world",False,0,True,"python教程")
运行结果:
普通形参 : arg= False
普通形参 : arg= hello world
************************************************************
不定长形参 : args= (False,)
不定长形参 : args= ('hello world',)
************************************************************
普通形参 + 不定长形参 : arg= False
普通形参 + 不定长形参 : args= ()
普通形参 + 不定长形参 : arg= hello world
普通形参 + 不定长形参 : args= ()
************************************************************
普通形参 + 不定长形参 : arg= False
普通形参 + 不定长形参 : args= (1, 23, 4, 5)
普通形参 + 不定长形参 : arg= hello world
普通形参 + 不定长形参 : args= (False, 0, True, 'python教程')
2.**kwargs的使用方法
a.形参 **kwargs 类型是字典dict,函数外部调用函数传递参数时需要使用关键字参数,实参写法:a=1,b=2,c=False,d=”hello”;
b.如果函数形参是不定长参数,外部调用函数传递多个参数时,默认按顺序实参匹配形参,关键字参数全部作为(字典)不定长参数传递;
c.如果没有为函数的不定长参数传递参数,默认为空字典{};
程序示例:
注意:这里代码要一块一块的运行
#普通函数
def function_print1(arg):
print("普通函数形参 : arg=",arg)
#普通函数不定长形参
def function_print2(**kwargs):
print("不定长形参 : args=",kwargs)
#普通函数形参 + 不定长形参
def function_print3(arg,**kwargs):
print("普通函数形参 + 不定长形参 : arg=",arg)
print("普通函数形参 + 不定长形参 : args=",kwargs)
function_print1(False)
function_print1("hello world")
print("***"*20)
function_print2(a=False)
function_print2(c="hello world")
print("***"*20)
function_print3(False)
function_print3("hello world")
print("***"*20)
function_print3(False,a=1,b=23,h=4,v=5)
function_print3("hello world",y=False,i=0,a=True,j="python教程")
运行结果:
普通函数形参 : arg= False
普通函数形参 : arg= hello world
************************************************************
不定长形参 : args= {'a': False}
不定长形参 : args= {'c': 'hello world'}
************************************************************
普通函数形参 + 不定长形参 : arg= False
普通函数形参 + 不定长形参 : args= {}
普通函数形参 + 不定长形参 : arg= hello world
普通函数形参 + 不定长形参 : args= {}
************************************************************
普通函数形参 + 不定长形参 : arg= False
普通函数形参 + 不定长形参 : args= {'a': 1, 'b': 23, 'h': 4, 'v': 5}
普通函数形参 + 不定长形参 : arg= hello world
普通函数形参 + 不定长形参 : args= {'y': False, 'i': 0, 'a': True, 'j': 'python教程'}
3.函数不定长参数args和kwargs只能放在形参的末尾,顺序不能错.*
def function_print(arg1,*args,**kwargs): # *args,**kwargs 必须在形参的末尾,顺序不能乱
pass
四. 函数的作用域
4.1 作用域介绍
python中的作用域分4种情况:
- L:local,局部作用域,即函数中定义的变量;
- E: enclosing,父级函数的局部作用域,即此函数的上级函数的局部作用域。
- G: globa,全局变量
- B: build-in,系统模块,如:int,max函数等
优先级顺序为:局部作用域(L) > 父级函数作用域(E) > 全局作用域(G) > 系统模块(B)
4.2 全局作用域
如果想要在函数内部改变外面不可变对象变量的值,则需要在函数内部使用global关键字
global是全局变量声明,声明之后可以在全局使用,这里的全局指的是当前py文件中
注意:函数内外名字要一致,否则就是一个新变量
程序示例:
variable = 223
def fun3():
global variable
variable += 1
return variable
print(fun3())
print(variable)
运行结果:
224
224
4.3 局部作用域
程序示例:
def f4():
var1 = 10
def func5():
nonlocal var1
var1 += 1
print('***',var1)
return var1
func5()
var1 += 2
print(var1)
return var1
f4()
运行结果:
*** 11
13
4.4 闭包函数
1.必须嵌套函数。
2.内嵌函数必须引用一个定义在闭合范围内(外部函数里)的变量——内部函数引用外部变量
3.外部函数必须返回内嵌函数——必须返回那个内部函数
闭包的作用:可以保持程序上一次运行后的状态然后继续执行。
程序示例:
# 闭包传参
def foo():
num=1
def add(n):
nonlocal num
num += n
return num
return add
f=foo()
print(f(1)) #2
print(f(2)) #4
运行结果:
2
4
五. 递归函数和匿名函数
5.1 递归函数
5.1.1 概念
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
递归函数特性:
- 必须有一个明确的结束条件;
- 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
- 相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入)。
- 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
5.1.2 代码实例
# 循环方式
def sum_cycle(n):
sum = 0
for i in range(1,n+1) :
sum += i
print(sum)
# 递归方式
def sum_recu(n):
if n>0:
return n +sum_recu(n-1)
else:
return 0
sum_cycle(100)
sum = sum_recu(100)
print(sum)
运行结果:
5050
5050
递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
把上面的递归求和函数的参数改成10000就导致栈溢出!
RecursionError: maximum recursion depth exceeded in comparison
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的
一般递归
def normal_recursion(n):
if n == 1:
return 1
else:
return n + normal_recursion(n-1)
执行过程:
normal_recursion(5)
5 + normal_recursion(4)
5 + 4 + normal_recursion(3)
5 + 4 + 3 + normal_recursion(2)
5 + 4 + 3 + 2 + normal_recursion(1)
5 + 4 + 3 + 3
5 + 4 + 6
5 + 10
15
可以看到, 一般递归, 每一级递归都需要调用函数, 会创建新的栈,随着递归深度的增加, 创建的栈越来越多, 造成爆栈:boom
尾递归
尾递归基于函数的尾调用, 每一级调用直接返回函数的返回值更新调用栈,而不用创建新的调用栈, 类似迭代的实现, 时间和空间上均优化了一般递归!
def tail_recursion(n, total=0):
if n == 0:
return total
else:
return tail_recursion(n-1, total+n)
执行过程:
tail_recursion(5)
tail_recursion(4, 5)
tail_recursion(3, 9)
tail_recursion(2, 12)
tail_recursion(1, 14)
tail_recursion(0, 15)
15
5.2 匿名函数
5.2.1 匿名函数的概念
在定义函数的时候,不想给函数起一个名字。这个时候就可以用lambda来定义一个匿名函数。
语法:
lambda 参数:表达式
注意:
- 表达式中不能包含 循环,return
- 可以包含 if…else…语句.
- 表达式计算的结果直接返回
5.2.2 匿名函数与普通函数的对比
程序示例:
def sum_func(a, b, c):
return a + b + c
sum_lambda = lambda a, b, c: a + b + c
print(sum_func(1, 100, 10000))
print(sum_lambda(1, 100, 10000))
运行结果:
10101
10101
可以看到,lambda适用于多个参数、一个返回值的情况,可以用一个变量来接收,变量是一个函数对象,执行这个函数对象的结果与执行一个普通函数的结果一样。
lambda函数比普通函数更简洁,且没有声明函数名,上面的代码是用一个变量来接收lambda函数返回的函数对象,并不是lambda函数的名字。
5.2.3 匿名函数的多种形式
# 无参数
lambda_a = lambda: 100
print(lambda_a())
# 一个参数
lambda_b = lambda num: num * 10
print(lambda_b(5))
# 多个参数
lambda_c = lambda a, b, c, d: a + b + c + d
print(lambda_c(1, 2, 3, 4))
# 表达式分支
lambda_d = lambda x: x if x % 2 == 0 else x + 1
print(lambda_d(6))
print(lambda_d(7))
运行结果:
100
50
10
6
8
可以看到,lambda的参数可以0个到多个,并且返回的表达式可以是一个复杂的表达式,只要最后的值是一个值就行了。
5.2.4 lambda作为一个参数传递
def sub_func(a, b, func):
print('a =', a)
print('b =', b)
print('a - b =', func(a, b))
sub_func(100, 1, lambda a, b: a - b)
运行结果:
a = 100
b = 1
a - b = 99
上面的代码中,sub_func中需要传入一个函数,然后这个函数在sub_func里面执行,这时候我们就可以使用lambda函数,因为lambda就是一个函数对象。
5.2.5 lambda作为函数的返回值
def run_func(a, b):
return lambda c: a + b + c
return_func = run_func(1, 10000)
print(return_func)
print(return_func(100))
运行结果:
<function run_func.<locals>.<lambda> at 0x00000254E4C94158>
10101
匿名函数可以作为一个函数的返回值,在上面的代码中,run_func返回的是一个匿名函数,返回的是一个函数对象,当我们执行这个函数时,可以得到lambda函数的结果。
六. 作业
本节作业:
1、一个列表由四个元组组成,每个元组都是四个数字组成,现在要求对这个列表排序,排序规则是按照每个元组的第二个元素大小排序
2、实现isPrime()函数,参数是整数,如果整数是质数,返回True,否则返回False
3、利用递归函数,实现一个阶乘函数,支持正数和负数的阶乘
4、定义一个函数,输入字符串,如果字符串是顺序的则返回‘UP’,如果是倒序的则返回‘DOWN’, 如果是乱序的则返回False
答案下节公布
上节答案:
1、找出两个列表中相同元素,并打印出来
li1 = [1,1,3,'b','a','c',9,9,8]
li2 = ['a','b','c','d',1,3,5,7,9]
li_one = set(li1)
li_two = set(li2)
li3 = list(li_one & li_two)
print(li3)
运行结果:
[1, 3, 'c', 9, 'a', 'b']
2、统计一串字符中,每个字母 a~z的出现次数,忽略大小写
st = input('请输入一段字符串:')
st1 = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z']
st2 = {}
for i in st1:
if i in st:
st2.update({i:st.count(i)})
## print(f'{i}出现的次数为{st.count(i)}')
print(st2)
for key,value in st2.items():
print(f'{key}出现的次数为:{value}')
运行结果:
请输入一段字符串:abcdeffgasgdddduekfe
{'a': 2, 'b': 1, 'c': 1, 'd': 5, 'e': 3, 'f': 3, 'g': 2, 'k': 1, 's': 1, 'u': 1}
a出现的次数为:2
b出现的次数为:1
c出现的次数为:1
d出现的次数为:5
e出现的次数为:3
f出现的次数为:3
g出现的次数为:2
k出现的次数为:1
s出现的次数为:1
u出现的次数为:1
3、利用26个字母和10个数字,随机生成10个8位密码
import random
a = 'abcdefghigklmnopqrstuvwxyz1234567890'
for i in range(10):
b = random.choices(a,k=8)
print(f'第{i+1}个8位密码为:',''.join(b))
运行结果:
第1个8位密码为: aiw3xg4e
第2个8位密码为: 36xbyd6d
第3个8位密码为: i3y99h6n
第4个8位密码为: giaa8tfg
第5个8位密码为: qx53lowx
第6个8位密码为: l5tlz6kr
第7个8位密码为: 753cepvd
第8个8位密码为: u7yko1xm
第9个8位密码为: shib8qoo
第10个8位密码为: h2h8r5zg
4、判断用户输入的是不是一个手机号码,并且是不是以13开头的手机号
num = input('请输入您的手机号:')
if len(num) == 11 and num.isdigit() == True and num[:2] == '13':
print('是13开头的手机号')
else:
print('不是13开头的号')
运行结果:
请输入您的手机号:13836542389
是13开头的手机号