Python(2)函数


此文章参考廖雪峰大神的官方:调用函数 - 廖雪峰的官方网站 (liaoxuefeng.com)

一、什么是函数

  • 函数通俗的来讲,就是一段固定的代码,函数还可以传入参数,最终得到的结果其实就固定代码的执行结果。
  • python中内置了许多函数,我们可以直接进行调用,并且我们自己也可以自定义函数

二、调用函数

  • python内置了许多函数,我们可以直接进行调用
  • 如果要调用函数的话,需要先知道函数的函数名称以及可以传入参数的数量和数据类型等,可以去pyton的官网进行查询:查询python内置函数
  • 也可以在python交互模式中使用help()函数来查看指定函数的相关信息
>>> help(abs) 
Help on built-in function abs in module builtins:

abs(x, /)
    Return the absolute value of the argument.
  • 调用函数时,如果传入的参数数量、数据类型不对的话,会报错,以求绝对值函数abs()为例:
>>> abs(1,2,3) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: abs() takes exactly one argument (3 given)
>>> abs(1)     
1
>>> abs("1") 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: bad operand type for abs(): 'str'
        
#例如求最大值函数max这种函数,可传入的参数数量就和abs不同
>>> max(1,23,4,5) 
23
  • 除了上述的max和abs,python内置的常用函数还包括数据类型转换的函数,下面来看几个例子:
>>> int('123')   #转换字符串为数字
123
>>> int(12.33)  #转换浮点数为整数
12
>>> str(123)  #转换数字为字符串
'123'
>>> bool(1)  #转换数字为布尔值
True
>>> list('aaa')  #转换字符串为列表
['a', 'a', 'a']
  • 函数还可以指向其他变量:
>>> a = abs  
>>> a(-1) 
1
>>> abs(-1) 
1

三、定义函数

  • 定义函数可以使用def语句,依次写出def 函数名(参数):,输入回车,在缩进块中编写代码,函数的返回值使用return语句返回
  • 以一个计算数字平方的函数示例:
- 交互环境中:
>>> def test(x):   #输入:后会有...提示符,输入代码后连续两次回车可以返回>>>提示符下
...     return x * x 
... 
>>> test(2) 
4
>>> test(22) 
484

- 写入py文件中:
# -*- coding: utf-8 -*-
def test(x):
    return x * x 

print(test(2))
print(test(22))

#输出
4
484
  • 要注意的是,在函数中的语句执行时,只要遇到了return语句时,就代表函数执行完毕,并且返回执行结果,如果一个函数中没有return语句,函数也会返回值,但是这个值是Nonereturn没有指定返回值也会返回None,也就是说return None可以写成return

  • 如果想要定义的函数什么都不做,可以使用pass语句:

>>> def test(x):
...     if x > 10:
...             return x
...     else:
...             pass
... 
>>> test(20)      
20
>>> test(9)  
>>> 

#pass语句表示什么都不做,可以作为占位符,例如函数的代码还没想好怎么写,就可以
  • 如果还想让函数返回多个值的话,可以这样做:
# -*- coding: utf-8 -*-
def test(i):
    x = i + 1
    y = i - 1
    return x,y
print(test(20))    

#输出:
(21, 19)

四、函数的参数

  • 在定义函数的时候,我们需要把函数参数的名字、数量、位置确定下来,在确定下来之后,函数的接口其实也就定义完成
  • 对于函数调用者来说,想要使用函数只需要知道如何传递正确的参数、参数的数量、参数的类型等,以及调用函数后会返回什么值,这样就已经足够了,而函数中间的逻辑算法等,调用者是不用去了解的
  • Python函数的定义简单、灵活度大,除了正常定义的必选参数外,还可以使用默认参数可变参数关键字参数,使得函数定义出来的接口,不但能够处理复杂的参数,还可以简化调用者的代码
1、位置参数
1、我们先来定义一个计算x二次方的函数:
>>> def test(x):
...     return x * x
... 

在test函数中,变量x就是位置参数,当我们要调用test函数时,必须传入x参数,并且只能传输一个,下面来看调用test参数时传入不同参数时的输出的结果
>>> print(test(4))   #传入整数
16
>>> print(test(4.5))   #传入浮点数
20.25
>>> print(test('4'))   #传入字符串,发现报错了,因为字符串无法使用 “ * ” 进行运算
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in test
TypeError: can't multiply sequence by non-int of type 'str'
>>> print(test(4,5))   #传入两个参数,同样报错,因为test函数中只有一个位置参数
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() takes 1 positional argument but 2 were given
>>> print(test())      #不传入任何参数,因为test函数中必须传入一个参数x,所以报错了
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() missing 1 required positional argument: 'x'

2、现在来优化test参数,使之可以计算x的n次方:
>>> def test(x,n):
...     i = 1
...     while n > 0:
...             n = n -1 
...             i = i * x 
...     return i 
... 
>>> print(test(7,3)) 
343

在优化后的test函数中,多了一个参数n,那么在每次调用test函数时,就需要两个数字类型的参数,传入的参数按照x和n的前后顺序依次传入,例如test(7,3),则x=7,n=3
2、默认参数、
  • 从上面说的位置参数来看,我们可以知道在调用函数时传入参数的数量数据类型都必须正确,否则会报错。
  • 以优化后可以计算x的n次方函数test为例,如果我们需要多次调用test函数,而每次参数n的值都是2时,我们需要每次调用函数都传入两个参数,这样调用的话,我们会传入很多重复的参数,而默认参数就可以帮助我们减少重复操作
1、定义test函数,可以计算x的n次方,默认计算是x的2次方
>>> def test(x,n=2):       
...     i = 1
...     while n > 0:
...             n = n - 1
...             i = i * x
...     return i
... 
>>> print(test(3,2)) 
9
>>> print(test(3))   
9
从调用test函数可以看出,默认n的参数没有传入时,n=2

2、在使用默认参数时,默认参数必须要指向不变对象,即不可变类型,下面来看一个案例:
>>> def aaa(L=[]):
...     L.append('end')
...     return L
... 
>>> print(aaa([1,2,3]))
[1, 2, 3, 'end']
>>> print(aaa(['x','y','a'])) 
['x', 'y', 'a', 'end']
>>> print(aaa())              
['end']
>>> print(aaa())
['end', 'end']
>>> print(aaa([1,2,3])) 
[1, 2, 3, 'end']
从上面的案例可以看出,在定义aaa参数时使用了L=[ ]作为默认参数,在我们正常调用aaa函数,传入列表参数时,输出的结果都是正常的,而在没有传入参数,使用aaa函数的默认参数时,我们发现在调用两次后,输出的结果并不是我们想的一个end,而是又添加了一个end,这是因为在定义函数时,默认参数L的值就已经被计算出来了,因为L也算一个变量,它指向了对象[ ],在每次调用时,如果修改了L的内容(即修改了空列表[ ]),则在下次调用时,L会变成修改后的值(即修改后的列表['end']),所以会输出这种结果

而解决的方法其实可以加一个if判断例如:
>>> def aaa(L=None):
...     if L is None:
...             L = [] 
...     L.append('end')
...     return L
... 
>>> print(aaa()) 
['end']
>>> print(aaa())
['end']
>>> print(aaa([1,2,3])) 
[1, 2, 3, 'end']
定义L的默认值为None,当L等于None时,才会定义一个空列表,这样就可以避免上面的情况

在定义默认参数时需要注意:

  1. 函数的参数传入是通过前后顺序,所以在定义默认参数时,要把必选参数放在前面,默认参数放在后面,否则会造成必须参数的值错误的传入了默认参数,而必须参数没有传入值,从而报错
  2. 默认参数必须指向不可变类型,避免不必要的错误
3、可变参数
  • 在python中,还可以定义可变参数
1、定义一个函数test,当传入的函数为a,b,c时,test函数可以计算a*a+b*b+c*c
>>> def test(number):
...     sum = 0
...     for i in number:
...             sum = sum + i * i
...     return sum
... 
>>> print(test([1,2,3,4])) 
30
>>> print(test((7,3,15,4)))  
299

上面定义的test函数虽然可以达到效果,但是在每次调用函数时,传入的参数需要先组装成一个列表或者元组

2、使用可变参数优化后:
>>> def test(*number):
...     sum = 0
...     for i in number:
...             sum = sum + i * i
...     return sum
... 
>>> print(test(1,2,3,4)) 
30
>>> print(test([1,2,3,4])) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in test
TypeError: can't multiply sequence by non-int of type 'list'
>>> print(test())          
0
>>> l = [1,2,3,4] 
>>> print(test(l[0],l[1],l[2],l[3])) 
30
>>> print(test(*l))                  
30

可以看到test函数的参数number前面加了一个 “ * ” ,在调用函数时无需组装成列表或元组,直接使用逗号隔开即可,可以传入任意数量的参数,包括0个参数,但是这样我们想要传入列表时就会报错,我们可以先定义列表,然后同样使用” * “来把列表传入函数
4、关键字参数
  • 上面的可变参数允许传入0个或者任意数量的参数,传入的参数在函数调用时会自动组装成一个元组,而关键字参数允许传入0个或者任意数量的包含参数名的参数,这样参数会自动组装成一个字典
1、利用关键字参数定义函数test
>>> def test(name,age,**id):
...     print('name:',name,'age:',age,'id:',id) 
... 
>>> print(test('zhangsan',22))   #在只传入前两个参数时,可以看到最后一个参数id输出了一个空字典{}
name: zhangsan age: 22 id: {}
None
>>> print(test('zhangsan',22,id=2223))     
name: zhangsan age: 22 id: {'id': 2223}
None
>>> print(test('zhangsan',22,id=2223,other=5553))   #传入两个key=value
name: zhangsan age: 22 id: {'id': 2223, 'other': 5553}
None
>>> aaa = {'id':2223,'other':5553}   #定义一个aaa字典
>>> print(test('zhangsan',22,id=aaa['id'],other=aaa['other']))    #传入字典时,使用下标调用
name: zhangsan age: 22 id: {'id': 2223, 'other': 5553}
None
>>> print(test('zhangsan',22,**aaa))         #和关键字参数一样,可以使用**一次性传入字典,需要注意的是,利用关键字参数把aaa字典传入到test函数中的**id参数,id参数会得到一个字典,而id的值只是aaa值的一份拷贝而已,修改id并不会影响aaa的值                 
name: zhangsan age: 22 id: {'id': 2223, 'other': 5553}
None
  • 对于关键字参数来说,我们可以传入任意不受限制的关键字参数,而具体的值,需要在函数内部通过带**的参数(即**id)来检查
5、命名关键字参数
1、以上面的test函数为例,现在我们需要检查传入的参数是否有id和other参数,可以这样写:
>>> def test(name,age,**id):
...     if 'id' in id:
...             pass
...     if 'other' in id:
...             pass
...     print('name:',name,'age:',age,'id:',id) 
... 
>>> print(test('zhangsan',22,id=2222,other=3333,io=4444))
name: zhangsan age: 22 id: {'id': 2222, 'other': 3333, 'io': 4444}
None

#但是发现我们仍然可以传入其他的参数,如果想要达到上面的效果,对传入的参数进行限制,我们可以使用命名关键字参数,例如:
>>> def test(name,age,*,id,other):
...     print('name:',name,'age:',age,'id:',id,'other:',other) 
... 
>>> print(test('zhangsan',22,id=3333,other=4444)) 
name: zhangsan age: 22 id: 3333 other: 4444
None
>>> print(test('zhangsan',22,id=3333,other=4444,io=5555))     #可以看到使用命名关键字参数后,除了id和other,继续传入io参数时会报错
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() got an unexpected keyword argument 'io'
>>> print(test('zhangsan',22,3333,4444))    #除此之外,也不能和位置参数一样直接传入值,必须传入带函数名的值
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() takes 2 positional arguments but 4 were given

2、命名关键字参数还可以利用默认参数
>>> def test(name,age,*,id=2222,other):   #id默认为2222
...     print('name:',name,'age:',age,'id:',id,'other:',other)
... 
>>> print(test('zhangsan',22,other=3333)) 
name: zhangsan age: 22 id: 2222 other: 3333
None

在定义命名关键字参数时需要注意:

  1. 在使用命名关键字参数时,需要注意加” * “,否则后面的命名关键字参数将会被python视为位置参数
6、参数组合
  • 参数组合其实就是使用上面的必选(位置)参数、默认参数、可变参数、关键字参数和命名关键字参数来定义函数
  • 需要注意的是在使用这些参数时有着定义顺序,顺序必须为

必选(位置)——默认——可变——命名关键字——关键字

1、下面来看几个例子
>>> def test_1(a,b,c=0,*args,**kw):  
...     print('a=',a,'b=',b,'c=',c,'agrs=',args,'kw=',kw)  
... 
>>> print(test_1(1,2)) 
a= 1 b= 2 c= 0 agrs= () kw= {}
None
>>> print(test_1(1,2,c=4))           
a= 1 b= 2 c= 4 agrs= () kw= {}
None
>>> print(test_1(1,2,4,'aaa','bbb'))   
a= 1 b= 2 c= 4 agrs= ('aaa', 'bbb') kw= {}
None
>>> print(test_1(1,2,4,'aaa','bbb',id=444,other=555)) 
a= 1 b= 2 c= 4 agrs= ('aaa', 'bbb') kw= {'id': 444, 'other': 555}
None
>>> def test_2(a,b,c=0,*,d,**kw):     
...     print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
... 
>>> print(test_2(1,2,d=4,id=1222,other=3333)) 
a = 1 b = 2 c = 0 d = 4 kw = {'id': 1222, 'other': 3333}
None

定义列表和字典
>>> l_1=['aaa','bbb'] 
>>> d_1={'id':111,'other':222}
>>> print(test_1(1,2,3,*l_1)) 
a= 1 b= 2 c= 3 agrs= ('aaa', 'bbb') kw= {}
None
>>> print(test_1(1,2,3,*l_1,**d_1)) 
a= 1 b= 2 c= 3 agrs= ('aaa', 'bbb') kw= {'id': 111, 'other': 222}
None
>>> l_2=[1,2,3,4]
>>> d_2={'d':1111,'id':2222,'other':3333}
>>> print(test_1(*l_2,**d_2)) 
a= 1 b= 2 c= 3 agrs= (4,) kw= {'d': 1111, 'id': 2222, 'other': 3333}
None
>>> l_3 = [1,2,3] 
>>> print(test_2(*l_3,**d_2)) 
a = 1 b = 2 c = 3 d = 1111 kw = {'id': 2222, 'other': 3333}
None
     
2、可以看到使用了***传入了列表和字典,以print(test_1(*l_2,**d_2)):
test_1的位置参数有2个,默认参数有1个,可变参数有一个,关键字参数有一个,按顺序来看l_2的值为1234,使用*一次性传入其实就变成了,12给位置参数赋值,3给默认参数赋值,4给可变参数赋值,如果l_2列表为12345的话,那么45其实都是给可变参数赋值,而d_2字典给关键字参数赋值了

3、同理来看print(test_2(*l_3,**d_2)) ,其实都是一样的,只不过是d_2字典中的d值,先给命名关键字参数赋值了,然后后面的id和other才给最后的关键字参数赋值,需要注意的值使用的l_3列表的元素数量是与test_2函数的参数相匹配的,否则会报错
  • 可以看到,对于仍和参数都可以通过类似于test(*args,**kw)的形式去调用它,无论他的参数是如何定义的
7、总结
  1. 默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误
  2. 要注意定义可变参数和关键字参数的语法:

*args是可变参数,args接收的是一个tuple;

**kw是关键字参数,kw接收的是一个dict。

  1. 以及调用函数时如何传入可变参数和关键字参数的语法:

可变参数既可以直接传入:func(1, 2, 3),也可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3))

关键字参数既可以直接传入:func(a=1, b=2),也可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})

  1. 命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。

  2. 定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。

  3. 使用*args**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。

五、递归函数

  • 在函数内部是可以调用其他函数的,而如果一个函数在自己的内部调用自己本身,那么这个函数就是递归函数

  • 下面来看几个案例:

1、定义一个计算阶乘如1*2*3*4的函数
>>> def test(n):
...     if n == 1:
...             return 1
...     return n * test(n-1) 
... 
>>> print(test(1)) 
1
>>> print(test(5))   
120
>>> print(test(100)) 
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

来解析计算过程的话,其实就是:
test(5)
5 * test(4)
5 * (4 * test(3))
5 * (4 * (3 * test(2)))
5 * (4 * (3 * (2 * test(1))))
5 * (4 * (3 * (2 * 1))
5 * (4 * (3 * 2))
5 * (4 * 6)
5 * 24
120
  • 递归函数的优点就是定义简单,逻辑清晰,理论上所有递归函数都可以写成循环的方式,但是循环的算法逻辑没有递归函数清晰
拓展-尾递归
  • 使用递归函数需要防止栈(stack)溢出,在计算机中,函数调用是通过调用栈来实现的,每进入一个函数调用,栈就会多加一层栈帧,同样的每当函数返回时,栈就会减少一层栈帧
  • 而由于栈的大小并不是无线的,所以当递归函数的调用次数过多,会导致栈溢出,例如:
>>> print(test(1000)) 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in test
  File "<stdin>", line 4, in test
  File "<stdin>", line 4, in test
  [Previous line repeated 995 more times]
  File "<stdin>", line 2, in test
RecursionError: maximum recursion depth exceeded in comparison
  • 那么如何解决递归函数调用时引起的栈溢出呢,我们可以通过尾递归优化,而其实尾递归和循环的效果是相同的,所以可以把循环看成一种特殊的尾递归函数
  • 尾递归就是指在函数返回的时候,调用自己本身,并且使用return语句时不能包含表达式,这样编译器或者解释器就可以把尾递归做优化,使递归无论调用多少次,都只占用一个栈,从而解决栈溢出的问题
  • 下面来看一下尾递归的案例:
以上面的test函数为例,test函数由于在末尾的return语句引用了乘法表达式,所以test函数不是尾递归,下面来优化一下test函数
>>> def test(n):
...     return test_2(n,1) 
... 
>>> def test_2(num,pro):   
...     if num == 1:
...             return pro  
...     return test_2(num - 1,num * pro) 
... 
可以看到test_2函数在最后只返回了test_2函数本身,num-1和num*pro在函数调用前就会被计算,不会影响函数调用
>>> print(test_2(5,1))  #test_2(5,1)就对应上面的test(5)
120
  • 尾递归调用时,如果做了优化,栈就不会增长,从而避免栈溢出
  • 但是大多数编程语言都没有针对尾递归做优化,python也没有,所以及时把上面的test函数改成尾递归方式,栈也会溢出
    一下尾递归的案例:
以上面的test函数为例,test函数由于在末尾的return语句引用了乘法表达式,所以test函数不是尾递归,下面来优化一下test函数
>>> def test(n):
...     return test_2(n,1) 
... 
>>> def test_2(num,pro):   
...     if num == 1:
...             return pro  
...     return test_2(num - 1,num * pro) 
... 
可以看到test_2函数在最后只返回了test_2函数本身,num-1和num*pro在函数调用前就会被计算,不会影响函数调用
>>> print(test_2(5,1))  #test_2(5,1)就对应上面的test(5)
120
  • 尾递归调用时,如果做了优化,栈就不会增长,从而避免栈溢出
  • 但是大多数编程语言都没有针对尾递归做优化,python也没有,所以及时把上面的test函数改成尾递归方式,栈也会溢出
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值