函数,一段可以被重复使用的代码
# 定义函数
def calcSum(beg, end):
sum = 0
for i in range(beg, end+1):
sum += i
print(sum)
# D调用函数
calcSum(1, 10)
calcSum(1, 100)
calcSum(300, 500)
定义格式
def 函数名 (形参列表):
函数体
return 返回值
调用
函数名(实际参数列表)
or
返回值=函数名(实际参数列表)
Python中要求,函数定义写在前面,函数调用写在后面,先定义后调用
这两个事情严重程度是完全不同的!
PEP8这是Python里面一套非常流行的编程规范,也就是写代码中的一些"软性要求"
在函数定义的时候,可以在()中指定"“形式参数”(简称形参),然后在调用的时候,由调用者把"实际参数"(简称实参)传递进去。这样就可以做到一份函数,针对不同的数据进行计算处理。
函数的调用是可以有多次的,同时每次调用的实参,也是可以不同的
实参和形参的关系就像签合同的甲方乙方和真实的人名一样!
C++ Java 里面,不光要求形参和实参的个数要匹配,还要求类型也要匹配,但是在Python里,只是要求个数,对类型没有要求(动态类型)
def add(x, y):
return x+y
print(add(1, 2))
print(add(1.1, 2.2))
print(add("hello", "world"))
也不是说,随便传入啥类型都行,只要保证传入的参数类型,在函数体里面能够支持对应的运算操作即可!
函数的参数可以视为是函数的"输入",则函数的返回值,就可以视为是函数的"输出"。
此处的"输入",“输出”是更广义的输入输出,不是单纯指通过控制台输入输出。
我们可以把函数想象成一个"工厂”。工厂需要买入原材料,进行加工,并生产出产品。
函数的参数就是原材料。函数的返回值就是生产出的产品。
def isOdd(num):
if num % 2 == 0:
return False
else:
return True
print(isOdd(20))
print(isOdd(9))
执行到return 语句,代码就会立即返回,而是回到调用的位置
Python 中的一个函数可以返回多个值(非常香的特性!!,C++,Java都馋哭了)|
C++要想返回多个值,可以通过输出型参数(指针/引用)
Java 要想返回多个值,需要把多个值给包装成一个对象,返回这个对象
# 写一个函数,返回坐标上的一个点
def getPoint():
x = 10
y = 10
return x, y
a, b = getPoint()
print(a, b)
_, c = getPoint()
print(c)
Golang在设计的时候,(多元赋值)也把这个机制给引入过来了!
虽然现在返回了多个值,但是我只想用其中的一部分,不关注其他的,可以使用_
来进行占位!
_, c = getPoint()
不要x了,只要y,把y赋值给b即可!
变量的作用域!
一个变量名的有效范围是一定的!只在一个固定的区域内生效
函数内部的变量名,只能在函数内部生效。出了函数,就无效了!
函数内部和函数外部是可以使用同名的变量的.虽然变量名相同,但是是不同的变量!
x = 10
def test():
x = 20
print(f'函数内部x={x}')
test()
print(f'函数外部x={x}')
结果:
在函数里尝试读取全局变量,是可以的!当函数中尝试访问某个变量的时候,会先尝试在局部变量中查找,如果找到,就直接访问如果没找到,就会往上一级作用域中进行查找。
使用这个函数,把全局的x改为20
x = 10
def test():
global x
x = 20
test()
print(f"x={x}")
另外:if, else, while, for这些关键字也会引入"代码块”,但是这些代码块不会对变量的作用域产生影响!在上述语句代码块内部定义的变量,可以在外面被访问!
for i in range(1, 11):
print(i)
print(i)
执行到函数调用的时候,就会跳转到函数体内部来进行执行。
当函数内部执行完毕(运行完了/遇到return),就回到之前调用的位置,继续往下执行
调试执行,相比于正常的运行,最大的区别,可以随时停下来,方便程序猿观察程序的中间过程!
链式调用
把一个函数的返回值,作为另一个函数的参数,这种操作称为链式调用.这是一种比较常见的写法.
print(isOdd(10))
嵌套调用
函数内部还可以调用其他的函数,这个动作称为"嵌套调用”。
def test():
print("hello")
test()
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()
调试器的左下角,能够看到函数之间的“调用栈"。
调用栈里面描述了当前这个代码的函数之间调用关系是啥,每一层这个调用关系就称为"函数的栈帧",每个函数的局部变量就在这个栈帧中体现的。
每一层栈帧,你选中了之后,都能看到里面的局部变量每个函数的局部变量就保存在对应的栈帧中,调用函数,则生成对应的栈帧,函数结束,则对应的栈帧消亡(里面的局部变量也就没了)
函数递归,就是一个函数,自己调用自己
求n的阶乘
# 求n的阶乘
def factor(n):
if n == 1:
return 1
return n*factor(n-1)
print(factor(5))
虽然都是n,但是n是函数形参,形参相当于函数的局部变量!局部变量在函数自己的栈帧上的!虽然两个函数的局部变量名相同,但是是不同的栈帧,是不同的内存空间也就是不同的变量了!另一方面,看起来是同一个函数,但是这里的两次调用,其实是两个栈帧。
递归的代码,不会无限的往下执行,会在每一次递归的时候,都逐渐逼近递归的结束条件 if n==1
递归的代码,虽然很多时候看起来写法很简单,但是执行过程可能会非常复杂!!在分析递归代码的时候,光用脑子想,是很困难!
递归代码要有两个要素:
可以类比数学归纳法:
递归的缺点:
递归的优点:
代码非常简洁!!尤其是处理一些"问题本身就是通过递归的方式定义的")
Python中的函数,可以给形参指定默认值。
带有默认值的参数可以在调用的时候不传参。
def add(x=1, y=1, debug=False):
if debug == True:
print(f"x={x},y={y}")
return x+y
res = add(10, 20)
print(res)
print(add())
形参的默认值,带有默认值的形参就可以在调用函数的时候,不必传参
通过这样的默认值,就可以让函数的设计更灵活。
要求带有默认值的形参,得在形参列表的后面而不能在前面or中间!
按照先后顺序来传参,这种传参风格,称为“位置参数”,这是各个编程语言中最普遍的方式。
关键字传参,按照形参的名字来进行传参!非常明显的告诉程序猿,你的参数要传给谁!!另外可以无视形参和实参的顺序!
def test(x, y):
print(f"x={x}")
print(f"y={y}")
test(10, 20)
test(x=10, y=20)
test(y=200, x=100)
结果:
总结:
位置参数和关键字参数还能混着用,只不过混着用的时候要求位置参数在前,关键字参数在后
关键字参数,一般也就是搭配默认参数来使用的
一个函数,可以提供很多的参数,来实现对这个函数的内部功能做出一些调整设定,为了降低调用者的使用成本,就可以把大部分参数设定出默认值,当调用者需要调整其中的一部分参数的时候,就可以搭配关键字参数来进行操作。