3.1 懒人炒菜机
1.何为函数
函数(Function)在数学上的定义是指集合之间的对应关系。从数据的角度,函数则是一个魔法盒子,以实现数据的转换。而在编程中,函数是一种语法结构,它把一些指令封装起来,通过对函数的调用以实现某种功能而达成某种目的。于是,我们有三种看待函数的方式:集合的对应关系、数据的魔法盒子、语句的封装。
2.定义函数
基本语法:
def 函数名(参数):
语句块
return 返回值
例子:定义一个 计算两数的平方和 的函数
def square_sum(a,b):
a = a**2
b = b**2
c = a + b
return c
其中,a和b是形参,是一个形式代表,并非真正的数据;当形参(例子中是a和b)不存在时,依旧要保留函数名后的括号;return 用于说明函数的返回值(可返回多个值,多个值成一个元组),即要输出什么;return非必需,无return时,返回None。
3.调用函数
前面讲过print()函数的调用,同样其他函数的调用也是如此,一般函数含有参数时,我们在调用函数时需要赋予其参数,即实参,是一个实际的数据。例如对上面定义的函数进行调用:
x = square_sum(4,3)
print(x) #结果是25
另外,实参也可以先以变量的形式存在:
a = 4
b = 3
x = square_sum(a,b)
print(x) #结果是25
4.函数文档
顾名思义,函数文档就是用来说明函数功能和用法的。其可以用内置函数help()来查找函数的说明文档,例如:
help(max)
结果:
Help on built-in function max in module builtins:
max(...)
max(iterable, *[, default=obj, key=func]) -> value
max(arg1, arg2, *args, *[, key=func]) -> value
With a single iterable argument, return its biggest item. The
default keyword-only argument specifies an object to return if
the provided iterable is empty.
With two or more arguments, return the largest argument.
那么自定义函数也用同样的方法进行查找文档,但是自定义函数需要自己进行注释,即在定义函数内的语句块开头以两个 " " " 括起来的内容,定义完成后,再用help()进行调用查找。
3.2 参数传递
1.基本传参
把数据用参数的形式输入到函数,被称为参数传递。
在调用函数时,我们赋予其实参,实参会根据位置与形参一一对应,这被称为位置传递;同时也可以用关键字的方式来传递参数,便可以不遵守位置的对应关系,被称为关键字传递。注意,如果位置传递与关键字传递混合使用,位置参数一定要在关键字参数之前,否则会报错。给点代码看看:
def mixed_chengfang(a,b):
a = a**3
b = b**2
c = a + b
return c
s = mixed_chengfang(2,3)
print(s) #打印17
d = mixed_chengfang(b=2,a=3)
print(d) #打印31
f = mixed_chengfang(b=2,3)
print(f) #会报错
设置参数默认值:
def f(a,b,c=8):
return a + b + c
print(f(3,2,1)) #参数c取传入的1,结果打印6
print(f(3,2)) #参数c取默认值10,结果打印13
新传入的参数值会覆盖掉默认值,不传入则为默认值
2. 包裹传参
上面传递参数的方式,需要说明参数的个数,很多时候我们不知道参数的个数,于是使用包裹传参的方式来传递参数。包裹传参也有位置和关键字两种形式:
def package_position(*all_arguments):
print(type(all_arguments))
print(all_arguments)
package_position(2,5,7)
package_position(3,4,5,1,2,3,9)
输出结果:
<class 'tuple'>
(2, 5, 7)
<class 'tuple'>
(3, 4, 5, 1, 2, 3, 9)
其中的 * 号是为了提醒Python参数all_arguments 是包裹位置传递所用的元组名
同样的,若希望数据容器不是元组,而是字典,则需要以两个 * 号进行提醒
两者,即元组和字典可以混合着用:
def package_mix(*puple,**dict):
print(puple)
print(dict)
package_mix(1,2,3,a=8,b=2,c=3)
输出:
(1, 2, 3)
{'a': 8, 'b': 2, 'c': 3}
还可以把包裹传参和基本传参混合使用,他们出现的先后顺序是:位置→关键字→包裹位置→包裹关键字。
3. 解包裹
直接代码:
def unpackage(a,b,c):
print(a,b,c)
args=(1,3,4) #元组的解包裹
unpackage(*args) #打印1 3 4
args1={"a":1,"b":2,"c":3} #词典的解包裹
unpackage(**args1) #打印1 2 3
基本原则:位置→关键字→位置解包裹→关键字解包裹
3.3 递归
1.数学归纳法
递归源自数学归纳法。
第一步 证明命题对于 n = 1成立
第二步 假设命题对于n成立,n为任意自然数,则则证明在此假设下,命题对于n+1成立
命题得证
递归就像多米诺骨牌,确定n会倒下会导致n+1倒下,那么只要推倒第一块骨牌,就能保证任意骨牌的倒下。
上代码:
def gaussian_sum(n):
if n == 1:
return 1
else:
return n + gaussian_sum(n-1)
print(gaussion_sum(100)) #打印结果为5050
只要根据if结构进行判断并执行即可(无限套娃)
2. 函数栈
递归中需要用到 栈 这一数据结构。栈可以有序地存储数据,其最显著的特征为“后进先出”,类比于装书、拿书的过程。栈只支持两个操作:pop和push,一个是取栈顶元素,另一个是将新元素存入栈顶。栈中元素称为 帧。
对于上面的代码的每次函数调用,都会在栈中推入一个新的帧以保存此次函数调用的相关信息。当函数返回时,帧便出栈。
3. 变量的作用域
根据栈的推入说明函数内部是可以创建新变量的。根据变量所属,分为局部变量和全局变量,局部变量的改变无法影响全局变量,而全局变量的改变则影响局部变量。例外:
当参数是一个数据容器时,函数内外部只存在一个数据容器,所以函数内部对该数据容器的操作,会影响到函数外部。
写点代码:
list = [2,3,4,5]
def change_list(list):
list[2] = list[2] + 2
return list
print(change_list(list)) #打印[2, 3, 6, 5]
print(list) #打印[2, 3, 6, 5]
3.4 引入那把宝剑
1. 引入模块
一个.py文件就构成一个模块,一个模块里有一个或者多个函数,引用语法:
import 模块名
from 模块名 import 函数名1,函数名2,函数名3......
当然不止可以引入内含的函数,还可以引入内含的变量:
在text_test.py中:
text = "i love Python!"
在import.py中:
from text_test import text
print(text) #打印"i love Python!"
2.搜索路径
Python可以在以下三个地方寻找库:
- 与应用文件同一目录
- 标准库的安装路径
- 操作系统环境变量PYTHONPATH所包含的路径
3.5 异常处理
1. bug
bug在程序里是很常见的,是指程序缺陷,会引发错误或其他后果。一般分为语法错误和语义错误。
语法错误Python会提醒你,不会运行这段程序;而语义错误是主观上的错误,一般是达不到要求或目的,但程序是可以正常执行的,比如你想得到A,但是最后却得到了B。
2.Debug
debug即修复程序缺陷,程序员的日常修炼任务便是如此!(太难了!)
3. 异常处理
对于运行时可能产生的错误,我们可以提前在程序中说明处理。
异常处理可以提高程序的容错性。语法形式:
try:
...
except exception1:
...
except exception2:
...
else:
...
finally:
...
try中是要处理的语句
except中是发生对应异常时要执行的语句
else中是try成功执行无异常后要执行的语句
finally是无论如何都要执行的语句
如果异常无法交给合适的except语句,则向上层抛出直到被捕捉或造成主程序异常。当然也可以在程序中主动抛出异常(例子是try-except结构无法处理相应的除以0的错误):
raise ZeroDivisionError()
本章完