07 python基础六--函数
1 调用函数
所谓调用函数,即指调用python提供的内置函数,需要知道的是,要调用一个函数,必须知道它的名称和参数
Python内置函数列表:https://docs.python.org/3/library/functions.html
#当然除了查看文档外,python还支持help()查看函数信息的方式
#eg
>>>help(abs)
Help on built-in function abs in module __builtin__:
abs(...)
abs(number) -> number
Return the absolute value of the argument. #返回一个数字的绝对值
>>> abs(100)
100
>>> abs(-100)
100
# 最大值
>>> max(1,2)
2
>>> max(1,2,3,4,5)
5
# 调用内置函数进行数据类型的转换
>>> int('123')
123
>>> int(12.34)
12
>>> float('12.34')
12.34
>>> str(123)
'123'
>>> bool(1)
True
>>> bool('')
False
>>> bool(0)
False
函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:
>>> a = abs
>>> a(-123)
123
2 定义函数
- 当内置函数无法满足我们的需求时,可以自定义一个函数
- 在Python中,定义一个函数使用def语句,依次写出函数名、括号、参数和冒号,然后,在缩进块中编写函数体,函数的返回值用return语句返回
>>> def ma_abs(x):
... if not instance(x, (int, float)):
... raise TypeError('bad operand type')
... if x >= 0:
... return x+x
... else:
... return x-x
...
>>> ma_abs(-2)
0
>>> ma_abs(2)
4
- 空函数,什么事也不做的空函数可以用pass语句
- 实际上,pass一般用来作为占位符,比如现在没想好怎么写函数的代码,可以放一个pass让代码先运行起来
>>> def nop():
... pass
...
>>> nop()
>>>
- Python函数可以返回多个值?
- 举个栗子🌰,比如在游戏中经常需要从一个点移动到另一个点,给出坐标、位移和角度,就可以计算出新的新的坐标:
>>> import math #导入math包
>>> def move(x, y, step, angle = 0):
... nx = x + step * math.cos(angle)
... ny = y - step * math.sin(angle)
... return nx, ny
...
>>> x, y = move(100, 100, 60, math.pi / 6)
>>> print(x, y)
(151.96152422706632, 70.0) # 通过结果可见:Python函数返回的其实仍然是单一值,是一个tuple,语法上,tuple可以省略括号。多个变量按位置赋值tuple的对应值
3 函数的参数
Python的函数定义非常简单,但灵活度却非常大。除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数。使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码。
- 位置参数、默认参数
>>> def power(x):
... return x * x
...
>>> power(2)
4
>>> power(6)
36
# x,n 即为函数的位置参数,传入的两个值按照位置顺序依次赋给参数x和n,n可以赋予默认值,称为默认参数
>>> def powerPlus(x, n = 2):
... s = 1
... while n > 0:
... n = n - 1
... s = s * x
... return s
...
>>> powerPlus(2, 3)
8
>>> powerPlus(4, 4)
256
- 可变参数
举个栗子🌰:以数学题为例子,给定一组数字a,b,c……,请计算a2 + b2 + c2 + ……
>>> def calc(numbers):
... sum = 0
... for n in numbers:
... sum = sum + n * n
... return sum
...
# 普通的参数只能接受一个值,要想实现多值,需要先组装出一个list或tuple
>>> calc([1,2,3])
14
>>> calc((1,2,3,4))
30
# 可变函数出场!!!
>>> def calc(*numbers):
... sum = 0
... for n in numbers:
... sum = sum + n * n
... return sum
...
>>> calc(1,2,3,4)
30
>>> calc()
0
定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数
- 关键字参数
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。请看示例:
>>> def person(name, age, **kw):
... print('name:', name, 'age:', age, 'other:', kw)
...
# 调用该函数外,可以只传入必选参数
>>> person('Bob', 30)
('name:', 'Bob', 'age:', 30, 'other:', {})
# 也可以传入任意个数的关键字参数
>>> person('Ami', 18, city = 'beijing')
('name:', 'Ami', 'age:', 18, 'other:', {'city': 'beijing'})
>>> person('Hua', 20, gender = 'M', Phone = 188011102222)
('name:', 'Hua', 'age:', 20, 'other:', {'gender': 'M', 'Phone': 188011102222})
# 简化调用方式
>>> extra = {'city':'beijing', 'job':'Teacher', 'Phone':13366667777}
>>> person('Lw', 26, **extra)
('name:', 'Lw', 'age:', 26, 'other:', {'city': 'beijing', 'job': 'Teacher', 'Phone': 13366667777})
- 命名关键字参数
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。
如果要限制关键字参数的名字,就可以用命名关键字参数,例如:只接收city和job作为关键字参数
>>> def person(name, age, **kw):
... if 'city' in kw:
... pass
... if 'job' in kw:
... pass
... print('name:', name, 'age:', age, 'other:', kw)
...
# 调用者传入参数个数不受限制
>>> person('Jack', 24, city='beijing', addr='chaoyang', job='coder')
name: Jack age: 24 other: {'city': 'beijing', 'addr': 'chaoyang', 'job': 'coder'}
# 采用**命名关键字参数**来限制参数个数,倘若多传参数进去则会报错
>>> def person(name, age, *, city, job):
... print(name, age, city, job)
>>> person('Jack', 22, city='beijing', job='coder')
Jack 22 beijing coder
# 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:
>>> def person(name, age, *args, city, job):
... print(name, age, args, city, job)
...
>>>
>>> person('Bob', 23, 1, 2, city = 'taiyuan', job = 'coder' )
Bob 23 (1, 2) taiyuan coder
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数
4 递归函数
在函数内部,可以调用其他函数。如果一个函数在内部调用本身,这个函数就是递归函数。
举个栗子🌰:
计算阶层 n! = 1 * 2 * 3 * 4 * 5 ... * n,用函数fact(n)表示:
fact(n) = n! = 1* 2 * 3 * ... * (n-1) * n = fact(n-1) * n
所以,fact(n) 可以表示为 n * fact(n-1),只有n = 1时需要特殊处理
>>> def fact(n):
... if n == 1:
... return 1
... return n * fact(n - 1)
...
>>> fact(1)
1
>>> fact(5)
120
# 溢出
>>> faxt(1000)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'faxt' is not defined
递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
使用递归函数需要注意防止栈溢出,在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,就会减一层栈帧。由于栈的大小不是无限的,所以递归调用的次数过多,会导致栈溢出。
解决递归调用栈溢出的方法是通过尾递归优化
尾递归优化是指:在函数返回的时候,调用函数本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身不论调用多少次,都只占用一个栈帧,不会出现溢出的情况。
>>> def fact(n):
... return fact_iter(n, 1)
...
>>> def fact_iter(num, product):
... if num == 1:
... return product
... return fact_iter(num - 1, num * product)
...
>>> fact(2)
2
fact(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L
尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。
遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。