Python学习笔记12_函数
文章目录
- 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
- 函数能提高应用的模块性,和代码的重复利用率。
1、函数定义
- Python 定义函数使用 def 关键字,一般格式如下:
def 函数名(参数列表):
函数体
- 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ();
- 圆括号之间用于定义参数;
- 函数内容以冒号 : 起始,并且缩进;
- return [表达式] 结束函数,也可以没有返回值;
- 默认情况下,参数值和参数名称是按函数声明中定义的顺序匹配起来的。
# 返回两数中的较大者
def max(a, b):
if a > b:
return a
else:
return b
2、函数调用
- 调用一个函数, 直接使用 函数名+() 就可以
- 调用注意:函数必须先定义、后调用,否则会报错,函数中调用函数不受此限制
函数调用格式:
# 先定义函数
def max(a, b):
if a > b:
return a
else:
return b
# 再调用函数
maxValue=max(1,2)
3、函数的参数
- 从主调函数和被调函数之间数据传送角度可分为有参函数和无参函数
- 形式参数:在函数声明和定义时的参数,称为形式参数(简称形参)
- 实际参数:在函数调用时必须给出的参数,称为实际参数(简称实参)
# 函数定义
def add(a,b): # 此 a,b 就是形参
c = a + b
return c
# 调用
print(add(1,2)) # 1和2 为实际给出的故为实参
- Python 函数可使用的正式参数类型:
- 必需参数(位置参数)
- 关键字参数
- 默认参数(缺省参数)
- 不定长参数(可变参数)
3.1、可更改对象和不可更改对象参数
1)在 python 中,类型属于对象,对象有不同类型的区分,变量是没有类型的:
a=[1,2,3]
a="Python"
[1,2,3] 是 List 类型,“Python” 是 String 类型,而变量 a 是没有类型,她仅仅是一个对象的引用(一个指针),可以是指向 List 类型对象,也可以是指向 String 类型对象。
2)在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。
- **不可更改类型:**变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变 a 的值,相当于新生成了 a。
- **可更改类型:**变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。
3)python 函数的参数,根据参数是否为可修改对象,函数的参数可以分为可更改对象参数和不可更改对象参数
- **不可更改类型参数:**类似 C++ 的值传递,如整数、字符串、元组。如 fun(a),传递的只是 a 的值,没有影响 a 对象本身。如果在 fun(a) 内部修改 a 的值,则是新生成一个 a 的对象,不会影响外部的 a 的值。
- **可更改类型参数:**类似 C++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后 fun 外部的 la 也会受影响。
不可更改类型参数:
def change(a):
print(id(a)) # 指向的是同一个对象
a=10
print(id(a)) # 一个新对象
a=1
print(id(a))
change(a)
'''
输出结果:
4379369136
4379369136
4379369424
'''
# 可以看出在调用函数前后,形参和实参指向的是同一个对象(对象 id 相同),在函数内部修改形参后,函数内部的a和外部的a的对象 id 不同了,表明此时函数内部的 a 是一个新的对象。
可更改类型参数:
def changeme( mylist ):
# 修改传入的列表
mylist.append([1,2,3,4])
print ("函数内取值: ", mylist)
return
# 调用changeme函数
mylist = [10,20,30]
changeme( mylist )
print ("函数外取值: ", mylist)
'''
输出结果:
函数内取值: [10, 20, 30, [1, 2, 3, 4]]
函数外取值: [10, 20, 30, [1, 2, 3, 4]]
'''
# 可变对象在函数里被修改了,在这个函数外部,对象内容也被修改了,表明函数内部和函数外部的 mylist 是同一个对象。
3.2、必需参数(位置参数)
- 必需参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样,顺序和数量都必须一致
# 定义函数,这里printme 函数只有一个参数,并且为必需参数
def printme( str ):
print (str)
return
# 调用函数,不加参数会报错
printme()
'''
程序报错:
Traceback (most recent call last):
File "test.py", line 10, in <module>
printme()
TypeError: printme() missing 1 required positional argument: 'str'
'''
3.3、关键字参数
- 关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。
- 使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
- 参数在传递时关键字参数需在所有必需参数后面, 必需参数则顺序传递, 关键字参数则不必按顺序。
def printinfo( name, age ):
print ("名字: ", name)
print ("年龄: ", age)
return
#调用printinfo函数
printinfo( age=50, name="python" )
'''
输出结果:
名字: python
年龄: 50
'''
def add(a,b,c,d): # 此 a,b 就是形参
sum = a + b + c + d
return sum
# 调用
print(add(1,2,d=4,c=3)) # 1,2为位置参数顺序传递给a,b;d=4,c=3为关键字参数,传递给对应形参d,c
# 注意:如果c为关键字参数,则之后的所有参数都必须为关键字参数
3.4、缺省参数
- 当用户给定参数内容时,则按用户给定的实参, 如果没有给定参数,则按默认的参数
# print 就是一个包含缺省参数的函数,sep 参数和 end 参数就有默认值,调用的时候若没有给定这两个参数值,默认用空格连接,结束为换行
print('hello','world',sep = '+',end = '') # 默认用空格连接,结束为换行
# 定义缺省参数, 直接在参数后面给定默认值即可
def introduce(name, age, monthly_income='1w'): # 缺省参数必须放在最后面
print('大家好,我是{},今年{}岁,月入{}元'.format(name, age, monthly_income))
introduce('张三', 25) # 大家好,我是张三,今年25岁,月入1w元
introduce('李四', 24, '9k') # 大家好,我是李四,今年24岁,月入9k元
3.5、不定长参数
- 可变参数表示用户输入多少参数,则接受多少:
- 使用 *args 表示参数会以元组(tuple)的形式导入,存放所有未命名的变量参数。
- 使用 **kwargs 表示参数会以字典的形式导入。
注:args和kwargs变量名是可以更改的
1)单星号 *
不定长参数函数
def functionname([formal_args,] *var_args_tuple ):
function_suite
return [expression]
# 可变位置参数
def add(a, b, *args): # 如果有缺省参数应放在*arge后面,如: def add(a, b, *args, num1=10):
c = a + b
for arg in args:
c += arg
return c
print(add(1, 2, 3, 4, 5, 6, 7)) # 1,2传递给a,b 剩下数据以元组的形式传递给args
注:如果在函数调用时没有指定参数,它就是一个空元组。
- 声明函数时,参数中星号 ***** 可以单独出现,如果单独出现星号 ***** 后的参数必须用关键字传入。
>>> def f(a,b,*,c):
... return a+b+c
...
>>> f(1,2,3) # 报错
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: f() takes 2 positional arguments but 3 were given
>>> f(1,2,c=3) # 正常
6
>>>
2)双星号**
不定长参数函数
def functionname([formal_args,] **var_args_dict ):
function_suite
return [expression]
def add(a, b, *args,num1=10,**kwargs):
c = a + b
for arg in args:
c += arg
return c
print(add(1, 2, 3, 4, num1=3, x=5, y=6)) # 1,2传递给a,b,之后直到 num=3 之间的参数以元组的形式传递给args,最后剩下的参数以字典的形式传递给 kwargs(kwargs中的内容为:['x':5,'y':6])
4、函数的返回值
- 函数按有无返回值分为有返回值函数和无返回值函数
- 函数返回值是通过被调函数使主调函数得到一个确定值, 使用 return 语句
def add(a,b):
c = a +b
return c # 将c返回给主调函数
t = add(1,2)
print(t)
- 函数返回多个值
# 一般情况下函数只执行一个return
# 特殊情况(finally语句)下,一个函数可能执行多个return语句
def test(a, b):
x = a // b
y = a % b
# return x # return语句表示一个函数的结束
# return y # 所以不会执行到此行
return x, y # return返回的实质是一个元组,但是小括号()可以省略,等价于 return (x, y)
return [x, y] # 可以返回一个列表
return {'x':x, 'y':y} # 也可以返回一个字典
shang, yushu = test(32,6) # 元组拆包
print('商是{},余数是{}'.format(shang, yushu))
5、参数类型建议
def add(a, b):
c = a + b
return c
# 在Python里的函数参数类型不会被限制
print(add('hello', 'world'))
# 但是我们可以给使用建议数据类型
def acc(a: int, b: int):
c = a * b
return c
print(acc('hello', 4)) # 一般'hello'会有警告提示,但不会报错
6、匿名函数(Lambda 函数)
- Python 使用 lambda 可以创建匿名函数
- 匿名函数,就是不再使用 def 语句这样标准的形式定义一个函数
- lambda 只是一个表达式,函数体比 def 简单
- lambda 的主体是一个表达式,而不是一个代码块
- lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数
- 虽然 lambda 函数看起来只能写一行,却不等同于 C 或 C++ 的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率
Lambda 函数 语法格式:
lambda [arg1 [,arg2,.....argn]]:expression
sum = lambda arg1, arg2: arg1 + arg2
# 调用sum函数
print ("相加后的值为 : ", sum( 10, 20 ))
print ("相加后的值为 : ", sum( 20, 20 ))
- 可以将匿名函数封装在一个函数内,这样可以使用同样的代码来创建多个匿名函数
def myfunc(n):
return lambda a : a * n
mydoubler = myfunc(2) # mydoubler=lambda a:a*2
mytripler = myfunc(3) # mytripler=lambda a:a*3
print(mydoubler(11)) # 22
print(mytripler(11)) # 33
- 把匿名函数当做参数传递给另一个函数
# 调用一个函数,完成多种运算
def calc(a, b, fn):
c = fn(a, b)
return c
def add(x, y):
return x + y
def mul(x, y):
return x * y
calc(4, 3, add) # 将函数名作为参数
calc(5, 7, mul)
# 当函数表达式比较简单时,可以使用匿名函数
# 把匿名函数作为函数参数
print(calc(21, 7, lambda x, y: x / y))
7、强制位置参数
- Python3.8 新增了一个函数形参语法
/
用来指明函数形参必须使用指定位置参数,不能使用关键字参数的形式。
def f(a, b, /, c, d, *, e, f):
print(a, b, c, d, e, f)
# 形参 a 和 b 必须使用指定位置参数,c 和 d 可以是位置形参或关键字形参,而 e 和 f 要求为关键字形参:
8、函数调用
- 函数之间可以相互调用, 也可以嵌套调用。函数还可以自己调用自己, 称为递归调用
函数之间相互调用:
def fact(n): # 求n的阶乘
t = 1
for i in range(1, n + 1):
t *= i
return t
def fac_sum(m): # 求阶乘的和
x = 0
for i in range(1, m + 1):
x += fact(i) # 函数调用函数
return x
print(fac_sum(5))
函数递归调用;
# 使用递归调用,实现快排
def quick_sort(data):
if len(data) >= 2: # 递归入口及出口
mid = data[len(data) // 2] # 选取基准值,也可以选取第一个或最后一个元素
left, right = [], [] # 定义基准值左右两侧的列表
data.remove(mid) # 从原始数组中移除基准值
for num in data:
if num >= mid:
right.append(num)
else:
left.append(num)
return quick_sort(left) + [mid] + quick_sort(right) # 递归调用
else:
return data # 递归出口 递归结束条件:len(data)<2
'''
array = [2, 3, 5, 7, 1, 4, 6, 15, 5, 2, 7, 9, 10, 5, 9, 17]
print(quick_sort(array))
# 输出为[1, 2, 2, 3, 4, 5, 5, 5, 6, 7, 7, 9, 9, 10, 15, 17]
'''
9、递归函数
在写递归函数时应该注意不能使函数调用成为"死循环",应该设置好递归结束条件
def say_story():
print('老头和小伙讲故事')
print('讲故事的是')
say_story() # 这样会陷入死循环,导致报错
say_story()
10、全局变量和局部变量
- 变量的有效范围(作用范围)称为变量的作用域
# 全局变量,在整个py文件里都可以使用
a = 14
word = '你好'
# 在Python里,只用函数能分割变量作用域
def test():
x = 'ok' # 定义在函数内部的变量为局部变量,只能在函数内度使用
# 如果函数内变量的名和全局变量同名,会在函数内定义一个新的变量,而不是修改全局变量,即在函数作用域内,将同名全局变量屏蔽了
a = 10
print('函数内部a={}'.format(a))
# 使用global进行对外部变量声明,进行修改全局变量
# 通过global的声明,取消了对 word 的屏蔽,这里使用和外部的 word 是同一个
global word
word = 'hello'
# 使用内置函数 locals()查看局部变量, globals()查看全局变量
print('locals={},\nglobals={}'.format(locals(), globals()))
test()
# print('x={}', format(x)) #函数外部不能使用函数内部的变量,会报错,变量未定义
print('函数外部a={}'.format(a))
print('函数外部word={}'.format(word))
11、高阶应用
1)把一个函数作为另一个函数的返回值
def test():
print('我是test函数')
return 'hello'
def bar():
print('我是bar函数')
# 在这里返回的是对test 函数的调用,书架上是在 bar 函数结束之前对test 函数调用了一次,bar返回的是test函数的返回值
return test() # 返回的是函数,即调用函数
def demo():
print('我是demo函数')
return test # 返回的是变量名,可以作为字符串被python解析
# 调用 函数 bar ,实际上运行了bar函数一遍,并且还运行了一遍 test 函数
bar()
'''
输出结果:
我是bar函数
我是test函数
'''
# 将 函数 bar的返回值赋给 x,并执行了一遍 bar函数
# x中存放的是test的返回值,字符串"hello",x类似于一个宏
x=bar()
print(x)
'''
输出结果:
我是bar函数
我是test函数
'''
# demo()()先执行一遍函数demo,之后变成 test(),再执行一遍函数 test()
demo()()
'''
输出结果:
我是demo函数
我是test函数
'''
先执行一遍demo函数,此时y相当于一个宏,内部存放的是字符串常量 "test",之后再执行一遍函数 test
y = demo()
y()
'''
输出结果:
我是demo函数
我是test函数
'''
2)Python支持函数的嵌套定义,在函数内部再定义一个函数
def data():
print('data被被打印了')
def lanm():
print('lanm被打印了')
return lanm()
'''
输出结果:
data被被打印了
lanm被打印了
'''
3)闭包函数
闭包函数和上面的嵌套函数类似,不同之处在于,闭包中外部函数返回的不是一个具体的值,而是一个函数。类似于把一个函数作为另一个函数的返回值实例中的demo函数的返回值
def outer():
x = 10 # 在外部函数定义了一个变量x,为一个局部变量
y = 30
def inner():
x = 20 # 不是修改外部变量x,而是在内部函数创建了一个新的变量x
z = y + 1 # 可以直接使用外部函数变量,但是不能修改
# 在内部函数用 nonlocal 声明外部函数变量
nonlocal y
y = 10 # 此时是修改外部函数变量y
return inner # 返回的是 inner 函数首地址,加小括号才能调用
# outer()() 相当于是 inner()
outer()() # 先返回的是 inner ,加上()后是调用
#闭包函数,其中 exponent 称为自由变量
def nth_power(exponent):
def exponent_of(base):
return base ** exponent
return exponent_of # 返回值是 exponent_of 函数
square = nth_power(2) # 计算一个数的平方,square是一个函数指针
cube = nth_power(3) # 计算一个数的立方,cube是一个函数指针
print(square(2)) # 计算 2 的平方,相当于 nth_power(2)(2)
print(cube(2)) # 计算 2 的立方,相当于 nth_power(3)(2)
def nth_power_rewrite(base, exponent):
return base ** exponent
# 不使用闭包
res1 = nth_power_rewrite(base1, 2)
res2 = nth_power_rewrite(base2, 2)
res3 = nth_power_rewrite(base3, 2)
# 使用闭包
square = nth_power(2)
res1 = square(base1) # 相当于 nth_power(2)(base1)
res2 = square(base2) # 相当于 nth_power(2)(base2)
res3 = square(base3) # 相当于 nth_power(2)(base3)
12、装饰器
- 装饰器指的是定义一个函数,该函数是用来为其他函数添加额外的功能,就是拓展原来函数功能的一种函数
- 装饰器在不修改被装饰器对象源代码以及调用方式的前提下为被装饰对象添加新功能
12.1、装饰器语法结构
def function_name(fn):
def inner():
pass
return inner
# 在不改变 demo 函数源码的条件下,为 demo 函数添加了 function_name 函数中的功能
@function_name # 用来装饰deom函数
def deom():
pass
12.2、装饰器原理
import time
def cal_time(fn):
def inner():
start = time.time()
t = fn()
end = time.time()
print('程序耗时', end - start)
return t
return inner
@cal_time #第一件事调用cal_time; 第二件事把被装饰的函数传给fn;
def dome(): # 第三件事返回inner给dome,即dome=inner(dome指向inner函数首地址)
for i in range(1,100000000):
i += i
return i
# 第四件事当再调用dome时,此时的dome函数不再是上面的dome,而是inner+demo
print('装饰后的dome={}'.format(dome))
# 装饰后的dome=<function cal_time.<locals>.inner at 0x00000281704D83A0>
dome()
12.3、装饰器的一个简单应用
# 提需求/改需求
def wegame(name,game):
print('{}在玩{}'.format(name,game))
wegame('张三','王者荣耀')
# 该需求,在22点后不能玩了
# 一般我们不会在原函数上修改了
def funct(fn):
def inner(x,y,time):
if time>=22:
ptint('太晚了,不能打游戏了')
else:
fn(x,y)
return inner
@funct
def wegame(name,game):
print('{}在玩{}'.format(name,game))
wegame('张三','王者荣耀',time=23)
注:函数名不能重名, 否则后一个会覆盖前一个
变量名不能和函数名一样, 因为函数名相当于一个变量