Python 函数精讲
1. 调用函数
1.1 核心知识点
- python内置很多函数,可以参考python官方网站了解内置的函数了解每个内置函数的具体用法;
- 每个函数对应的参数个数和参数类型都是不一样的,个数和类型传入错误是会有不同错误提醒的,下面以求绝对值函数abs来进行说明
#参数个数错误
>>> abs(-12.3)
12.3
>>> abs(-12.3,d)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: abs() takes exactly one argument (2 given)
#参数数据类型错误
>>> abs('ddd')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: bad operand type for abs(): 'str'py
1.2 常用的几个数据类型转换函数
hex():整数转换为十六进制字符串格式
int():将字符串转换为整数类型
str():将其他类型转换为字符串
bool():返回True或者False,只要参数不是0,不是空列表,空元组等,都会返回True,其他情况返回False
float():将其他类型转换为浮点数
演示代码:
>>> int('255')
255
>>> int(2.55)
2
>>> float(12)
12.0
>>> float('12')
12.0
>>> float('1234.34')
1234.34
>>> str(['w',3,'e'])
"['w', 3, 'e']"
>>> str(123)
'123'
>>> bool(0)
False
>>> bool(1)
True
>>> bool([2,3])
True
>>> bool([])
False
>>> bool(())
False
>>> bool((1))
True
>>> bool((1,))
True
>>> bool({})
False
>>> bool({'name':'xiao'})
True
>>> hex(123)
'0x7b'
1.3 函数别名
函数名就是指向函数对象的一个引用,所以可以给函数名其别名。
>>> i = int
>>> i(12.5)
12
2. 定义函数
定义函数使用def,然后后面跟上函数名,小括号,小括号里是参数,冒号,然后在缩进块中编写函数体,最后函数返回使用return语句返回。
演示代码:
写一个求绝对值的函数:
def my_abs(x):
if x>= 0:
return x
else:
return -x
print(my_abs(9))
print(my_abs(0))
print(my_abs(-3))
函数的返回值:
函数内部只要遇到return函数就执行结束,并且返回值,因此可以通过条件判断和循环语句来实现复杂的逻辑关系;
如果函数体没有return语句,函数也会返回值,返回值为None,return None可以简写为return.
导入函数:
进入python交互模式,使用form 文件名 import 函数名导入
演示代码:
>>> from test2 import my_abs
>>> my_abs(100)
100
空函数:
函数体使用pass语句来定义一个空函数,pass起到一个占位的作用,保证函数没有语法错误,程序可以运行起来
演示代码:
def kong_hanshu(s):
pass
参数检查:
一个完整的函数定义是应该有参数检查的,在参数出现错误的时候报相应的错误TypeError,而不是造成函数体执行错误。
下面还以自定义求绝对值函数来进行演示:
def my_abs(x):
if x>= 0:
return x
else:
return -x
//缺少参数定义的函数my_abs
>>> from test2 import my_abs
>>> my_abs('A')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Users\cdata\Desktop\test2.py", line 2, in my_abs
if x>= 0: //自定义函数缺少参数类型检查时会造成函数内部if条件判断执行错误
TypeError: '>=' not supported between instances of 'str' and 'int'
>>> abs('A')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: bad operand type for abs(): 'str'
//python内置的abs函数有参数检查步骤,因此在参数检查步骤就会报错,而不会走到函数体内部,对人造成误解
//下面在my_abs上增加函数检查步骤,这样函数就会在第一步进行参数检查,如果类型不对就报错:
>>> 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')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in My_abs
TypeError: bad operand type
返回多个值:
当返回多个值的时候,其实返回的是一个tuple,返回值时单一的,在语法上,当返回一个tuple时可以省略括号,并且多个变量可以按照位置接收tuple里的元素,所以可以将返回多个值的函数一次赋值给不同变量。
下面以移动坐标进行代码演示:
import math //导入包math,这样后面的代码就可以使用这里面的cos,sin,pi等函数
def move(x,y,distance,angle=0):
nx = x+distance*math.cos(angle)
ny = y-distance*math.sin(angle)
return nx,ny
>>> n,m = move(20,20,30,math.pi/6)
>>> print(n,m)
45.98076211353316 5.000000000000002 //返回两个值
>>> r = move(20,20,30,math.pi/6)
>>> print(r)
(45.98076211353316, 5.000000000000002) //其实是返回一个tuple
3. 函数的参数
定义函数的时候,我们把参数的名字和位置确定下来,函数的接口就定义好了,然后调用者只需知道需要传入的参数和返回结果就可以了,而不用明白函数内部复杂的逻辑关系。
python函数定义非常简单,但是灵活性非常大,不但可以定义必选参数,还可以定义默认参数,可选参数,关键字参数,使得函数定义出来的接口不但能够接收复杂的参数,而且还可以简化调用者的代码。
参数分类及作用:
位置参数::调用函数时按照位置,将传入的值按照位置依次赋给参数,比如下面case中的x,n就是两个位置参数
Case::求x的n次方的函数
def power(x,n):
s =1
while n>0:
s=s*x
n=n-1
return s
必选参数:不给位置参数赋默认值,就是必选参数
默认参数:给位置参数赋个默认值,在传入参数的时候可以不传入这个参数,不但简化函数的调用,而且少传入这个参数时也不会报错,并且在想实现复杂的调用时,又可以传递更多的参数来实现。
默认参数必须在必选参数后面,我们一般将变化大的参数作为必选参数,变化小的作为默认参数,比如一个学生信息表中,姓名和性别就可以作为必选参数,而城市和年龄这两项变换小的就可以作为默认参数。
Case1::求x的n次方的函数,默认n=2
def power(x,n=2):
s =1
while n>0:
s=s*x
n=n-1
return s
>>> power(3)
9
>>> power(3,4)
81
Case2::按照输入输出学生信息表
def class_info(name,gender,age=3,city='shenzhen'):
print('name is:',name)
print('gender is:',gender)
print('age is:',age)
print('city is:',city)
>>> class_info('xiao','M') //只传入必选参数
name is: xiao
gender is: M
age is: 3
city is: shenzhen
>>> class_info('xiao','M',6) //按顺序传入参数
name is: xiao
gender is: M
age is: 6
city is: shenzhen
>>> class_info('xiao','M',6,'beijing') //按顺序传入参数
name is: xiao
gender is: M
age is: 6
city is: beijing
>>> class_info('xiao','M',city='beijing') //不按顺序传入参数,需要把参数名写上
name is: xiao
gender is: M
age is: 3
city is: beijing
Case3::默认参数是可变对象时的坑
>>> def add_END(L=[]):
... L.append('END')
... return L
...
>>>
>>> add_END([1,2,3])
[1, 2, 3, 'END']
>>> add_END([2,3,4])
[2, 3, 4, 'END'] //如果给默认参数传入值,那么是没有问题的
>>> add_END()
['END']
>>> add_END()
['END', 'END'] //如果使用默认参数,那么上一次调用计算出来的值将会存储在可变变量中,下次调用时会调用
//因此定义默认参数时一定不能是不可变对象
//所以上面的函数可以改成None这个不变对象
def add_end(L=None):
if L == None:
L =[]
L.append('END')
return L
为什么设置str,None这些不变变量呢?
不变对象一旦建立,元素就确定了,不用担心因为修改数据而造成的错误,并且多任务环境下读取对象不用加锁,同时读取没有问题,因此我们在设计程序时优先设置不变对象。
可变参数:使用*参数的格式定义一个可变参数,应用在参数个数不确定的情况下,与list参数或者tuple参数是一个效果,只不过在函数调用时采用可变参数的格式会更加简洁。
1.可变参数可以传入0个或者任意多个参数,在函数调用时自动封装成一个tuple。
2.可变参数可以直接传入fun(1,2,3),也可以先封装成list或者tuple,然后在前面加上*号来传入fun(*[1,2,3])
Case:可变参数和list/tuple参数的对比,求a2 + b2 + c2 + ……的和
//list/tuple参数方式
def calc(L):
sum=0
for n in L:
sum = sum + n*n
return sum
>>> calc([2,3,4])
29
>>> calc((2,3,4))
29
>>> calc(2,3,4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: calc() takes 1 positional argument but 3 were given //调用的时候必须封装成list或者tuple,否则会报错
>>> L = [2,3,4]
>>> calc(L)
29 //对于已经存在的llist或者tupler,可以直接调用
//可变参数方式
def calc(*L):
sum=0
for n in L:
sum = sum + n*n
return sum
>>> calc(L[0],L[1],L[2])
29
>>> calc(*L)
29 //调用已经存在的list或者tuple时,使用*list或者*tuple的方式调用很简便
>>> calc(2,3,4)
29 //传入list或者tuple时也可以做简化
关键字参数:可以接收0个或者任意多个参数,在函数调用时封装成dict
关键字参数的作用时函数的扩展,像用户注册这种功能的实现,前面的姓名,性别是必选参数,其他的年龄城市可以使用关键字参数做成可选功能。
1.关键字参数可以直接传入fun(a=1,b=2),也可以再已经封装好的dict前面加**,如fun(**{‘a’:1,‘b’:2})
Case:用户注册信息函数的定义和调用
def user_info(name,genter,**kw):
print('name:',name,'genter:',genter,'other:',kw)
>>> user_info('xiao','M',age=18,city='shenzhen')
name: xiao genter: M other: {'age': 18, 'city': 'shenzhen'} //直接传入,关键字参数被封装成dict
>>> dict1={'age':18,'city':'shenzhen'}
>>> user_info('xiao','M',age=dict1['age'])
name: xiao genter: M other: {'age': 18} //对于以存在的dict的调用
>>>
>>> user_info('xiao','M',**dict1)
name: xiao genter: M other: {'age': 18, 'city': 'shenzhen'} //对于存在的dict的调用,简便方法
//注意:**dict1是将dict1内部的key-value作为关键字参数传入到函数的**kw参数,而外部的dict1是没有变化的。
命名关键字参数:
1.关键字参数无法对传入的参数进行限制,命名关键字参数可以对关键字参数进行限制,定义的关键字参数在调用的时候必须传入。
2.当命名关键字参数与位置参数前面使用*隔开,否则命名关键字参数将会被视为位置参数
3.如果命名关键字参数前面有可变参数,那么*号分隔符可以省略。
4.命名关键字参数可以定义默认值。
Case:
def user_info(name,genter,*,city,age):
print('name:',name,'genter:',genter,'city:',city,'age:',age)
>>> user_info('xiao','M',age=19,city='shenzhen')
name: xiao genter: M city: shenzhen age: 19 //命名关键字参数
def user_info(name,genter,*args,city,age):
print('name:',name,'genter:',genter,'agrs:',args,'city:',city,'age:',age)
>>> user_info('xiao','M',(1,2,3),city='beijing',age=18)
name: xiao genter: M agrs: ((1, 2, 3),) city: beijing age: 18 //带可变变量的命名关键字参数
def user_info(name,genter,*,city='shenzhen',age):
print('name:',name,'genter:',genter,'city:',city,'age:',age)
>>> user_info('xiao','M',age=18)
name: xiao genter: M city: shenzhen age: 18
>>> user_info('xiao','M',age=18,city='guangzhou')
name: xiao genter: M city: guangzhou age: 18 //命名关键字参数赋予默认值
参数组合:上面介绍的五种参数可以组合使用。
1.组合中顺序为,必选参数,默认参数,可变参数,命名关键字参数,关键字参数
2.调用函数时会根据参数位置和参数名将将参数传进去
3.无论函数如何定义,都可以使用func(*args,**kw)的格式来调用,可变参数和关键字参数时pyhton的习惯写法,最好这样使用
4.虽然参数可以组合,但是尽量减少组合的参数类型,否则会增加代码的可读性
Case:
def fun1(a,b,c=5,*arg,**kw):
print('a:',a,'b:',b,'c:',c,'arg:',arg,'kw:',kw)
>>> fun1(1,2,3,*[6,7,8,9,10],d=7,e=10)
a: 1 b: 2 c: 3 arg: (6, 7, 8, 9, 10) kw: {'d': 7, 'e': 10} //传入必选参数的形式
>>> fun1(*[6,7,8,9,10],d=7,e=10)
a: 6 b: 7 c: 8 arg: (9, 10) kw: {'d': 7, 'e': 10}
//不传入必选参数,传入可变参数,将可变参数前面的值根据位置依次传给必选参数和默认参数
练习:计算一个或多个数的积
def product(*x):
if x==():
raise TypeError //这里需要加入一个错误处理,当传入的是空tuple时,需要报错,提前终止程序
s = 1 //空tuple不是None,所以这里if判断不能写成x==None,但是可以写成x is ()
for n in x:
s = s*n
return s
# 测试
print('product(5) =', product(5))
print('product(5, 6) =', product(5, 6))
print('product(5, 6, 7) =', product(5, 6, 7))
print('product(5, 6, 7, 9) =', product(5, 6, 7, 9))
if product(5) != 5:
print('测试失败!')
elif product(5, 6) != 30:
print('测试失败!')
elif product(5, 6, 7) != 210:
print('测试失败!')
elif product(5, 6, 7, 9) != 1890:
print('测试失败!')
else:
try:
product()
print('测试失败!')
except TypeError:
print('测试成功!')
4. 递归函数
递归函数就是在函数体内部再次调用函数本身,比如n!就是一个典型的递归函数;
递归函数的缺点是容易栈溢出。
递归函数的优点是逻辑清晰,容易理解。
所有的递归函数都可以写成循环的方式,但是循环的逻辑不如递归清晰
没有循环函数的编程语言采用尾递归来实现循环。
练习:
def f(n):
if n == 1:
return 1
return n*f(n-1)
>>> f(1)
1
>>> f(3)
6
>>> f(5)
120
>>> f(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
栈溢出解释:
在计算机中,函数的调用是使用栈这种数据结构,函数调用会在栈上添加一层帧,函数返回会减去一层帧,所以如果函数调用次数太多就会造成栈溢出。
尾递归:
尾递归如果做了优化,在函数调用时就不会造成栈溢出,但是包括python在内的大部分编程语言都没有对尾递归做优化,所以即使做了尾递归还是会造成栈溢出。