python函数(7)

吉吉:

OC](python函数)

函数的设计和使用

函数的设计和使用

  • 函数是为实现一个操作而集合在一起的语句集,可以用来定义可重用代码、组织和简化代码。
  • 将可能需要反复执行的代码封装为函数,并在需要该功能的地方进行调用,不仅可以实现代码复用,更重要的是可以保证代码的一致性,只需要修改该函数代码则所有调用均受到影响。
  • 在实际项目开发中,往往会把一些通用的函数封装到一个模块中,并把这个通用模块文件放到顶层文件夹中,这样更方便管理。

1、函数定义语法:


   def 函数名([参数列表]):
   '''注释'''
   函数体

注意事项:
函数名是标识符,命名必须符合Python标识符的规定(字母、数字、下划线,但是不能以数字打头) ;
形式参数,简称为形参,写在一对圆括号里面;
括号后面的冒号必不可少;
函数体相对于def关键字必须保持一定的空格缩进

  • 在定义函数时,开头部分的注释并不是必需的,但如果为函数的定义加上注释的话,可以为用户提供友好的提示。
  • 三个引号能包含多行字符串,常常出现在函数的声明的下一行,来注释函数的功能,python中这个注释成为函数的一个默认属性,还可以通过 函数名.doc 来访问(有点意思)

函数的返回值

  • 如果函数不返回任何值,在主程序中被当作一个语句调用。
    而另一些函数可能有返回值。

  • 如果函数有返回值,则可称为带返回值的函数,使用关键字return来返回值;

  • 不论return语句出现在函数的什么位置,一旦得到执行将直接结束函数的执行,即执行return语句意味着函数的终止。

  • Python中不需要指定函数返回值类型,函数返回值类型与return语句返回表达式的类型一致。如例6-4所示。

  • 如果函数没有return语句,或者有return语句但是没有执行到,或者执行了不返回任何值的return语句,解释器都会认为该函数以return None结束,即返回空值。如下例所示。


>>> def fib(n):
a,b=1,1
while n>=a:
print(a,end=" ")
a,b=b,a+b

>>> type(fib(100))
1 1 2 3 5 8 13 21 34 55 89 <class 'NoneType'>
>>> 
  • Python中return语句还可以从函数中返回多个值
  • 格式如下:
    def 函数名(形式参数):
    ……
    return <表达式1>,…,<表达式n>
  • 当函数具有多个返回值的时候,我们如果只用一个变量来接收返回值,函数返回的“多个值”实际上构成了一个元组,所以也可以按如下元组的方式输出。

#主程序
r=3
re=circle (r)  #函数调用,并赋值给变量re
print "半径为",r,"的圆面积为:",re[0]
print "半径为",r,"的圆周长为:",re[1]
>>> =================RESTART=========================
半径为 3 的圆面积为: 28.26
半径为 3 的圆周长为: 18.84
  • 一般来说,函数执行完所有步骤之后才得出计算结果并返回,return语句通常出现在函数的末尾。但是,有时我们希望改变函数的正常流程,在函数到达末尾之前就终止并返回,例如当函数检查到错误的数据时就没有必要继续执行

函数的调用

  • 函数的定义是定义函数做什么。而函数一旦被定义,就可以在程序的任何地方调用这个函数。
  • 当调用一个函数时,程序控制权就会转移到被调用的函数上;当执行完函数,被调用的函数就会将程序控制权交还给调用者。

函数的递归调用

  • 函数的递归调用是函数调用的一种特殊情况,函数调用自己,自己再调用自己,自己再调用自己,…,当某个条件得到满足的时候就不再调用了,然后再一层一层地返回直到该函数的第一次调用。

  • 递归是一种非常实用的程序设计技术。许多问题具有递归的特性,在某些情况下,用其它方法很难解决的问题,利用递归可以轻松解决。

  • 编写递归函数的时候必须满足:
    (1)递归终止条件及终止时的值;
    (2)能用递归形式表示,并且向终止条件的方向发展。

思考,如果这样编写fac函数会出现什么问题?


def  fac(n): 
 s=n*fac(n-1)
 return s
  • 以上示例既可以用递归函数实现,也可以用非递归方法实现。

  • 但有些问题使用递归很容易解决,不使用递归很难解决,如经典的汉诺塔问题。

  • 汉诺塔问题
    n个标记1、2、3、…、n的大小互不相同的盘子,三个标记A、B、C的塔。借助塔C把所有盘子从塔A移动到塔B。初始状态时所有盘子都放在塔A,任何时候盘子都不能放在比它小的盘子的上方,每次只能移动一个盘子,并且这个盘子必须在塔顶位置。

  • 分析:
    当只有1个盘子,即n=1时,我们可以简单的把这个盘子直接从塔A移动到塔B。这就是我们所说的终止条件。当n>1时,我们依次解决以下三个子问题即可(具体分析过程请大家参阅相关书籍):
    (1)借助塔B将前n-1个盘子从A移到C;
    (2)将盘子n从塔A移到塔B;
    (3)借助塔A将前n-1个盘子从C移到B。

  • 使用递归解决汉诺塔问题


#eg5_24.py
#ftower,表示原始盘
#ttower,表示目标盘
#atower,表示过渡盘
def han(n,ftower,ttower,atower):  #函数定义
if n==1:
print(n,"from",ftower,"to",ttower)
else:
han(n-1,ftower,atower,ttower)
print (n,"from",ftower,"to",ttower)
han(n-1,atower,ttower,ftower)
 
#主程序
a=input("please enter n:")
han(a,"A","B","C")

程序运行结果:


>>> ==========RESTART===========
please enter n:4
1 from A to C
2 from A to B
1 from C to B
3 from A to C
1 from B to A
2 from B to C
1 from A to C
4 from A to B
1 from C to B
2 from C to A
1 from B to A
3 from C to B
1 from A to C
2 from A to B
1 from C to B

函数定义要点总结:

  • 函数名是标识符,命名必须符合Python标识符的规定;

  • 形式参数,简称为形参,写在一对圆括号里面,形参是可选的,即函数可以包含参数,也可以不包含参数;即使该函数不需要接收任何参数,也必须保留一对空的圆括号

  • 函数形参不需要声明类型

  • 括号后面的冒号必不可少

  • 函数体相对于def关键字必须保持一定的空格缩进

  • 不论return语句出现在函数的什么位置,一旦得到执行将直接结束函数的执行。

  • 不需要指定函数返回值类型,函数返回值类型与return语句返回表达式的类型一致。

  • 如果函数没有return语句、有return语句但是没有执行到或者执行了不返回任何值的return语句,解释器都会认为该函数以return None结束,即返回空值。

  • Python允许嵌套定义函数

  • 设计函数时,应注意提高模块的内聚性,同时降低模块之间的隐式耦合。

  • 在编写函数时,应尽量减少副作用,尽量不要修改参数本身,不要修改除返回值以外的其他内容。

  • 应充分利用Python函数式编程的特点,让自己定义的函数尽量符合纯函数式编程的要求,例如保证线程安全、可以并行运行等等。

形参与实参

  • 函数定义时()内为形参,如果有多个形参,需要使用逗号进行分割。
  • 函数调用时向其传递实参,将实参的值或引用传递给形参。
  • ,在函数内部直接修改形参的值不会影响实参。例如:

>>> def addOne(a):
	print(a)
	a += 1
	print(a)	
>>> a = 3
>>> addOne(a)
3
4
>>> a
3
  • 在有些情况下,可以通过特殊的方式在函数内部修改实参的值,例如下面的代码。

>>> 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’}
    modify(a)
    a
    {‘age’: 38, ‘name’: ‘Dong’, ‘sex’: ‘Male’}

  • Python在定义函数时不需要指定形参的类型,完全由调用者传递的实参类型以及Python解释器的理解和推断来决定,类似于重载和泛型。

  • 但是Python支持对函数参数和返回值类型的标注,但实际上并不起任何作用,只是看起来方便。


>>> def test(x:int, y:int) -> int:
	'''x and y must be integers, return an integer x+y'''
	#用assert断言来测试表示式,其返回值为假,就会触发异常 
 assert isinstance(x, int), 'x must be integer '
	assert isinstance(y, int), 'y must be integer '
	z = x+y
	assert isinstance(z, int), 'must return an integer'
	return z
>>> test(1, 2)
3
>>> test(2, 3.0)#参数类型不符合要求,抛出异常
AssertionError: y must be integer

位置参数

  • 在Python中,函数的实参(即调用函数时的参数传递方式)有2种形式:位置参数和关键参数。
  • 位置参数(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

关键参数

  • 实参作为关键参数被传递是指通过name=value的形式传递每个参数
  • 通过关键参数,实参顺序可以和形参顺序不一致,但不影响传递结果,避免了用户需要牢记位置参数顺序的麻烦。

>>> def demo(a,b,c):
	   print(a,b,c)
>>> demo(3,7,5)
3 7 5
>>> demo(a=7,b=3,c=6)
7 3 6
>>> demo(c=8,a=9,b=0)
9 0 8

默认值参数

  • 在Python中,函数的形参也可以有多种形式。
  • 可以为其设置默认值,Python允许定义带默认参数值的函数
  • 如果在调用函数时不为这些函数提供值,这些参数就使用默认值;
  • 如果在调用的时候有实参,则将实参的值传递给形参。
  • 设置默认参数值的格式如下:

 def 函数名(形参名=默认值,……)

>>> 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 demo(newitem,old_list=[]):
old_list.append(newitem)
return old_list
print(demo('5',[1,2,3,4]))   #right
print(demo('aaa',['a','b'])) #right
print(demo('a')) #right
print(demo('b')) #wrong
  • 试着想一想,这段代码会输出什么呢?
  • 上面的代码输出结果如下,最后一个结果是错的。

[1, 2, 3, 4, ‘5’]
[‘a’, ‘b’, ‘aaa’]
[‘a’]
[‘a’, ‘b’]

继续想:为什么会这样呢?

  • 原因在于默认值参数的赋值只会在函数定义时被解释一次。当使用可变序列作为参数默认值时,一定要谨慎操作。

  • 最后一个问题来了:正确的代码该怎么写呢?

  • 终极解决方案:改成下面的样子就不会有问题了


def demo(newitem,old_list=None):
if old_list is None:
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') )

注意:
默认值参数只在函数定义时被解释一次
可以使用“函数名.defaults”查看所有默认参数的当前值


>>> i = 3
>>> def f(n=i): #参数n的值仅取决于i的当前值
	print(n)
>>> f()
3
>>> i = 5 #函数定义后修改i的值不影响参数n的默认值
>>> f()
3

可变长度参数

  • 在前面的函数介绍中,我们知道一个实参只能接收一个形参,其实在Python中,函数可以接收不定个数的参数,即用户可以给函数提供可变长度的参数,这可以通过在参数前面使用标识符*来实现。
  • 可变长度参数主要有两种形式:
    *parameter用来接受多个位置实参并将其放在一个元组中
    **parameter接受多个关键参数并存放到字典中

(1)parameter的用法:
形参p前面有一个标识符
,表明形参p可以接收不定个数的参数,并以元组的形式传递进函数。


>>> def demo(*p):  #定义函数demo
	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)
>>> demo(1) 
(1,)  		 #即使实参只有一个,也以元组的形式传递进函数中
  • 用标识符*实现的可变长度的参数也可以和其它普通参数混合使用,这时一般将可变长度参数放在形参列表的最后。

>>> def all_print(brgs,*args): #函数定义
print (brgs)
print (args)


>>> all_print(“abc”,“a”,2,3,“b”)   #函数调用
abc#位置参数原样输出
(‘a’, 2, 3, ‘b’)   #可变长度参数结合成元组
>>> 
  • (2)parameter的用法
    形参p前面有标识符
    ,表明形参p可以接收多个关键字参数,并以一个字典的形式传递进函数。

>>> def demo(**p):
	 for item in p.items():
		print(item)

>>> demo(x=1,y=2,z=3)
('y', 2)
('x', 1)
('z', 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)	
>>> 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
  • 如果函数实参是字典,可以在前面加两个星号**进行解包,等价于关键参数。

>>> 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
>>> 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

变量作用域

  • 变量起作用的代码范围称为变量的作用域,不同作用域内变量名可以相同,互不影响。

  • 一个变量在函数外部定义和在函数内部定义,其作用域是不同的。在函数内部定义的普通变量只在函数内部起作用,称为局部变量。当函数执行结束后,局部变量自动删除,不再可以使用。

  • 局部变量的引用比全局变量速度快,应优先考虑使用。

  • 如果想要在函数内部给一个定义在函数外的变量赋值,那么这个变量就不能是局部的,其作用域必须为全局的,能够同时作用于函数内外,称为全局变量,可以通过global来定义。这分为两种情况:

  • 一个变量已在函数外定义,如果在函数内需要为这个变量赋值,并要将这个赋值结果反映到函数外,可以在函数内用global声明这个变量,将其声明为全局变量。

  • 在函数内部直接将一个变量声明为全局变量,在函数外没有声明,该函数执行后,将增加为新的全局变量。

def demo():
global x #声明x是全局变量
x = 3
y =4
print(x,y)


>>> x = 5
>>> demo()   #本次调用demo中, global x 为声明使用外部变量x
3  4
>>> x
3
>>> y
NameError: name 'y' is not defined
>>> del x
>>> x
NameError: name 'x' is not defined
>>> demo()#本次调用demo中, global x 为定义(新增)全局变量
3  4
>>> x
3
>>> y
NameError: name 'y' is not defined

也可以这么理解:

  • 在函数内如果只引用某个变量的值而没有为其赋新值,该变量为(隐式的)全局变量;

  • 如果在函数内任意位置有为变量赋新值的操作,该变量即被认为是(隐式的)局部变量,除非在函数内显式地用关键字global进行声明。

  • 注意:在某个作用域内只要有为变量赋值的操作,该变量在这个作用域内就是局部变量,除非使用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
  • 如果局部变量与全局变量具有相同的名字,那么该局部变量会在自己的作用域内隐藏同名的全局变量。

>>> x = 3
>>> def demo():
	x = 5   #创建了局部变量,并自动隐藏了同名的全局变量	
>>> demo()
>>> x   #函数执行不影响外面全局变量的值
3
  • 如果需要在同一个程序的不同模块之间共享全局变量的话,可以编写一个专门的模块来实现这一目的。例如,假设在模块A.py中有如下变量定义:

global_variable = 0
  • 可以在模块B.py中包含以下用来修改全局变量的语句:

import A
A.global_variable = 1
  • 也可以在模块C.py中有以下语句来访问全局变量的值:

import A
print(A.global_variable)

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

>>> 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表达式中调用函数
[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]

__name__属性

  • 每个Python模块脚本在运行时都有一个“name”属性。如果脚本作为模块被导入,则其“name”属性的值被自动设置为模块名;如果脚本独立运行,则其“name”属性值被自动设置为“main”。例如,假设文件nametest.py中只包含下面一行代码:print(name)
  • 在IDLE中直接运行该程序时,或者在命令行提示符环境中运行该程序文件时,运行结果如下:main
  • 而将该文件作为模块导入时得到如下执行结果:

>>> import nametest
nametest
  • 在导入模块的时候,首先,会去程序的当前主目录下寻找;如果找不到,就去PYTHONPATH目录下寻找;如果还找不到,就去标准链接库目录寻找。
  • 可以将要被导入的模块所在的路径- 导入到path中。
  • 我们先通过import sys导入sys模块。
  • 然后通过sys.path.append(r“F:\python\moddm”)将模块所在的路径插入path中
  • 然后再通过import dm导入dm模块
  • 在很多Python代码中,__name__属性被用来区分上述module文件被使用的两种方式。一种常用的做法是将module文件自己的单测代码,放到__name__属性为"main"的情形中去。
  • 这样一来,程序直接执行时将会得到测试代码,而使用import语句将其作为模块导入后可以使用其中的类、方法、常量或其他成员。

def fib(n):
a,b=1,1
while n>=a:
print(a,end=" ")
a,b=b,a+b

#以下为检测的代码
if __name__=='__main__':
  fib(100)

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]
发布了44 篇原创文章 · 获赞 82 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/weixin_41503009/article/details/85764685