Python——函数设计与使用

发布于:2022-11-06 ⋅ 阅读:(275) ⋅ 点赞:(0)


函数定义

提示:这里可以添加本文要记录的大概内容:
①、函数是最基本的一种代码抽象的方式,将需要反复执行的代码封装为函数,并在需要该功能的地方进行调用,不仅可以实现代码复用,更重要的是可以保证代码的一致性,只需要修改该函数代码则所有调用均受到影响。

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

③、在编写函数时,应尽量减少副作用,尽量不要修改参数本身,不要修改除返回值以外的其他内容。
应充分利用Python函数式编程的特点,让自己定义的函数尽量符合纯函数式编程的要求。

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

    [return [表达式]] 

注意事项:

函数形参不需要声明其类型,也不需要指定函数返回值类型

即使该函数不需要接收任何参数,也必须保留一对空的圆括号

括号后面的冒号必不可少

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

Python允许嵌套定义函数

return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的
return相当于返回 None。

提示:下面案例可供参考

【例1】长方形面积函数定义和调用

def area(width, height): 
      '''计算长方形面积'''
      return width * height

w = 4 
h = 5 
print("width =", w, " height =", h, " area =", area(w, h))

【例2】如果想定义一个什么事也不做的空函数,可以用pass语句

>>> def nop():
    	pass

pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。

【例3】生成斐波那契数列的函数定义和调用

def fib(n):
     a, b = 1, 1
     while a < n:
         print(a, end=' ')
         a, b = b, a+b

fib(1000)

提示:下面案例可供参考

形参与实参

1、函数定义时括弧内为形参,一个函数可以没有形参,但是括弧必须要有,表示该函数不接受参数。

函数调用时向其传递实参,将实参引用(地址)传递给形参。

def fib(n): #n形参
     a, b = 1, 1
     while a < n:
         print(a, end=' ')
         a, b = b, a+b

fib(1000) #1000实参

2、在定义函数时,对参数个数并没有限制,如果有多个形参,需要使用逗号进行分隔。
编写函数,接受两个整数,并输出其中最大数。

def printMax(a, b):
    if a>b:
         pirnt(a, 'is the max')
    else:
         print(b, 'is the max')

3、对于绝大多数情况下,在函数内部直接修改形参的值不会影响实参。例如:

>>> def addOne(a):
		print(a)
		a += 1
		print(a)	
>>> a = 3
>>> addOne(a)
3
4
>>> a
3

可以看出,函数内部修改了形参a的值,但当函数运行结束后,实参a的值并未被修改

4、在有些情况下,可以通过特殊的方式在函数内部修改实参的值,例如下面的代码。

>>> def modify(v):          #修改列表元素值
		v[0] = v[0]+1
>>> a = [2,4]
>>> modify(a)
>>> a
[3,4]
>>> def modify(v, item):    #为列表增加元素
		v.append(item)
>>> a = [2]
>>> modify(a,3)
>>> a
[2, 3]
>>> def modify(d): #修改字典元素值或为字典增加元素
		d['age'] = 38
>>> a = {'name':'jack', 'age':37, 'sex':'Male'}
>>> a
{'age': 37, 'name': 'jack', 'sex': 'Male'}
>>> modify(a)
>>> a
{'age': 38, 'name': 'jack', 'sex': 'Male'}

也就是说,如果传递给函数的是可变序列,并且在函数内部使用下标或可变序列自身的方法增加、删除元素或修改元素时,修改后的结果是可以反映到函数之外的,实参也得到相应的修改。



验证

传不可变对象:不会改变外面实参值

def changeInt( a ): 
     a = 10 

b = 2 
ChangeInt(b)
print( b ) # 结果是 2

不可变对象:数字类型、元组、字符串

不可变类型:类似 c 的值传递,如 整数、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身。比如在 fun(a)内部修改 a 的值,只是修改另一个复制的对象,不会影响 a 本身。
可变类型:类似 c的指针传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响
画图解释

传可变对象实例:会改变外面实参值

def changeList( blist ): 
     blist.append([4,5,6])
     print ("函数内取值: ", blist) 
     # 结果是 [1,2,3,4,5,6]
     
alist = [1,2,3] 
changeList(alist)
print( alist ) # 结果是 [1,2,3,4,5,6]

可变对象:列表、字典、集合等

当一个引用传递给函数的时候,函数自动复制一份引用。这个函数里的引用和外边的引用没有任何关系了.所以函数把引用指向了一个不可变对象,当函数返回的时候,外面的引用没半毛关系.
而第函数内的引用指向的是可变对象,对它的操作就和定位了指针地址一样,在内存里进行修改.

传不可变对象:函数内部直接修改形参的值不会影响实参
传可变对象:函数内部修改实参的值会影响到实参

形参和实参变量各自有不同的存储单元,当一个引用传递给函数的时候,函数自动复制一份引用



参数类型

在Python中,函数参数有很多种:可以为普通参数、默认值参数、关键参数、可变长度参数等。

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

Python函数定义时也不需要指定函数的返回值类型,这将由函数中的return语句来决定,如果没有return语句或者return没有得到执行,则认为返回空值None。

必备参数

必需参数须以正确的顺序传入函数。调用时的数量、参数类型、位置必须和声明时的一样。否则会出现语法错误。

def add( x , y ):
        return x+y

默认值参数

默认值参数:在定义函数时为形参设置默认值。

默认值参数必须出现在函数参数列表的最右端,任何一个默认值参数右边不能有非默认值参数。

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

注意:

默认值参数的赋值只在函数定义时被解释一次

>>> i = 3
>>> def f(n=i):      #参数n的值仅取决于i的当前值
    print(n)

>>> f()
3
>>> i = 5           #函数定义后修改i的值不影响参数n的默认值
>>> f()
3

默认参数必须指向不变对象!

1、可以使用“函数名.defaults”查看所有默认参数的当前值,返回值为元组,其中元素依次表示每个默认值参数的当前值。

>>> say.__defaults__
(1,)

>>> f.__defaults__     #查看函数默认值参数的当前值
(3,)

2、默认值参数必须出现在函数参数列表的最右端,且任何一个默认值参数右边不能有非默认值参数。(为了避免歧义)

>>> def f(a=3,b,c=5):
	print(a,b,c)
>>> def f(a=3,b):
	print(a,b)
>>> def f(a,b,c=5):
	print(a,b,c)
>>> def f(a,b,c=5,d=5):
	print(a,b,c,d)	
>>> def f(a,b,c=5,d,e=8):
	print(a,b,c,d)

3、默认值参数如果使用不当,会导致很难发现的逻辑错误,例如:

def demo(newitem,old_list=[]):
    old_list.append(newitem)
    return old_list
print(demo(5,[1,2,3,4])) 
A [1, 2, 3, 4, 5] B [1, 2, 3, 4] C 报异常   
print(demo('aaa',['a','b'])) 
A ['a', 'b', 'aaa'] B ['a', 'b'] C 报异常 
print(demo('a'))  
A ['a'] B [] C 报异常      
print(demo('b'))          
A ['b'] B ['a', 'b'] C 报异常 

#正确代码如下:
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') )

位置参数

位置参数(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 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


可变长度参数

可变长度参数主要有两种形式:
*args 用来接受多个实参并将其放在一个元组中
**kw接受关键字参数并存放到字典中

可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。
而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。

*args的用法

接收多个参数放元组里
加了星号 * 的参数会以元组(tuple)的形式导入

>>> def demo(*p):
        print(p)
>>> demo(1,2,3)
(1, 2, 3)
>>> demo(1,2,3,4,5,6,7)
(1, 2, 3, 4, 5, 6, 7)

**kw的用法

接受关键参数并存放到字典中
两个星号 ** 的参数会以字典的形式导入

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

注意:调用函数时如果对实参使用一个星号*进行序列解包,这么这些解包后的实参将会被当做普通位置参数对待,并且会在关键参数和使用两个星号**进行序列解包的参数之前进行处理。

return语句

return语句用来从一个函数中返回一个值,同时结束函数。
如果函数没有return语句,或者有return语句但是没有执行到,或者只有return而没有返回值,Python将认为该函数以return None结束。

def maximum( x, y ):
    if x>y:
        return x
    else:
        return y

在调用函数或对象方法时,一定要注意有没有返回值,这决定了该函数或方法的用法

>>> 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声明这个变量,将其声明为全局变量。
在函数内部直接将一个变量声明为全局变量,在函数外没有声明,该函数执行后,将增加为新的全局变量。

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

网站公告

今日签到

点亮在社区的每一天
去签到