Python学习笔记Chaper2
Python学习之函数
一级目录
二级目录
三级目录
2.1函数
如果经学习过其他的高级语言,如C语言,java等,函数的概念不再赘述。
python中的函数和C中的函数概念相当,和java中的方法概念相当。
2.1.1调用函数
python有强大的函数库,可以供我们进行调用。
要调用一个函数,就要知道函数的名称和参数。在python交互环境中输入help(functionName)回车,即可知道该函数的使用方法,这一点类似于Matlab的语法,以abs函数为例:
在进行函数调用时,若函数的参数不正确,就会报TypeError
的错误。函数的参数不正确包括:
- 参数类型错误
- 参数数量错误
在上一章的再议input中,提到要将input()的字符串类型转换成int()类型才能进行后续计算,调用的就是int
方法,和这里是一样的。
2.1.2定义函数
2.1.2.1函数的定义方法
在python中,定义一个函数要使用def
语句,再依次写出函数名,括号,括号中的参数和冒号。
如:
#记住python中的函数调用格式:
def myAbs(a):
if a>=0:
return a
else:
return -a
print(myAbs(-102))
在Python交互环境中定义函数时,注意Python会出现...
的提示。函数定义结束后需要按两次回车重新回到>>>
提示符下:
┌────────────────────────────────────────────────────────┐
│Command Prompt - python - □ x │
├────────────────────────────────────────────────────────┤
│>>> def my_abs(x): │
│... if x >= 0: │
│... return x │
│... else: │
│... return -x │
│... │
│>>> my_abs(-9) │
│9 │
│>>> _ │
│ │
│ │
└────────────────────────────────────────────────────────┘
2.1.2.2空函数的使用
如果想定义一个什么事也不做的空函数,可以用pass
语句:
def nop():
pass
pass
语句什么都不做,那有什么用?实际上pass
可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass
,让代码能运行起来。
pass
还可以用在其他语句里,比如:
if age >= 18:
pass
缺少了pass
,代码运行就会有语法错误。
2.1.2.3参数类型的检查
调用函数时,如果参数个数不对,Python解释器会自动检查出来,并抛出TypeError
:
>>> my_abs(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: my_abs() takes 1 positional argument but 2 were given
让我们修改一下my_abs
的定义,对参数类型做检查,只允许整数和浮点数类型的参数。数据类型检查可以用内置函数isinstance()
实现:
#记住python中的函数调用格式:
def myAbs(a):
if not isinstance(a,(int,float)):
raise TypeError('bad operand type')
if a>=0:
return a
else:
return -a
print(myAbs(-102))
2.1.2.4python和其他语言的函数定义对比
-
java
java中定义方法:
/*[修饰符列表] 返回值类型 方法名 (形式参数列表){ 方法体 } 如: */ public static void main(String[] args){ System.out.println("mian方法!") }
上述的
isinstance()
函数就类似于java中的instanceof
-
C语言
c语言中定义函数
//返回值类型 函数名 (形式参数列表) int main(){ return xxx }
可以看出python的函数定义和C语言的函数定义基本相同,只是要增加一个def关键字和冒号
学习过C语言可以知道,C语言的return语句只能返回一个参数,但是python不同
python的return语句一次可以返回多个值
import math # return语句可以返回多个参数 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
包,并允许后续代码引用math
包里的sin
、cos
等函数但实际上我们要知道,python的return实际上也只是返回了一个值:实际返回的是一个tuple元组。这和matlab的函数返回有些相似。
import math def quadratic(a, b, c): x1=(-b+math.sqrt(b**2-4*a*c))/(2*a) x2=(-b-math.sqrt(b**2-4*a*c))/(2*a) return x1,x2 print(quadratic(2,3,1))
通过这个例子,想要表述的是了解python中的乘方应该如何表示,一开始使用了matlab中的写法,运行期间报错,正确的写法应为:
n**m #表示n的m次方
2.1.3函数的参数
2.1.3.1默认参数
函数的参数数量和类型要正确,否则会报错,这一点我们不再赘述。这里想要说的是关于python的默认参数。其实在Matlab中也有默认参数,只不过要使用varargin参数表的形式。
回到正题:python的默认参数
假设我们编写了下面这样的程序:
def myPow(x,n):
result=x
while n>1:
result=result*x
n=n-1
return result
print(myPow(5,3))
参数输入5和3时,程序是完全没有问题的,但是如果我们只输入5,根据上面参数错误的情况描述,会报错。考虑到我们经常要用到的是平方运算,我们可以进行这样的修改:当只输入一个参数时,默认进行平方运算,能否做到呢?这时候就需要使用默认参数了。
def myPow(x,n=2):
result=x
while n>1:
result=result*x
n=n-1
return result
print(myPow(5))
print(myPow(5,3))
运行结果如下:
25
125
程序运行正常,这就是默认参数的用法。特别要注意以下几点:
- 一是必选参数在前,默认参数在后,否则python的解释器会报错
- 二是如何进行默认参数的选择,遵循下面的原则:
- 变化大的参数作为必选参数
- 变化小的参数作为默认参数
默认参数的好处:降低调用函数的难度
举个例子,我们写个一年级小学生注册的函数,需要传入name
和gender
两个参数:
def enroll(name, gender):
print('name:', name)
print('gender:', gender)
这样,调用enroll()
函数只需要传入两个参数:
>>> enroll('Sarah', 'F')
name: Sarah
gender: F
如果要继续传入年龄、城市等信息怎么办?这样会使得调用函数的复杂度大大增加。
我们可以把年龄和城市设为默认参数:
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
只有与默认参数不符的学生才需要提供额外的信息:
enroll('Bob', 'M', 7)
enroll('Adam', 'M', city='Tianjin')
可见,默认参数降低了函数调用的难度,而一旦需要更复杂的调用时,又可以传递更多的参数来实现。无论是简单调用还是复杂调用,函数只需要定义一个。
def informationGet(name,grade,gender,age=6,city="黄山"):
print("name:",name)
print("grade:",grade)
print("gender:",gender)
print("age:",age)
print("city:",city)
informationGet("张丽丽","大二","女","19")
informationGet("李华","一年级","男")
默认参数“坑”:
我们编写下面的代码:
def add_end(L=[]):
L.append('END')
return L
print(add_end([1,2,3]))
print(add_end([2,3,4,5]))
print(add_end(["猫在走猫步"]))
print(add_end(["鸟儿在飞翔","风筝在飞"]))
print(add_end())
print(add_end())
print(add_end())
运行结果如下:
可以看出在不使用默认参数的情况下,我们如何调用都不会有问题,输出永远正确。
但是使用默认参数的情况下,似乎程序记住了’END’。原因如下:
Python函数在定义的时候,默认参数L
的值就被计算出来了,即[]
,因为默认参数L
也是一个变量,它指向对象[]
,每次调用该函数,如果改变了默认参数L
的内容,则下次调用时,默认参数的内容就会上次改变的那个结果了,不再是函数定义时的[]
了。
定义默认参数要牢记一点:默认参数必须指向不变对象!
修改如下:
#默认参数始终要指向 不变对象!!!!
def add_end(L=None):
if L is None:
L=[]
L.append('END')
return L
print(add_end([1,2,3]))
print(add_end([2,3,4,5]))
print(add_end(["猫在走猫步"]))
print(add_end(["鸟儿在飞翔","风筝在飞"]))
print(add_end())
print(add_end())
print(add_end())
为什么要设计str
、None
这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。
2.1.3.2可变参数
可变参数意为参数的数量是可变的,可以是0,1,2,…个
给出例子:要求求出任意一串数据的和: a 2 + b 2 + . . . . + n 2 a^2+b^2+....+n^2 a2+b2+....+n2
要定义这个函数,必须要知道数据的个数,我们可以先把数据封装进一个list或者tuple,再从list或者tuple中提取数据进行计算:
def cal(numbers):
sum=0
for x in numbers:
sum=sum+x**2
return sum
print(cal([1,2,3,4,5]))
但是我们调用的时候必须组装出一个list或者tuple,即第六行cal([1,2,3,4,5])
的步骤,但是如果使用可变参数的话,代码可以化简为:
#使用可变参数的求平方和
def cal2(*numbers):
sum=0
for x in numbers:
sum=sum+x**2
return sum
print(cal2(1,2,3,4,5))
但是如果已经有组装好的list或者tuple呢?我们直接将该list或者tuple传入,在list或者tuple名前加上*即可,此时表示接受到了一个tuple,代码如下:
#如果已经够造出一个list或者tuple,且要使用可变参数的话:
nums=[1,2,3,4,5]
print(cal2(*nums))
#直接采取在前面加上*号的方式即可
2.1.3.3关键字参数
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。
关键字参数允许传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
编写下面的代码:
#函数的关键字参数
#允许传入0个或任意个参数,这些参数在内部自动组成一个dict
def infoInput(name,age,**kw):
print("name:",name)
print("age:",age)
print("others:",kw)
#特别注意这里的键值对的组成方法,要使用key=value的方式:
infoInput("Judy",17,school="西电")
关键字参数有什么用?它可以扩展函数的功能。比如,在person
函数里,我们保证能接收到name
和age
这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。
同时,和可变参数类似的是,我们也可以先组装出来一个dict,然后传进去,也要注意要使用key=value的方式。这里就要知道如何根据key寻找value了,直接使用索引,即key值放在方括号中即可,和赋值式子类似,直接返回value值。代码如下:
infoDict={"居住地":"黄山","性别":"女"}
infoInput("Judy",19,location=infoDict["居住地"],gender=infoDict["性别"])
上面写法若是键值对过多的话就变得有些复杂了,可以改成下面的简化版:
#键值对较多的情况下,下面的写法更方便:
extra={"居住地":"黄山","性别":"男","婚姻状态":"已婚","学历":"本科"}
infoInput("Mike",21,**extra)
代码运行结果:
name: Judy
age: 19
others: {'location': '黄山', 'gender': '女'}
name: Mike
age: 21
others: {'居住地': '黄山', '性别': '男', '婚姻状态': '已婚', '学历': '本科'}
**extra
表示把extra
这个dict的所有key-value用关键字参数传入到函数的**kw
参数,kw
将获得一个dict,注意kw
获得的dict是extra
的一份拷贝,对kw
的改动不会影响到函数外的extra
。
2.1.3.4命名关键字参数
上面一小节讲述了关键字参数:允许传入任意个参数,在内部在动组装成一个dict。但是究竟传入了哪些,我们要进行检查:
def person(name,age,**kw):
if 'city' in kw:
#含有city参数
pass
if 'job' in kw:
#含有job参数
pass
print("name:",name,"age:",age,"others:",kw)
person("WangEr",12,**{'city':'BeiJing','job':'student'})
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city
和job
作为关键字参数。这种方式定义的函数如下:
#关键字命名参数
#如果要限制只能接受job和city的参数,那么代码修改为:
def person(name,age,*,city,job):
print("name:",name,"age:",age,"city:",city,"job:",job)
person("WangEr",12,**{'city':'BeiJing','job':'student'})
运行结果如下:
name: WangEr age: 12 city: BeiJing job: student
此时如果传入过多的参数,在编译阶段就会报错:got an unexpected keyword ‘birth’
可以看出和关键字参数**kw
不同,命名关键字参数需要一个特殊分隔符*
,*
后面的参数被视为命名关键字参数。
调用方式如下:
person("Amily",20,city="BeiJing",job="student")
如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*
了:
def person(name, age, *args, city, job):
print(name, age, args, city, job)
2.1.3.5参数组合
#参数组合
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,3,*[1,2,3],**{"Mike":12,"Judy":18})
f2(1,3,d="fw",**{"Mike":5})#省略参数c
2.1.3递归函数
python的递归函数和C语言基本一样,不予赘述,给出下面的例子:
#编写一个递归函数
def fact(n):
if n == 1:
return 1
else:
return n*fact(n-1)
print(fact(5))
#参数组合
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,3,*[1,2,3],**{"Mike":12,"Judy":18})
f2(1,3,d="fw",**{"Mike":5})#省略参数c
2.1.3递归函数
python的递归函数和C语言基本一样,不予赘述,给出下面的例子:
#编写一个递归函数
def fact(n):
if n == 1:
return 1
else:
return n*fact(n-1)
print(fact(5))