1、定义函数
参数检查
观察自己设置的绝对值函数(my_abs)和内置函数abs的报错区别:
def my_abs(x):
if x>=0:
return x
else:
return -x
my_abs('A')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-1-3a70387458fd> in <module>
5 return -x
6
----> 7 my_abs('A')
<ipython-input-1-3a70387458fd> in my_abs(x)
1 def my_abs(x):
----> 2 if x>=0:
3 return x
4 else:
5 return -x
TypeError: '>=' not supported between instances of 'str' and 'int'
abs('A')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-2-6eee3e7a739d> in <module>
----> 1 abs('A')
TypeError: bad operand type for abs(): 'str'
观察上方的报错信息,内置函数abs会检查参数错误而我们自己定义的函数不会,所以我们的函数定义不够完善。
对my_abs的定义进行修改,只允许整数和浮点数类型的参数,进行数据类型的检查。
isinstance() 函数来判断一个对象是否是一个已知的类型,类似 type()。
isinstance()语法规范:isinstance(object, classinfo)
classinfo:可以是直接或间接类名、基本类型或者由它们组成的元组。
#添加参数检查
def my_abs(x):
if not isinstance(x,(int,float)):
raise TypeError('bad operand type')
if x>=0:
return x
else:
return -x
my_abs('A')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-3-f13543a1b7d8> in <module>
7 else:
8 return -x
----> 9 my_abs('A')
<ipython-input-3-f13543a1b7d8> in my_abs(x)
2 def my_abs(x):
3 if not isinstance(x,(int,float)):
----> 4 raise TypeError('bad operand type')
5 if x>=0:
6 return x
TypeError: bad operand type
返回多个值
import math
def move(x,y,step,angle = 0):
nx = x+step*math.cos(angle)
ny = y-step*math.sin(angle)
return nx,ny
#import math语句表示导入math包,也就是导入相应sin和cos之类的数学函数
x,y = move(100,100,60,math.pi/6)
print(x,y)
151.96152422706632 70.0
其实这是一种返回多个值的假象,实际上python函数返回的是一个tuple,多个变量可以同时接收一个tuple,按位置赋给对应的值。
练习
定义一个函数quadratic(a,b,c),接收三个参数,返回一元二次方程的两个解
计算平方根调用math.sqrt()函数:
import math
def quadratic(a,b,c):
d = b*b-4*a*c
if d>=0:
x1 = (-b+math.sqrt(d))/(2*a)
x2 = (-b-math.sqrt(d))/(2*a)
return x1,x2
else:
return '此方程无解'
quadratic(1,2,1)
(-1.0, -1.0)
2、函数的参数
定义函数接口的基本要求:确定参数的名字和位置即可。
位置参数
编写计算某数平方的函数
def power(x):
return x*x
print(power(5))
print(power(10))
25
100
以上代码中,x为位置参数,调用函数必须传入对应个数的位置参数
编写计算x^n的函数
def power(x,n):
s = 1
while n > 0:
n = n-1
s = s * x
return s
print(power(5,2))
print(power(5,3))
25
125
传入位置参数时不仅要保证个数相同,还要保证和定义函数时候的顺序也相同(直接用函数名的话可以忽略顺序)
默认参数
给幂设置默认值
def power(x,n=2):
s = 1
while n>0:
n = n-1
s = s*x
return s
print(power(5))
print(power(5,2))
25
25
设置默认参数的注意事项:
(1)必选参数在前,默认参数在后。
(2)当函数有多个参数时,把变化大的参数放在前面,变化小的参数放在后面(变化小的参数就可以作为默认参数)
设置默认参数的好处:降低调用函数的难度。
学生注册问题:来自相同的城市,拥有相同的年龄,只有名字和班级不一样。
def enroll(name,gender,age=6,city='Beijing'):
print('name',name)
print('gender',gender)
print('age',age)
print('city',city)
enroll('Sarah','F')
name Sarah
gender F
age 6
city Beijing
定义默认函数时候的注意点:默认参数必须指向不变对象
例如:如果默认参数指向一个列表,在运行函数时会将列表看作一个变量,改变列表的内容后下次调用函数列表的初始内容就会发生改变。
可以使用None这种不变对象来设置默认参数值。
def add_end(L = []):
L.append('END')
return L
add_end()
add_end()
add_end()
['END', 'END', 'END']
可变参数
定义具有可变参数的函数,必须确定输入参数。
可变参数在函数调用时自动组装成为一个tuple。
定义可变参数比定义一个list或tuple参数相比,仅仅在参数前加了一个 * 号。在函数内部,可以传入任意个参数,包括0个参数。
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum+n*n
return sum
print(calc(1,2))
print(calc())
5
0
关键字参数
关键字参数允许传入0个或任意个含参数名的参数,这些关键字参数被自动组装成一个dict。
函数除了需要接受必须参数外,还可以接受关键字参数kw,传入关键字参数后会以字典的形式呈现出来
def person(name,age,**kw):
print('name:',name,'age:',age,'other:',kw)
print(person('Michael',30))
print(person('Bob',35,city='Beijing'))
name: Michael age: 30 other: {}
None
name: Bob age: 35 other: {'city': 'Beijing'}
None
关键字参数可以扩展函数功能,接收到调用者提供的更多参数(例如必填项之外的选项)
** +字典名表示把字典内所有的key-value用关键字参数传入到函数 ** kw参数中,kw仅仅是dict的一份拷贝。
命名关键字参数
限制关键字参数的名字,需要用命名关键字参数,在参数之间加上特殊的分隔符 * 即可,分隔符后面的参数即为命名关键字参数。
def person(name,age,*,city,job):
print(name,age,city,job)
person('Jack',24,city='Beijing',job='Engineer')
Jack 24 Beijing Engineer
如果函数定义中已经含有可变参数,那么不需要分隔符,只需要在可变函数后加上命名关键字参数即可。
def person(name,age,*args,city,job):
print(name,age,args,city,job)
person('Jack',24,city = 'Beijing',job='Engineer')
Jack 24 () Beijing Engineer
函数参数的定义顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
3、递归函数
求阶乘的函数 f(n) = n! = 1×2×3×⋅⋅⋅×(n−1)×n=(n−1)!×n=f(n−1)×n
所以f(n)可以表示为n * f(n-1)
def f(n):
if n ==1:
return 1
return n*f(n-1)
print(f(1))
print(f(5))
print(f(100))
1
120
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
使用递归函数可能会导致栈溢出的问题(调用次数过多的时候)
解决递归调用栈递归的方法是尾递归优化
尾递归:在函数返回的时候调用自身本身,且return语句不能包括表达式,能够让递归函数只使用一个栈帧,不出现栈溢出。上面的f(n)在return语句中使用了乘法表达式,不能算作尾递归。
def fact_iter(num,product):
if num==1:
return product
return fact_iter(num-1,num*product)
fact_iter(5,1)
120
上述为尾递归调用,大多数编程语言没有针对尾递归的优化,python也没有,所以即使将f(n)修改成尾递归方式也会导致栈溢出