【python】函数详解 目录一、函数1.1 函数是什么1.2 语法格式1.3 函数参数1.4 函数的返回值1.5 变量作用域1.6 函数的执行过程1.7 链式调用1.8 嵌套调用1.9 函数递归1.10 参数默认值1.11 关键字参数1.12 小结一、函数1.1 函数是什么编程中的函数和数学中的函数有一定的相似之处.数学上的函数, 比如 y sin x , x 取不同的值, y 就会得到不同的结果.编程中的函数,是一段可以被重复使用的代码片段.求数列的和,不使用函数# 1. 求 1 - 100 的和 sum 0 for i in range(1, 101): sum i print(sum) # 2. 求 300 - 400 的和 sum 0 for i in range(300, 401): sum i print(sum) # 3. 求 1 - 1000 的和 sum 0 for i in range(1, 1001): sum i print(sum)可以发现,这几组代码基本是相似的,只有一点点差异.可以把重复代码提取出来,做成一个函数求 数列 的和,使用函数# 定义函数 def calcSum(beg, end): sum 0 for i in range(beg, end 1): sum i print(sum) # 调用函数 sum(1, 100) sum(300, 400) sum(1, 1000)可以明显看到,重复的代码已经被消除了.1.2 语法格式创建函数/定义函数def函数名(形参列表):函数体return返回值调用函数/使用函数函数名(实参列表)//不考虑返回值返回值函数名(实参列表)//考虑返回值函数定义并不会执行函数体内容,必须要调用才会执行.调用几次就会执行几次deftest1():print(hello)#如果光是定义函数,而不调用,则不会执行.函数必须先定义,再使用.test3()#还没有执行到定义,就先执行调用了,此时就会报错.deftest3():print(hello)就像昆虫记里面必须先念诗才能成尊。1.3 函数参数在函数定义的时候,可以在( )中指定形式参数 (简称形参),然后在调用的时候,由调用者把实际参数(简称实参)传递进去.这样就可以做到一份函数,针对不同的数据进行计算处理.考虑前面的代码案例:def calcSum(beg, end): sum 0 for i in range(beg, end 1): sum i print(sum) calcSum(1, 100) calcSum(300, 400) calcSum(1, 1000)上面的代码中,beg, end就是函数的形参.1, 100/300, 400就是函数的实参.在执行sum(1, 100)的时候,就相当于beg 1, end 100,然后在函数内部就可以针对1-100进行运算.在执行sum(300, 400)的时候,就相当于beg 300, end 400,然后在函数内部就可以针对300-400进行运算.注意:一个函数可以有一个形参,也可以有多个形参,也可以没有形参.一个函数的形参有几个,那么传递实参的时候也得传几个.保证个数要匹配.def test(a, b, c): print(a, b, c) test(10)和C / Java不同, Python是动态类型的编程语言,函数的形参不必指定参数类型.换句话说,一个函数可以支持多种不同类型的参数.def test(a): print(a) test(10) test(hello) test(True)1.4 函数的返回值函数的参数可以视为是函数的输入,则函数的返回值,就可以视为是函数的输出 .此处的 输入, 输出 是更广义的输入输出, 不是单纯指通过控制台输入输出.我们可以把函数想象成一个 工厂. 工厂需要买入原材料, 进行加工, 并生产出产品.函数的参数就是原材料, 函数的返回值就是生产出的产品.下列代码def calcSum(beg, end): sum 0 for i in range(beg, end 1): sum i print(sum) calcSum(1, 100)可以转换成def calcSum(beg, end): sum 0 for i in range(beg, end 1): sum i return sum result calcSum(1, 100) print(result)这两个代码的区别就在于,前者直接在函数内部进行了打印,后者则使用return语句把结果返回给函数调用者,再由调用者负责打印.我们一般倾向于第二种写法.实际开发中我们的一个通常的编程原则, 是 逻辑和用户交互分离. 而第一种写法的函数中, 既包含了计算逻辑, 又包含了和用户交互(打印到控制台上). 这种写法是不太好的, 如果后续我们需要的是把计算结果保存到文件中, 或者通过网络发送, 或者展示到图形化界面里, 那么第一种写法的函数, 就难以胜任了.而第二种写法则专注于做计算逻辑, 不负责和用户交互. 那么就很容易把这个逻辑搭配不同的用户交互代码, 来实现不同的效果.一个函数中可以有多个return语句# 判定是否是奇数 def isOdd(num): if num % 2 0: return False else: return True result isOdd(10) print(result)执行到return语句,函数就会立即执行结束,回到调用位置.# 判定是否是奇数 def isOdd(num): if num % 2 0: return False return True result isOdd(10) print(result)如果num是偶数,则进入if之后,就会触发return False,也就不会继续执行return True一个函数是可以一次返回多个返回值的.使用,来分割多个返回值.def getPoint(): x 10 y 20 return x, y a, b getPoint()如果只想关注其中的部分返回值,可以使用_来忽略不想要的返回值.def getPoint(): x 10 y 20 return x, y _, b getPoint()1.5 变量作用域观察以下代码def getPoint(): x 10 y 20 return x, y x, y getPoint()在这个代码中,函数内部存在x, y,函数外部也有x, y.但是这两组x, y不是相同的变量,而只是恰好有一样的名字.变量只能在所在的函数内部生效.在函数getPoint()内部定义的x, y只是在函数内部生效.一旦出了函数的范围,这两个变量就不再生效了.def getPoint(): x 10 y 20 return x, y getPoint() print(x, y)在不同的作用域中,允许存在同名的变量虽然名字相同,实际上是不同的变量.x 20 def test(): x 10 print(f函数内部 x {x}) test() print(f函数外部 x {x})注意:在函数内部的变量,也称为局部变量不在任何函数内部的变量,也称为全局变量如果函数内部尝试访问的变量在局部不存在,就会尝试去全局作用域中查找x 20 def test(): print(fx {x}) test()如果是想在函数内部,修改全局变量的值,需要使用global关键字声明x 20 def test(): global x x 10 print(f函数内部 x {x}) test() print(f函数外部 x {x})如果此处没有 global , 则函数内部的 x 10 就会被视为是创建一个局部变量 x, 这样就和全局变量 x 不相关了.if / while / for等语句块不会影响到变量作用域换而言之,在if / while / for中定义的变量,在语句外面也可以正常使用.for i in range(1, 10): print(f函数内部 i {i}) print(f函数外部 i {i})1.6 函数的执行过程调用函数才会执行函数体代码.不调用则不会执行.函数体执行结束(或者遇到return语句),则回到函数调用位置,继续往下执行.def test(): print(执行函数内部代码) print(执行函数内部代码) print(执行函数内部代码) print(1111) test() print(2222) test() print(3333)这个过程还可以使用PyCharm自带的调试器来观察.点击行号右侧的空白, 可以在代码中插入断点右键, Debug, 可以按照调试模式执行代码. 每次执行到断点, 程序都会暂停下来.使用 Step Into (F7) 功能可以逐行执行代码.1.7 链式调用前面的代码很多都是写作# 判定是否是奇数 def isOdd(num): if num % 2 0: return False else: return True result isOdd(10) print(result)实际上也可以简化写作print(isOdd(10))把一个函数的返回值,作为另一个函数的参数,这种操作称为链式调用.这是一种比较常见的写法.1.8 嵌套调用函数内部还可以调用其他的函数,这个动作称为嵌套调用 .def test(): print(执行函数内部代码) print(执行函数内部代码) print(执行函数内部代码)test函数内部调用了print函数,这里就属于嵌套调用.一个函数里面可以嵌套调用任意多个函数函数嵌套的过程是非常灵活的.def a(): print(函数 a) def b(): print(函数 b) a() def c(): print(函数 c) b() def d(): print(函数 d) c() d()如果把代码稍微调整,打印结果则可能发生很大变化.def a(): print(函数 a) def b(): a() print(函数 b) def c(): b() print(函数 c) def d(): c() print(函数 d) d()注意体会上述代码的执行顺序. 可以通过画图的方式来理解.函数之间的调用关系,在Python中会使用一个特定的数据结构来表示,称为函数调用栈.每次函数调用,都会在调用栈里新增一个元素,称为栈帧.可以通过 PyCharm 调试器看到函数调用栈和栈帧.在调试状态下, PyCharm 左下角一般就会显示出函数调用栈.每个函数的局部变量,都包含在自己的栈帧中def a(): num1 10 print(函数 a) def b(): num2 20 a() print(函数 b) def c(): num3 30 b() print(函数 c) def d(): num4 40 c() print(函数 d) d()选择不同的栈帧, 就可以看到各自栈帧中的局部变量.思考:上述代码, a, b, c, d 函数中的局部变量名各不相同. 如果变量名是相同的, 比如都是 num , 那么这四个函数中的 num 是属于同一个变量, 还是不同变量呢?显然是不同的。1.9 函数递归递归是 嵌套调用 中的一种特殊情况,即一个函数嵌套调用自己.递归计算5!def factor(n): if n 1: return 1 return n * factor(n - 1) result factor(5) print(result)上述代码中,就属于典型的递归操作.在factor函数内部,又调用了factor自身.递归代码务必要保证存在递归结束条件.比如if n 1就是结束条件.当n为1的时候,递归就结束了.每次递归的时候,要保证函数的实参是逐渐逼近结束条件的.如果上述条件不能满足,就会出现无限递归 .这是一种典型的代码错误.def factor(n): return n * factor(n - 1) result factor(5) print(result)如前面所描述, 函数调用时会在函数调用栈中记录每一层函数调用的信息.但是函数调用栈的空间不是无限大的. 如果调用层数太多, 就会超出栈的最大范围, 导致出现问题递归的优点递归类似于数学归纳法 ,明确初始条件,和递推公式,就可以解决一系列的问题.递归代码往往代码量非常少.递归的缺点递归代码往往难以理解,很容易超出掌控范围递归代码容易出现栈溢出的情况递归代码往往可以转换成等价的循环代码.并且通常来说循环版本的代码执行效率要略高于递归版本.实际开发的时候,使用递归要慎重!1.10 参数默认值Python中的函数,可以给形参指定默认值.带有默认值的参数,可以在调用的时候不传参.计算两个数字的和def add(x, y, debugFalse): if debug: print(f调试信息: x{x}, y{y}) return x y print(add(10, 20)) print(add(10, 20, True))此处debugFalse即为参数默认值.当我们不指定第三个参数的时候,默认debug的取值即为False.带有默认值的参数需要放到没有默认值的参数的后面def add(x, debugFalse, y): if debug: print(f调试信息: x{x}, y{y}) return x y print(add(10, 20))1.11 关键字参数在调用函数的时候,需要给函数指定实参.一般默认情况下是按照形参的顺序,来依次传递实参的.但是我们也可以通过关键字参数,来调整这里的传参顺序,显式指定当前实参传递给哪个形参.def test(x, y): print(fx {x}) print(fy {y}) test(x10, y20) test(y100, x200)形如上述test(x10, y20)这样的操作,即为关键字参数.1.12 小结函数是编程语言中的一个核心语法机制. Python中的函数和大部分编程语言中的函数功能都是基本类似的.我们当下最关键要理解的主要就是三个点函数的定义函数的调用函数的参数传递我们在后续的编程中,会广泛的使用到函数.大家在练习的过程中再反复加深对于函数的理解.