Python复习笔记——函数的设计与使用
文章目录
- Python复习笔记——函数的设计与使用
- 形参与实参
- 参数类型
- 位置参数
- 默认值参数
- 关键参数
- 可变长参数
- 参数传递的序列解包
- return语句
- 变量作用域
- lambda表达式
- map()函数
- recude()函数
- filter()
- 生成器函数
- 函数形参不需要声明类型,也不需要指定函数返回值类型
- 即使该函数不需要接收任何参数,也必须保留一对空的圆括号
- 括号后面的冒号必不可少
- 函数体相对于def关键字必须保持一定的空格缩进
- Python允许嵌套定义函数
形参与实参
- 函数定义时括弧内为形参,一个函数可以没有形参,但是括弧必须要有,表示该函数不接受参数
- 函数调用时,将实参的引用传递给形参
- 在定义函数时,对参数个数并没有限制,如果有多个形参,需要使用逗号进行分隔
- 对于绝大多数情况下,在函数内部直接修改形参的值不会影响实参,而是创建一个新变量。
>>> def addOne(a):
print(id(a), ':', a)
a += 1
print(id(a), ':', a)
>>> v = 3
>>> id(v)
1599055008
>>> addOne(v)
1599055008 : 3
1599055040 : 4
>>> v
3
>>> id(v)
1599055008
- 在有些情况下,可以通过特殊的方式在函数内部修改实参的值
>>> def modify(v): # 使用下标修改列表元素值
v[0] = v[0]+1
>>> a = [2]
>>> modify(a)
>>> a
[3]
>>> def modify(v, item): # 使用列表的方法为列表增加元素
v.append(item)
>>> a = [2]
>>> modify(a,3)
>>> a
[2, 3]
- 也就是说,如果传递给函数的实参是可变序列,并且在函数内部使用下标或可变序列自身的原地操作方法增加、删除元素或修改元素时,实参也得到相应的修改
>>> def modify(d): #修改字典元素值或为字典增加元素
d['age'] = 38
>>> a = {'name':'Dong', 'age':37, 'sex':'Male'}
>>> a
{'age': 37, 'name': 'Dong', 'sex': 'Male'}
>>> modify(a)
>>> a
{'age': 38, 'name': 'Dong', 'sex': 'Male'}
参数类型
- 在Python中,函数参数有很多种:可以为位置参数、默认值参数、关键参数、可变长度参数等等
- Python在定义函数时不需要指定形参的类型,完全由调用者传递的实参类型以及Python解释器的理解和推断来决定
位置参数
- 位置参数(positional arguments)是比较常用的形式,调用函数时实参和形参的顺序必须严格一致,并且实参和形参的数量必须相同
>>> def demo(a, b, c):
print(a, b, c)
>>> demo(3, 4, 5) #按位置传递参数
3 4 5
>>> demo(3, 5, 4)
3 5 4
>>> demo(1, 2, 3, 4) #实参与形参数量必须相同
TypeError: demo() takes 3 positional arguments but 4 were given
默认值参数
- 调用带有默认值参数的函数时,可以不对默认值参数进行赋值,也可以为其赋值,具有很大的灵活性
>>> def say(message, times=1 ):
print(message*times)
>>> say('hello')
hello
>>> say('hello',3)
hello hello hello
>>> say('hi',7)
hi hi hi hi hi hi hi
- 下面的函数使用指定分隔符将列表中所有字符串元素连接成一个字符串
>>> def join(lst, sep=None):
return (sep or ' ').join(lst)
>>> aList = ['a', 'b', 'c']
>>> join(aList)
'a b c'
>>> join(aList, ',')
'a,b,c'
- 注意:默认值参数必须出现在函数参数列表的最右端,任何一个默认值参数右边不能有非默认值参数
>>> def func(a=3, b, c=5): # 失败,带默认值的参数后面有不带默认值的参数
print(a, b, c)
SyntaxError: non-default argument follows default argument
>>> def func(a=3, b): # 失败,带默认值的参数后面有不带默认值的参数
print(a, b)
SyntaxError: non-default argument follows default argument
>>> def func(a, b, c=5): # 成功
print(a, b, c)
- 另外,默认值参数如果使用不当,会导致很难发现的逻辑错误
def demo(newitem,old_list=[]):
old_list.append(newitem)
return old_list
print(demo('5',[1,2,3,4]))
print(demo('aaa',['a','b']))
print(demo('a'))
print(demo('b'))
[1, 2, 3, 4, '5']
['a', 'b', 'aaa']
['a']
['a', 'b']
- 原因在于默认值参数的赋值只会在函数定义时被解释一次。当使用可变序列作为参数默认值时,一定要谨慎操作,修改为以下形式
def demo(newitem,old_list=None):
if old_list is None:
old_list=[]
new_list = old_list[:]
new_list.append(newitem)
return new_list
print(demo('5',[1,2,3,4]))
print(demo('aaa',['a','b']))
print(demo('a'))
print(demo('b'))
关键参数
- 通过关键参数,实参顺序可以和形参顺序不一致,但不影响传递结果,避免了用户需要牢记位置参数顺序的麻烦
>>> def demo(a,b,c=5):
print(a,b,c)
>>> demo(3,7)
3 7 5
>>> demo(a=7,b=3,c=6)
7 3 6
>>> demo(c=8,a=9,b=0)
9 0 8
可变长参数
- 可变长度参数主要有两种形式:在参数名前加1个星号*或2个星号**
- *parameter用来接收多个位置实参并将其放在元组中
- **parameter接收多个关键参数并存放到字典中
>>> def demo(*p):
print(p)
>>> demo(1,2,3)
(1, 2, 3)
>>> demo(1,2)
(1, 2)
>>> demo(1,2,3,4,5,6,7)
(1, 2, 3, 4, 5, 6, 7)
>>> def demo(*p):
print(p)
>>> demo(1,2,3)
(1, 2, 3)
>>> demo(1,2)
(1, 2)
>>> demo(1,2,3,4,5,6,7)
(1, 2, 3, 4, 5, 6, 7)
- 几种不同类型的参数可以混合使用,但是不建议这样做
>>> def func_4(a, b, c=4, *aa, **bb):
print(a,b,c)
print(aa)
print(bb)
>>> func_4(1,2,3,4,5,6,7,8,9,xx='1',yy='2',zz=3)
1 2 3
(4, 5, 6, 7, 8, 9)
{'xx': '1', 'yy': '2', 'zz': 3}
>>> func_4(1,2,3,4,5,6,7,xx='1',yy='2',zz=3)
1 2 3
(4, 5, 6, 7)
{'xx': '1', 'yy': '2', 'zz': 3}
参数传递的序列解包
- 传递参数时,可以通过在实参序列前加一个星号将其解包,然后传递给多个单变量形参
>>> def demo(a, b, c):
print(a+b+c)
>>> seq = [1, 2, 3]
>>> demo(*seq)
6
>>> tup = (1, 2, 3)
>>> demo(*tup)
6
>>> dic = {1:'a', 2:'b', 3:'c'}
>>> demo(*dic)
6
>>> Set = {1, 2, 3}
>>> demo(*Set)
6
>>> demo(*dic.values())
abc
- 如果函数实参是字典,可以在前面加两个星号进行解包,等价于关键参数
>>> def demo(a, b, c):
print(a+b+c)
>>> dic = {'a':1, 'b':2, 'c':3}
>>> demo(**dic)
6
>>> demo(a=1, b=2, c=3)
6
>>> demo(*dic.values())
6
- 注意:调用函数时对实参序列使用一个星号*进行解包后的实参将会被当做位置参数对待,并且会在关键参数和使用两个星号**进行序列解包的参数之前进行处理
>>> def demo(a, b, c): #定义函数
print(a, b, c)
>>> demo(*(1, 2, 3)) #调用,序列解包
1 2 3
>>> demo(1, *(2, 3)) #位置参数和序列解包同时使用
1 2 3
>>> demo(1, *(2,), 3)
1 2 3
>>> demo(a=1, *(2, 3)) #序列解包相当于位置参数,优先处理
Traceback (most recent call last):
File "<pyshell#26>", line 1, in <module>
demo(a=1, *(2, 3))
TypeError: demo() got multiple values for argument 'a'
>>> demo(b=1, *(2, 3))
Traceback (most recent call last):
File "<pyshell#27>", line 1, in <module>
demo(b=1, *(2, 3))
TypeError: demo() got multiple values for argument 'b'
>>> demo(c=1, *(2, 3))
2 3 1
>>> demo(**{'a':1, 'b':2}, *(3,)) #序列解包不能在关键参数解包之后
SyntaxError: iterable argument unpacking follows keyword argument unpacking
>>> demo(*(3,), **{'a':1, 'b':2})
Traceback (most recent call last):
File "<pyshell#30>", line 1, in <module>
demo(*(3,), **{'a':1, 'b':2})
TypeError: demo() got multiple values for argument 'a'
>>> demo(*(3,), **{'c':1, 'b':2})
3 2 1
return语句
- return语句用来从一个函数中返回一个值,同时结束函数
- 对于以下情况,Python将认为该函数以return None结束,返回空值
- 函数没有return语句
- 函数有return语句但是没有执行到
- 函数有return也执行到了,但是没有返回任何值
- 在调用函数或对象方法时,一定要注意有没有返回值
>>> a_list = [1, 2, 3, 4, 9, 5, 7]
>>> print(sorted(a_list))
[1, 2, 3, 4, 5, 7, 9]
>>> print(a_list)
[1, 2, 3, 4, 9, 5, 7]
>>> print(a_list.sort())
None
>>> print(a_list)
[1, 2, 3, 4, 5, 7, 9]
变量作用域
- 变量起作用的代码范围称为变量的作用域,不同作用域内变量名可以相同,互不影响
- 在函数内部定义的普通变量只在函数内部起作用,称为局部变量。当函数执行结束后,局部变量自动删除,不再可以使用
- 局部变量的引用比全局变量速度快
- 全局变量会增加函数之间的隐式耦合
全局变量可以通过关键字global来定义。这分为两种情况
- 一个变量已在函数外定义,如果在函数内需要为这个变量赋值,并要将这个赋值结果反映到函数外,可以在函数内使用global声明其为全局变量
- 一个变量已在函数外定义,如果在函数内需要为这个变量赋值,并要将这个赋值结果反映到函数外,可以在函数内使用global声明其为全局变量
>>> def demo():
global x
x = 3
y = 4
print(x,y)
>>> x = 5
>>> demo()
3 4
>>> x
3
>>> y
NameError: name 'y' is not defined
>>> del x
>>> x
NameError: name 'x' is not defined
>>> demo()
3 4
>>> x
3
>>> y
NameError: name 'y' is not defined
- 注意:在某个作用域内任意位置只要有为变量赋值的操作,该变量在这个作用域内就是局部变量,除非使用global进行了声明
>>> x = 3
>>> def f():
print(x) #本意是先输出全局变量x的值,但是不允许这样做
x = 5 #有赋值操作,因此在整个作用域内x都是局部变量
print(x)
>>> f()
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
f()
File "<pyshell#9>", line 2, in f
print(x)
UnboundLocalError: local variable 'x' referenced before assignment
- 如果局部变量与全局变量具有相同的名字,那么该局部变量会在自己的作用域内隐藏同名的全局变量
>>> def demo():
x = 3 #创建了局部变量,并自动隐藏了同名的全局变量
>>> x = 5
>>> x
5
>>> demo()
>>> x #函数执行不影响外面全局变量的值
5
- 除了局部变量和全局变量,Python还支持使用nonlocal关键字定义一种介于二者之间的变量,或称闭包作用域变量。关键字nonlocal声明的变量会引用距离最近的非全局作用域的变量,要求声明的变量已经存在,关键字nonlocal不会创建新变量。
def scope_test():
def do_local():
spam = "我是局部变量"
def do_nonlocal():
nonlocal spam # 这时要求spam必须是已存在的变量
spam = "我不是局部变量,也不是全局变量"
def do_global():
global spam # 如果全局作用域内没有spam,就自动新建一个
spam = "我是全局变量"
spam = "原来的值"
do_local()
print("局部变量赋值后:", spam) # 原来的值
do_nonlocal()
print("nonlocal变量赋值后:", spam) # 我不是局部变量,也不是全局变量
do_global()
print("全局变量赋值后:", spam) # 我不是局部变量,也不是全局变量
scope_test()
print("全局变量:", spam) # 我是全局变量
lambda表达式
- lambda表达式可以用来声明匿名函数(也可以定义具名函数),也就是没有函数名字的临时使用的小函数,尤其适合需要一个函数作为另一个函数参数的场合
- lambda表达式只可以包含一个表达式,该表达式可以任意复杂,其计算结果可以看作是函数的返回值
>>> f = lambda x, y, z: x+y+z #可以给lambda表达式起名字
>>> f(1,2,3) #像函数一样调用
6
>>> g = lambda x, y=2, z=3: x+y+z #参数默认值
>>> g(1)
6
>>> g(2, z=4, y=5) #关键参数
11
>>> L = [(lambda x: x**2), #匿名函数
(lambda x: x**3),
(lambda x: x**4)]
>>> print(L[0](2),L[1](2),L[2](2)) #调用lambda表达式
4 8 16
>>> D = {'f1':(lambda:2+3), 'f2':(lambda:2*3), 'f3':(lambda:2**3)}
>>> print(D['f1'](), D['f2'](), D['f3']())
5 6 8
>>> L = [1,2,3,4,5]
>>> print(list(map(lambda x: x+10, L))) #lambda表达式作为函数参数
[11, 12, 13, 14, 15]
>>> L
[1, 2, 3, 4, 5]
>>> def demo(n):
return n*n
>>> demo(5)
25
>>> a_list = [1,2,3,4,5]
>>> list(map(lambda x: demo(x), a_list)) #在lambda表达式中调用函数
#等价于list(map(demo, a_list))
[1, 4, 9, 16, 25]
>>> data = list(range(20)) #创建列表
>>> data
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> import random
>>> random.shuffle(data) #打乱顺序
>>> data
[4, 3, 11, 13, 12, 15, 9, 2, 10, 6, 19, 18, 14, 8, 0, 7, 5, 17, 1, 16]
>>> data.sort(key=lambda x: x) #和不指定规则效果一样
>>> data
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
>>> data.sort(key=lambda x: len(str(x))) #按转换成字符串以后的长度排序
>>> data
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> data.sort(key=lambda x: len(str(x)), reverse=True)
#降序排序
>>> data
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> import random
>>> x = [[random.randint(1,10) for j in range(5)] for i in range(5)]
#包含5个子列表的列表
#每个子列表中包含5个1到10之间的随机数
>>> for item in x:
print(item)
[5, 6, 8, 7, 4]
[1, 5, 3, 9, 4]
[9, 6, 10, 7, 6]
[8, 2, 7, 1, 6]
[1, 7, 5, 3, 5]
>>> y = sorted(x, key=lambda item: (item[1], item[4]))
#按子列表中第2个元素升序、第5个元素升序排序
>>> for item in y:
print(item)
[8, 2, 7, 1, 6]
[1, 5, 3, 9, 4]
[5, 6, 8, 7, 4]
[9, 6, 10, 7, 6]
[1, 7, 5, 3, 5]
map()函数
- 内置函数map()可以将一个函数作用到一个或多个序列或迭代器对象上,返回可迭代的map对象
>>> list(map(str,range(5)))
['0', '1', '2', '3', '4']
>>> def add5(v):
return v+5
>>> list(map(add5,range(10)))
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
>>> def add(x, y):return x+y
>>> list(map(add, range(5), range(5)))
[0, 2, 4, 6, 8]
recude()函数
- 标准库functools中的reduce()函数可以将一个接受2个参数的函数以迭代的方式从左到右依次作用到一个序列或迭代器对象的所有元素上
>>> from functools import reduce
>>> seq=[1,2,3,4,5,6,7,8,9]
>>> reduce(lambda x,y:x+y, seq)
45
>>> def add(x, y):
return x + y
>>> reduce(add,range(10))
45
>>> reduce(add,map(str,range(10)))
'0123456789'
filter()
- 内置函数filter将一个函数作用到一个序列上,返回该序列中使得该函数返回值为True的那些元素组成的filter对象
>>> seq=['foo','x41','?!','***']
>>> def func(x):
return x.isalnum()
>>> list(filter(func,seq))
['foo', 'x41']
>>> seq
['foo', 'x41', '?!', '***']
>>> [x for x in seq if x.isalnum()]
['foo', 'x41']
>>> list(filter(lambda x:x.isalnum(),seq))
['foo', 'x41']
生成器函数
- 包含yield语句的函数可以用来创建生成器对象,这样的函数也称生成器函数
- 每次执行到yield语句会返回一个值然后暂停或挂起后面代码的执行,下次通过生成器对象的__next__()方法、内置函数next()、for循环遍历生成器对象元素或其他方式显式“索要”数据时恢复执行
- 生成器对象具有惰性求值的特点,适合大数据处理
>>> def f():
a, b = 1, 1 #序列解包,同时为多个元素赋值
while True:
yield a #暂停执行,需要时再产生一个新元素
a, b = b, a+b #序列解包,继续生成新元素
>>> a = f() #创建生成器对象
>>> for i in range(10): #斐波那契数列中前10个元素
print(a.__next__(), end=' ')
1 1 2 3 5 8 13 21 34 55
>>> for i in f(): #斐波那契数列中第一个大于100的元素
if i > 100:
print(i, end=' ')
break
144
>>> a = f() #创建生成器对象
>>> next(a) #使用内置函数next()获取生成器对象中的元素
1
>>> next(a) #每次索取新元素时,由yield语句生成
1
>>> a.__next__() #也可以调用生成器对象的__next__()方法
2
>>> a.__next__()
3