(3)python函数与递归
【1】系统函数、自定义函数
【1.1】位置参数
即定义和使用的参数一一对应
def test_converter(C): expression_1=C*9/5 + 32 return str(expression_1)+'F' print(test_converter(30))
86.0F
def test(): num=1+1 print(num) print(test())
2
None
【1.2】关键字参数
在使用函数的时候,我们一般情况是位置参数,入参与定义参数的位置一一对应。
但关键字参数可以切换位置,用参数名来赋值的方式,这种方式就叫关键字参数
位置参数方式:
from math import *; print(pow(10,2)) def lenth_1(long,high): return (pow(long,2)+pow(high,2))**0.5 print(lenth_1(long=3,high=4))
100.0
5.0
关键字参数方式:
def area(base_up,base_down,height=2): return (base_up+base_down)*height/2 print(area(4,4,5),area(4,4))
20.0 8.0
混用:正确方式,位置参数一定要在最左边
def test_fn(x,y,z): print(x,y,z) test_fn(3,z=1,y=2)
错误方式:关键字参数不能在位置参数之前
【1.3】*args,**kargs,参数的收集
他们这2个参数,只能放到形参最后面
*args:收集所有未知的位置参数,这里的单词并非固定 主要是 一个 * 和 ** 的区别,汇总到一个元组里
(这个名称不是固定的,你也可以换个名称,但 * 必须有,但建议使用 *args)
def test(name,age,*args,**kargs): print(*args) #print('aaaa~~~{}'.format(*args)) #print(**kargs) name1='abc' name2='李四' age1=123 age2=18 desc_var1='abc是个百岁老人' desc_var2='李四是一个年轻小伙' dict_var1={ 'name':'abc', 'age':123, 'desc':'abc是一个百岁老人' } test(name1,age1,name2,age2,desc_var1,desc_var2,dict_var1)
李四 18 abc是个百岁老人 李四是一个年轻小伙 {'name': 'abc', 'age': 123, 'desc': 'abc是一个百岁老人'}
**kwargs: 收集所有的未知关键字参数,汇总到一个字典里
def test_fn(**kwargs): print(kwargs) test_fn(name='chaoqun',age=25,sex='m') test_fn(**{'name': 'chaoqun.guo','age': 25, 'set': 'm'}) a = {'name': 'chaoqun.guo','age': 25, 'set': 'm'} test_fn(**a)
【1.4】嵌套函数
嵌套函数,就是在函数的内部用def 定义另外的函数
def foo(): print('in the foo') def bar(): print('in the bar') bar() foo()
in the foo
in the bar
~~~~ 注意层级,调用只能在相同层级,不能在高层调用底层,但可以在底层调用高层,也可以定义与调用在同级,下面这个例子演示给你看
def grandpa(): x=1 print(x) def dad(): x=2 print(x) def son(): x=3 print(x) son() dad() grandpa()
1
2
3
~~~然后我们如果把层级调用修改一下,运行就会报错,左边是高层调用底层,右边是底层调用高层,其实这也是一个作用于的问题
【2】局部/全局变量在函数中的应用
【2.1】局部变量
字符串和证书,分为局部和全局,这里不能改
a = 'q' def test_fn(): a = 'w' +'a' test_fn() print(a)
q
【2.2】全局变量
数字、字符串:
a = 'q' def test_fn(): global a # 必须要在这里再引用一次,才能引用到外面的 a a = a +'a' test_fn() print(a)
qa
如果是字典、列表等,不需要global,可以直接改:
a = ['a','b','c'] def test_fn(): a[0]='ggg' # 但是如果在函数内部这样,a = [1,2,3],这则是重新定义一个 a 的局部变量列表,不会修改外部的 a
test_fn() print(a)
['ggg', 'b', 'c']
【2.3】作用域案例
x=0 def grandpa(): x=1 print(x) def dad(): x = 2 print(x) def son(): x = 3 print(x) son() dad() grandpa()
1
2
3
【3】递归
【3.1】递归特点
(1)递归必须要有一个明确的结束条件
(2)每次进入更深一层的递归时,问题规模相比上次递归都应有所减少
(3)递归效率不高,递归层次过多会导致栈溢出(
在计算机中,函数调用是通过栈(stack)这种数据结构实现的;
每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会建一层栈帧。
由于栈的大小不是无限的,所以递归调用的次数越多,会导致栈溢出 )
【3.2】递归基本案例:二分法
def recursion_info(n): print(n) if(n/2==0): return n return(recursion_info(int(n/2))) # 默认最大递归次数是999 recursion_info(100)
100
50
25
12
6
3
1
【3.3】高效的递归:尾递归
前面说递归效率不高,因为每递归一次,就多了一层栈,递归次数太多还会导致栈溢出,这也是为什么python会默认限制递归次数的原因。但有一种方式是可以实现递归过程中不产生多层栈的,即尾递归,
尾递归
如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。
当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高。
尾递归例子
def calc(n): print(n - 1) if n > -50: return calc(n-1)
我们之前求的阶乘是尾递归么?
def factorial(n): if n == 0: #是0的时候,就运算完了 return 1 return n * factorial(n-1) # 每次递归相乘,n值都较之前小1 d = factorial(4) print(d)
上面的这种递归计算最终的return操作是乘法操作。
所以不是尾递归。因为每个活跃期的返回值都依赖于用n乘以下一个活跃期的返回值,因此每次调用产生的栈帧将不得不保存在栈上直到下一个子调用的返回值确定。
【4】函数与函数式编程的不同
如下图,第一行是我们想要实现的表达式计算
感觉学汇编的时候是这么用的。
【5】高阶函数:把函数名做实参传给另外一个函数的形参
【5.1】基本概念演示
有两点
(1)把函数名做实参传给另外一个函数的形参(这也就表达了,高阶函数可以在不修改被装饰函数代码的情况下,为其添加功能)
(2)返回值中包含函数名(不修改函数的调用方式的情况下,为其添加功能)
(1)把函数名做实参传给另外一个函数的形参
# 把函数本身做实参传给另外一个函数的形参
def add(a,b,f):
print(f) return f(a)+f(b) res = add(3,-6,abs) # abs 为绝对值函数 print(res)
<built-in function abs>
9
~~~ 上面这个结果,因为 abs 是内置函数方法,所以输出的不是内存地址值
def bar(): print('in the bar') def test(func): print(func) func() test(bar)
<function bar at 0x015195C8>
in the bar
~~~上面输出的,这个 <function bar at 0x015195C8> 是什么玩意,这个就是函数即变量概念(见【7】)。它输出的是函数体所在的内存地址
~~~ 这也就表达了,高阶函数可以在不修改原函数代码的情况下,为其添加功能
(2)返回值中包含函数名
import time def bar(): print('in the bar') time.sleep(3) def test2(func): print('调用函数test2,返回一个函数') print(func) return func bar = test2(bar) bar()
调用函数test2,返回一个函数
<function bar at 0x01969610>
in the bar
~~~ 那么,这里我们可以看到,我们用高阶函数对bar函数进行一番骚操作,最终使得 bar 函数在 不被修改函数的调用方式的情况下,为其添加了功能
【5.2】应用场景
有人可能问,这有什么应用场景呢?比如我用它实现不同函数的调用,实际上就是把函数做了一层封装
# 用高阶函数使用四则运算 def four_operation(a,b,f): return f(a,b) def add(a,b): return a+b def multiplication(a,b): return a*b def reduce(a,b): return a-b def division(a,b): return a/b res = four_operation(3,-6,add) print(res)
-3
# 用高阶函数计算被修饰函数的执行时间 import time def test1(func): start_time=time.time() func() stop_time=time.time() print('the func run time is {}'.format(stop_time-start_time)) def bar(): print('in the bar') time.sleep(3) test1(bar)
in the bar
the func run time is 2.9999334812164307
很明显睡3秒,怎么运行时间还小于3呢?这个值应该是一个预估值
【6】匿名函数(函数即变量)lambda
顾名思义,匿名函数就是没有名字的函数,以 lambda 为关键字定义
calc = lambda x:x * 3 #定义匿名函数 print(calc(3))
9
calc = lambda x,y:x * y print(calc(3,4))
12
【7】内存回收机制:函数即变量
【7.1】基本概念
在我们python内存中,它的回收机制是什么样的呢?我们根据上图来详细分析一下;
在数字、字符串变量中,我们之前的数据类型文章中就说了,它是不可以修改的,因为它是放在内存堆中。如果修改就是新赋值,就是在内存堆中重新开辟了一个字符串/数字,然后把变量指向它;
字符串举例:
如果有 x=1,y=x,那么这个时候, x 和 y都是引用指向 1 的内存地址,x 如果修改不影响 y。
那么这个内存中的1什么时候被回收呢? 当该内存存储地址没有被任何变量、函数等引用指向的话,就会随着python默认的刷新检测机制检测发现没有任何引用,就会把它回收掉:
函数同理:
那么同理,函数名也是一个引用指针,指向内存中存储函数体的这一块地址。这个我们从上面的匿名函数形式 就可以比较直观的看出来;
【7.2】实践举例
(1)在函数中调用未定义的函数
如上图,我们发现,即使报错,print 里面的内容依然输出了,为什么?
我们从【7.1】的思想中可以发现,函数即变量,调用 foo的时候,会访问它指向的内存地址块,执行地址库对应存储的代码。
第1句 print 的时候,没有报错,所以正常输出,第2句执行 bar() 的时候,发现在内存中没有找到 bar() 指向的地址,因为它根本没有定义,所以会出现这样的结果;
应该修改为:
(2)在函数定义中调用还未定义的函数
如图,其实是可以的,用函数即变量的思考方式,在没有执行之前,你在函数中写任何代码基本都是可以的。
但再调用执行函数的时候,如果未定义就会报错,这个见下面
【注意事项】
(1)return 不写默认返回None
def test_fn():
print(1)
print(test_fn())
1
None
(2)返回多个值,结果以元组包含多个值
def test_fn(): return 1,2,1,'a',['g','q'],{'a': 1,'b': 2},(1,2,3),{1,2,1} print(test_fn())
(1, 2, 1, 'a', ['g', 'q'], {'a': 1, 'b': 2}, (1, 2, 3), {1, 2})
(3)return 语句执行后退出函数
def test_fn():
print(1)
return 0
print(2)
print(test_fn())
1
0
【练习】
练习:重量转换器k换成kg
def weight_switch(k):
return k/1000
print(weight_switch(1500))
1.5
练习:求直角三角形的斜边长
from math import *;
print(pow(10,2),end=' ');
def lenth_1(long,high):
return (pow(long,2)+pow(high,2))**0.5
print(lenth_1(3,4))
100.0 5.0
【案例】
(1)写日志
import time def write_log(msg): with open('log.txt','a',encoding='utf-8') as f: time_format = '%Y-%m-%d %X' time_current = time.strftime(time_format) # 获取指定格式的当前时间 f.write(time_current+'\n') f.write(msg+'\n') write_log('今天天气好晴朗')
(2)获取value对应的key
def getkey_byvalue(dict_var,value_var):
return ([key for key,value in dict_var.items() if value==value_var])
dict_var1={
'a':100,
'b':200,
'c':300
}
print(getkey_byvalue(dict_var1,200))
['b']