所有笔记内容参考廖雪峰官网,需要详细了解大家可以去这个网站,我这里主要做笔记方便自己复习
调用函数
Python中内置了许多函数,我们可以直接调用,我们直接从Python官网查看文档以了解函数:http://docs.python.org/3/library/functions.html#abs
例如求一个数的绝对值:abs(-20)
;注意:在调用函数的时候,如果传参个数有问题,或者传入的参数类型不能为函数所接受,那么会有TypeError的错误。这里可能会涉及到一个数据类型转换的问题
数据类型转换
例如:int('123')
是将123字符串转换为整数,类似的还有float,str,bool
等
函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:
a = abs # 变量a指向abs函数
a(-1) # 所以也可以通过a调用abs函数
定义函数
定义函数用到的关键字是def
语句,例如:
def my_fun(x):
if x >= 0:
return x
else:
return -x
print(my_fun(9))
空函数
一个什么事也不做的函数,可以使用pass
语句:
def name():
pass
pass语句什么也不做,但是我们可以用pass作为占位符,比如写函数时候,当还没有想好怎么写的时候,我们可以先放一个pass,使代码能先运行起来,还可以放在其他的语句中:
if age >=18:
pass
参数检查
在调用函数的时候,如果参数个数不正确,那么Python解释器会自动检查出来,并抛出TypeError的错误;
当传入了不恰当的参数类型,内置函数会检查出参数错误,但是如果我们自己定义的函数没有进行参数检查,不会报类似于内置函数的错误,所以我们需要对参数类型进行检查。数据类型的检查可以使用内置函数isinstance()
实现:那么之前定义的my_fun函数可以这么定义
def my_abs(x):
if not isinstance(x, (int, float)):
raise TypeError('bad operand type')
if x >= 0:
return x
else:
return -x
注意:如果在函数中没有return
语句,那么函数会自动return none
返回多个值
比如在游戏中经常需要从一个点移动到另一个点,给出坐标、位移和角度,就可以计算出新的新的坐标:
import 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)
这样的话可以获得x,y的值,但是其实函数返回的仍然是单一的值,我们可以使用一个变量来接收函数的返回值,最后会发现返回的会是一个tuple,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便。
函数的参数
位置参数
在上面的函数中,my_abs(x)
其中的x就是一个函数,my_abs(8)
就是对函数的一个调用,我们也可以使用类似于上面的move(x,y,step,angle=0)
的多个参数,我们可以看到,在函数的参数列表中有一个angle=0的部分。这就是我们所说的默认参数;
默认参数
如果我们在一个函数中有默认函数,我们在调用这个函数的时候,在参数列表中可以省略默认参数的出现,例如对上面move函数的调用,我们可以使用这种语法:move(0,0,100)
;当然,如果我们想改变默认参数的值,我们还是可以使用move(0,0,100,1)
这样的语法。
设置默认参数的注意点
- 必选参数在前,默认参数在后
当函数有多个参数时,变化大的参数放前面,变化小的参数放后面,变化小的参数可以作为默认参数
么默认参数有什么用处呢? 用处就是可以降低调用函数的函数。
注意:默认参数一定指向不可变对象,否则会出现bug;例如:
def add_end(L=[]):
L.append('END')
return L
#第一次调用
print(add_end()) #将会返回['END']
#第二次调用
print(add_end()) #将会返回['END', 'END']
#第三次调用
print(add_end()) #将会返回['END', 'END', 'END']
可见,原因解释如下:
Python函数在定义的时候,默认参数L
的值就被计算出来了,即[]
,因为默认参数L
也是一个变量,它指向对象[]
,每次调用该函数,如果改变了L
的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]
了。
我们可以通过none
这个对象来修改上面的代码
def add_end(L=None):
if L is None:
L = []
L.append('END')
return L
这样的话,无论调用多少次,我们返回的都是[‘END’];
为什么要设计str
、None
这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。
可变参数
可变参数就是参数的个数是可以变化的;可以是0到任意个
当我们不知道参数的个数的时候,我们可以考虑将参数作为一个list或者tuple传进来,这样,函数可以定义为:
def cal(numbers):
sum=0
for n in numbers:
sum=sum+n*n
return sum
#调用函数为下面代码
cal([1,2,3])
#或者
cal((1,2,3))
当我们采用可变参数的时候,函数的调用可以简化成以下:
cal(1,2,3)
所以,我们把函数的参数改为可变参数:
def cal(*numbers):
sum=0
for n in numbers:
sum=sum+n*n
return sum
可以看出,定义可变参数和定义一个list和tuple参数相比,仅仅在参数前面加了一个*
号。在函数的内部,参数numbers接收到的是一个tuple,因此,函数的代码没有改变,但是,当我们在调用函数的时候,我们可以传入包括0的任意个参数
还有一种情况就是已经有一个list或者tuple,需要调用一个可变参数,我们同样可以在函数的参数列表中加上一个*
号,例如:
numbers=[1,2,3]
cal(*numbers)
关键字参数
关键字参数允许你传入0个或者任意个参数,这些可变参数在参数调用时自动组装成一个tuple。而关键字参数允许你传入0个或者任意个含参数名的参数,这些关键字参数在函数内部自动组装成一个dict,例如:
def person(name,age,**kw):
print('name:',name,'age:',age,kw)
person('wan',18)
person('wan',18,city='beijing')
person('wan',18,city='bejing',gender='man')
info={'city':'beijing','gender':'man'}
person('wan',18,**info)
输出为:
name: wan age: 18 {}
name: wan age: 18 {‘city’: ‘beijing’}
name: wan age: 18 {‘city’: ‘bejing’, ‘gender’: ‘man’}
name: wan age: 18 {‘city’: ‘bejing’, ‘gender’: ‘man’}
注意:后面的可变参数可以使用一个dict来存储,在调用的时候可以在参数前面加上**
完成调用
命令关键字参数
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数,至于到底传入了哪些,就需要在函数内部通过检查,例如,还是上面的person函数,我们希望检查是否有city
和gender
参数
def person(name,age,**kw):
if 'city' in kw:
pass
print('有一个city')
if 'gender' in kw:
pass
print('有一个gender')
print('name:',name,'age:',age,kw)
person('wan',18)
person('wan',18,city='beijing')
person('wan',18,city='bejing',gender='man')
info={'city':'beijing','gender':'man'}
person('wan',18,**info)
print('wam',18,city='bejing',gender='man',job='student')
下面是输出:
有一个city
name: wan age: 18 {}
有一个city
name: wan age: 18 {‘city’: ‘beijing’}
有一个city
有一个gender
name: wan age: 18 {‘city’: ‘bejing’, ‘gender’: ‘man’}
有一个city
有一个gender
name: wan age: 18 {‘city’: ‘beijing’, ‘gender’: ‘man’}
name: wam age: 18 {‘city’: ‘bejing’, ‘gender’: ‘man’, ‘job’: ‘student’}
如果我们需要限制关键字的名字,就可以使用命名关键字参数,例如,如果只接受city
和gender
作为关键字参数,那么函数定义如下:
def person(name, age, *, city, gender):
print(name, age, city,gender)
#调用
person('wan',18,city='beijing',gender='man')
和关键字参数**kw
不同,命令关键字参数需要一个分隔符*
,*
后面的参数被视为命名关键字参数
如果函数定义已经存在一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*
了,例如:
def person(name,age,*args,city,job)
print(name,age,args,city,job)
#调用
person('wan',18,'beijing','china',city='loudi',job='stu')
对于上面的函数调用,如果是这个语句person('wan',18,'beijing','china')
;这时候由于缺少参数city和job,调用会报错。
注意:当函数定义中存在一个默认参数值的时候,这个默认参数在调用的时候可以省略,即命令关键字参数可以有缺省值
参数组合
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数可以组合使用。但是,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数
递归函数
一个函数在内部调用自身,这个函数便是递归函数,例如计算n!:
def fact(n):
if n=1:
return 1
return n*fact(n-1)
与java语言中相同,递归函数的优点是定义简单,逻辑清晰,理论上,所有的递归函数都可以写成循环的方式,但是循环的逻辑没有递归清晰。
使用递归函数需要注意防止栈的溢出。在计算机中,函数调用是通过栈这种数据结构来实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层帧。由于栈的大小不是无限的,所以递归调用的次数过多可能会导致溢出。
我觉得学了数据结构这门课,对这个地方的理解还是相对比较简单的。