2. 函数
常用的函数:abs()
求绝对值、max()
可以接收多个参数,求其中最大值。可以用help(max)
来查看所有的max
函数信息。
数据类型转换:Python内置的常用函数还包括数据类型转换函数,比如int()
函数可以把其他数据类型转换为整型。int()
、str()
、bool()
。
函数名其实就是指向一个函数对象的引用。完全可以用其他变量名代替函数名,相当于给函数起了个别名。
2.1定义函数
在Python中,定义一个函数用def
语句,依次写出函数名、括号、括号中间的参数和冒号。然后在缩进块中编写函数体,函数的返回值用return
语句返回。例如:
def my_abs(x):
if(x>0)
return x;
else
return -x;
注意:在函数内部,执行到return
语句时,函数执行完毕,并返回。如果没有return
语句,函数也能正常返回,但是返回值为None
。return None
可以简写为return
.
在Python**交互环境中定义函数时,注意Python会出现...
提示,函数定义结束后,连按两次回车键,会重新返回到>>>
提示符下。如果把my_abs()
函数定义保存到abstest.py文本文件下,那么,可以在该文件目录下**启动Python解释器,用from abstest import my_abs
来导入my_abs()
函数。注意:abstest
是文件名(不包含.py
后缀)。
空函数
如果要定义一个什么也不做的函数,可以在缩进块中使用
pass
语句。def nop(): pass;
pass
可以用来作为占位符,比如现在没想好函数怎么写。可以先放一个pass
。参数检查
调用函数时,如果参数个数不正确,Python解释器会自动检查出来,并抛出
TypeError
。但是,如果参数类型不正确,Python解释器就无法帮我们检查。我们可以在函数体中,加入数据类型检查语句
isinstance()
。比如对my_abs()
函数参数限制只能接收整数和浮点数:def my_abs(x): if not isinstance(x,(int,float)) if(x>0) return x; else return -x;
isinstance()
函数定义:>>> help(isinstance) Help on built-in function isinstance in module builtins: isinstance(obj, class_or_tuple, /) Return whether an object is an instance of a class or of a subclass thereof. A tuple, as in ``isinstance(x, (A, B, ...))``, may be given as the target to check against. This is equivalent to ``isinstance(x, A) or isinstance(x, B) or ...`` etc. >>>
返回多个值
比如,在游戏中经常需要从一个点移动到另外一个点,给出坐标、位移和角度,就可以计算出新的坐标:
import math def move(x,y,step,angle=0): mx = x + step*math.cos(angle) my = y + step * math.sin(angle) return mx,my
import math
就是引入math包,并允许后续使用math
包里的sin
、cos
等函数。然后我们可以使用>>>x,y = move(100,100,60,math.pi/6) >>>print(x,y) 151.96152422706632 70.0
注:看似返回的两个值,其实是一个
tuple
类型的对象小结:
定义函数时,要确定函数名和参数个数,参数类型不用确定。
如果有必要,可以先对参数类型进行检查。
函数体内可以使用
return
随时返回函数结果。函数执行完毕,也没用
return
时,自动return None
函数可以同时返回多个值,但其实就是一个
tuple
2.2 函数的参数
Python函数的定义除了常规的参数外,还可以使用默认参数、可变参数和关键字参数。
2.2.1 位置参数
例如:计算x^n。
def power(x,n):
s = 1
while n>0:
n = n - 1
s = s * n
return s;
对于这个函数power(x,n)
x和n都是位置参数,调用此函数时,传入的两个值按照位置顺序依次赋值给参数x和n。
2.2.2 默认参数
当我们定义了power(x,b)
后,原来的power(x)
函数则无法使用,调用power(5)
时,会报错。此时,可以使用默认参数:
def power(x,n=2):
s = 1
while n>0:
n = n - 1
s = s * n
return s;
此时,调用power(5)
和power(5,2)
都不会出现错误。
设置默认参数时,要注意:
必选参数在前,默认参数在后,否则Python解释器会报错;
当函数有多个参数时,把变化大的参数放在前面,变化小的参数放在后面,变化小的参数就可以设置为默认参数。
有多个默认参数时,调用的时候,既可以按顺序提供默认参数,也可以不按顺序提供部分默认参数。当不按顺序提供部分默认参数时,需要把参数名写上。
默认参数必须指向不变对象。
2.2.3 可变参数
在Python函数中,还可以定义可变参数。可变参数就是指传入的参数个数是可变的。可以是0个、1个或多个。
首先,我们想到的是用list和tuple作为参数,携带多个参数。有了可变参数,只需要在参数前面加上*
即可。定义可变参数:
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
可变参数允许传入0个或任意个参数,这些可变参数在函数调用时自动组装成一个tuple
。
2.2.4 关键字参数
关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装成一个dict
。比如:
def person(name,age,**kw):
print('name:',name,'age:',age,'other:',kw)
当调用此函数时,可以传入任意个关键字参数。kw
指向一个dict
对象。
>>>person('Bob',35,city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
可见,传入的关键字参数在函数体内,转换为了dict
类型。
>>>extra={'city':'Beijing','job':'Engineer'}
>>>person('Jack',32,**extra)
name: Jack age: 24 other: {'city': 'Beijing', job': Engineer'}
**extra
表示把extra
这个dict
的所有key-value
用关键字参数传入到函数的**kw
参数,kw
将获得一个dict
,注意kw
获得的是extra
的一个拷贝,对kw
的改动不会影响到函数外的extra
。
2.2.5命名关键字参数
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw
检查。
还是以person()
函数为例
def person(name,age,**kw):
if 'city' in kw:
#如果有city参数
pass
if 'job' in kw:
#如果有job参数
pass
print('name:',name,'age:',age,'other:',kw)
但是调用者仍然可以传不受限制的关键字参数:
>>>person('Jack',24,city='Beijing',addr='Guangzhou',zipcode='123456')
>>>
如果要限制关键字参数的名字,就可以使用命名关键字参数,例如只接收city
和Job
作为关键字参数。这种方式函数定义如下:
def person(name,age,*,city,job):
print('name',name,'age:',age,'city:',city,'job:',job)
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错。
使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个*
作为特殊分隔符。如果缺少*
,Python解释器将无法识别位置参数和命名关键字参数。
2.2.6 参数组合
在Python中,函数可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数。这5种参数都可以组合使用,但是要注意使用的顺序:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
2.2.7小结
默认参数一定要用不可变对象,如果可变的对象,程序运行时会有逻辑错误。
要注意定义可变参数和关键字参数的语法:
*agrs
是可变参数,args
接收的是一个tuple
**kw
是关键字参数,kw
接收一个dict
可变参数既可以直接传入:
func(1,2,3)
,又可以先组装成list
和tuple
,再通过*args
传入:func(*(1,2,3))
关键字参数既可以直接传入:
func(a=1,b=2)
有可以先组装dict
,再通过**kw
传入:func(**{'a':1,'b':2})
。关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
2.3递归函数
举例,计算n的阶乘
def fact(n):
if n==1:
return 1
return n*fact(n-1)
上面这个递归函数,当n取较大值时,会出现栈溢出异常。递归就是通过栈(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)
可以看到,return fact_iter(num-1,num*product)
仅返回递归函数本身,num-1
和num*product
在函数调用前就已被计算出来。尾递归调用时,如果做了优化栈不会增长,因此,无论多少次调用也不会导致栈溢出。遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没用做优化。所以上述都是‘废话’