Learn_Python_函数12

一.函数的概述

1.什么是函数

简单来说,函数就是个黑匣子,它接收输入(参数),然后执行特定任务以完成特定功能,最后生成输出(返回值)。其中,输入(参数)和输出(返回值)都是可选的,也就是说,可以有也可以没有。

函数就是执行特定任务以完成特定功能的一段代码。可以在程序中将某段代码定义成函数,并指定一个函数名及接收的输入(参数),这样,就可以在程序的其它地方通过函数名多次调用并执行该段代码了,每次调用并执行后,都会根据接收的输入(参数)执行特定的任务以完成特定功能从而生成相应的输出(返回值)
Python语言已经定义了很多内置函数。我们可以在程序中通过函数名直接调用这些内置函数。

例如:当我们调用内置函数id()时,输入(参数)是任意的对象,完成的特定功能是获取输入(参数)的唯一标识,输出(返回值)是输入(参数)的唯一标识。

from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu.jpg', width=300, height=150)
from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu1.jpg', width=300, height=150)
# 第1次调用内置函数id(),输入(参数)是一个整数类型的对象
print(id(18))
# 第2次调用内置函数id(),输入(参数)是一个字符串类型的对象
print(id('hello'))
# 第2次调用内置函数id(),输入(参数)是一个列表类型的对象
print(id([1,2,3]))
140725874651424
2329163585160
2329163604552
2.为什么需要函数
from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu2.jpg', width=300, height=150)

复用代码

如果在程序中需要多次完成某个特定的功能,我们无需把这个特定功能的相关代码在多个地方编写多次,完全可以把这个特定功能的相关代码定义成函数,然后再多个地方调用该函数,每次调用都会把函数对应的相关代码执行一遍。

隐藏实现细节

函数就是个黑匣子,将实现细节隐藏起来了。很多时候我们无需关注函数的实现细节,只需关注其接收的输入(参数)及生成的输出(返回值)就可以了。

提高可维护性

把完成某段特定功能的代码定义为函数后,如果需要对这段代码进行修改,只需要在一个地方进行修改,提高了程序的可维护性。否则,需要找到这段代码的多个不同地方,每个地方都要进行同样的修改,费事费力还容易出错。

提高可读性、便于调式

每个函数都对应一段完成特定功能的代码,提高了程序的可读性,也便于程序调试。

二.函数的定义和调用

1.定义函数

定义函数的语法格式:

def 函数名([形式参数1,形式参数2,……形式参数n]):

    函数体

其中,def是Python定义的关键字。

关于函数名的说明:

a.每个函数都有一个函数名,用于函数的调用。

b.函数名属于标识符,因此,必须遵守标识符的命名规则,推荐遵守标识符的命名规范。此外,函数名最好是动宾格式,以表明函数完成的特定功能,例如:handle_message、print_result。

关于函数体的说明:

a.函数体是用于执行特定任务以完成特定功能的主体代码。

b.函数体对应的代码块必须缩进。

c.如果函数需要有输出(返回值),可以在函数体内通过语句return xxx语句返回,同时结束函数体的执行。如果函数不需要有输出(返回值),可以在函数体内通过语句return直接结束函数体的执行,或者让函数体正常执行结束,其实,函数在这两种情况下都是有返回值的,其返回值都是None。

d.函数体在调用的时候才会被执行,因此,定义函数不会改变程序的执行流程。

def decide_args(arg1,arg2):
    if arg1 and arg2:
        return arg1,arg2
    elif (not arg1) and (not arg2):
        return
    else:
        result = arg1 or arg2
from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu3.jpg', width=300, height=150)
from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu4.jpg', width=300, height=150)
from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu5.jpg', width=400, height=200)
print(type(decide_args))
<class 'function'>
2.调用函数

调用函数时,每个实参都被用于初始化相应的形参。所有形参都被初始化之后,函数体对应的代码块被执行。程序的执行流会跳转到定义函数的函数体内,执行安函数体对应的代码块,执行完函数体后再跳回到调用函数的地方,继续执行下一条语句。

print(decide_args(18,'Hello'))
(18, 'Hello')
print(decide_args([],{}))
None
print(decide_args(18,[]))
None

三.函数的调用之位置实参

调用函数时,可以根据每个形参在所有形参中的位置传递对应位置的实参,从而用每个实参初始化对应位置的形参,这样的实参称为位置实参。

上节课在讲解“调用函数”时我们举的例子都是位置实参。

def f(a,b,c):
    print('a =',a,'b =',b,'c = ',c)
f(2,5,8)
a = 2 b = 5 c =  8
from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu6.jpg', width=400, height=200)

四.函数的调用之关键字实参

函数调用时,传递的实参的形式可以为:形参名 = 实参值,从而用指定的实参值初始化指定名称的形参,这样的实参称为关键字实参。

f(a = 2,b = 5,c = 8)
from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu7.jpg', width=300, height=150)
a = 2 b = 5 c =  8
f(b = 5,c = 8,a = 2)
f(c = 8,b = 5,a = 2)
from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu8.jpg', width=300, height=150)
a = 2 b = 5 c =  8
a = 2 b = 5 c =  8

调用函数时,可以组合使用位置实参和关键字实参。但是,位置实参必须位于关键字实参之前。否则,无法根据位置来匹配位置实参和对应的形参。

f(2,5,c = 8)
# f(2,c = 8,5)
a = 2 b = 5 c =  8

五.函数的调用之实参的传递

前面的课程学过:“变量名相当于标签。对于赋值语句:变量 = 对象,相当于给对象贴上了一个标签,标签名就是变量名。”

调用函数时把实参传递给形参从而用实参初始化形参,本质上执行了赋值语句:形参 = 实参对象,相当于给实参对象贴上了一个标签,标签名就是形参名。

如果实参对象是可变类型,在函数体内对形参对象的任何修改其实就是对实参对象的修改。

def f(arg1,arg2):
    print('初始化形参后:arg1 =',arg1,'arg2 =',arg2)
    arg1 = arg1 * 2
    arg2.append(4)
    print('修改形参后:arg1 =',arg1,'arg2 =',arg2)
i = 10
L = [1,2,3]
print('调用函数前:i =',i,'L =',L)
f(i,L)
print('调用函数后:i =',i,'L =',L)
from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu9.jpg', width=400, height=200)
调用函数前:i = 10 L = [1, 2, 3]
初始化形参后:arg1 = 10 arg2 = [1, 2, 3]
修改形参后:arg1 = 20 arg2 = [1, 2, 3, 4]
调用函数后:i = 10 L = [1, 2, 3, 4]
i = 10
L = [1,2,3]
print('调用函数前:i =',i,'L =',L)
f(i,L[:])    #切片操作,与上面对比,看图,调用函数并没改变 L
print('调用函数后:i =',i,'L =',L)
from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu10.jpg', width=400, height=200)
调用函数前:i = 10 L = [1, 2, 3]
初始化形参后:arg1 = 10 arg2 = [1, 2, 3]
修改形参后:arg1 = 20 arg2 = [1, 2, 3, 4]
调用函数后:i = 10 L = [1, 2, 3]

六.函数的定义之多个返回值

如果需要在调用函数后有多个返回值,可以在定义函数时在函数体内使用return语句返回由多个返回值组成的元组。

# 把列表中的所有数分成奇数和偶数两类
def classify_numbers(numbers):
    odds = []
    evens = []
    for number in numbers:
        if number % 2:
            odds.append(number)
        else:
            evens.append(number)
    return odds, evens
print(classify_numbers([15,86,39,26,53,68]))
([15, 39, 53], [86, 26, 68])
#  查找列表中的最小值和最大值
def lookup_min_max(numbers):
    if len(numbers) == 0:
        return
    min_num = numbers[0]
    max_num = numbers[0]
    for number in numbers[1:len(numbers)]:
        if number < min_num:
            min_num = number
        elif number > max_num:
            max_num = number
    return min_num, max_num
print(lookup_min_max([35,26,19,86,93,68]))
from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu11.jpg', width=300, height=150)
(19, 93)

七.函数的定义之带默认值的形参

定义函数时,可以给形参设置默认值,这样,调用函数时如果不传递对应的实参,就会使用设置的默认值初始化形参。

给形参设置默认值的语法格式为:形参 = 默认值。

给形参设置默认值之后,可以简化函数的调用,只有与默认值不符的形参才需要传递额外的实参

def f1(a, b = 5):
    print('a =', a, 'b =', b)
f1(2, 6)
f1(2)
a = 2 b = 6
a = 2 b = 5
def f2(a, b = 5, c = 8):
    print('a =', a, 'b =', b, 'c =',c)
f2(2,6,9)
f2(2)
f2(2,6)
f2(2, c = 9)
a = 2 b = 6 c = 9
a = 2 b = 5 c = 8
a = 2 b = 6 c = 8
a = 2 b = 5 c = 9

定义函数时,没有设置默认值的形参必须位于设置了默认值的形参之前,否则,无法根据位置来匹配位置实参和对应的形参。

# def f(b = 5, a):
#    print()
#假设上面的定义是合法的,对于调用f(2),你可能想把实参2传递给形参a,但是实参2是位置实参,因此,实参2会传递给形参b,从而导致形参a存在实参缺失。

当函数有多个形参时,把变化大的形参放在前面,把变化小的形参放在后面,变化小的形参就可以设置默认值。

#  给形参设置默认值之后,调用函数时就存在多种调用方式
def fun(a,b = 5):
    print('a =', a, 'b =', b)
fun(3)
fun(a = 3)
fun(3,6)
fun(a = 3,b = 6)
fun(b = 6,a = 3)
fun(3,b = 6)
from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu12.jpg', width=300, height=150)
a = 3 b = 5
a = 3 b = 5
a = 3 b = 6
a = 3 b = 6
a = 3 b = 6
a = 3 b = 6

定义函数时,给形参设置的默认值就被计算出来了。因此,如果给形参设置的默认值是可变类型的对象,并且前一次调用函数时在函数体内修改了形参的默认值那么修改后的值将作为下一次调用函数时形参的默认值

def fun1(L = []):
    L.append(18)
    print(L)
fun1()
fun1()
fun1()
[18]
[18, 18]
[18, 18, 18]

不要把形参的默认值设置为可变类型的对象

def fun2(L = None):
    if L is None:
        L = []
    L.append(18)
    print(L)
fun2()
fun2()
fun2()
[18]
[18]
[18]

八.函数的定义之使用 * 定义关键字形参

定义函数时,可以在所有形参的某个位置添加一个 * ,这样, * 后面的所有形参都被定义为只能接收关键字实参的关键字形参。

def f(a,b,*,c,d):
    print('a =',a,'b =',b,'c =',c,'d =',d)
f(1,2,c = 3,d = 4)
# f(1,2,3,4)  # TypeError: f() takes 2 positional arguments but 4 were given
from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu13.jpg', width=300, height=150)
a = 1 b = 2 c = 3 d = 4

九.函数定义之使用 * 定义个数可变的位置形参

定义函数时,可能无法事先确定传递的位置实参的个数,在这这种情况下,可以在形参前添加一个 * ,将形参定义为个数可变的位置形参,从而可以接收0个或任意多个位置实参.

这些位置实参会将个数可变的位置形参初始化为一个元组。

def f(*args):
    print(args)

f()
f(1)
f(1,2,3)
()
(1,)
(1, 2, 3)

定义函数时,最多只能定义一个个数可变的位置形参。

#  def fun(*arg1, *arg2):

很多内置函数都定义了个数可变的位置形参。例如,内置函数print()

print()
print(1)
print(1,2)
print(1,2,3)
1
1 2
1 2 3

通常,把个数可变的位置形参定义为最后一个形参,以便接收所有剩余的位置实参。

def fun1(a, b, *c):
    print('a =', a, 'b =', b, 'c =', c)
fun1(1,2,3,4,5)
a = 1 b = 2 c = (3, 4, 5)

如果个数可变的位置形参不是最后一个形参,那么其后面的所有形参都被定义为只能接收关键字实参的关键字形参。

def fun2(a, *b, c, d):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d)
fun2(1,2,3,4,c = 5,d = 6)
# 这样写就不行,会都传给b
# fun2(1,2,3,4,5,6)  #TypeError: fun2() missing 2 required keyword-only arguments: 'c' and 'd'

from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu14.jpg', width=300, height=150)
a = 1 b = (2, 3, 4) c = 5 d = 6

十.函数的调用之使用 * 将序列中的每个元素都转换为位置实参

def f(a, b, c):
    print('a =', a, 'b =', b, 'c =', c)
f(1,2,3)
a = 1 b = 2 c = 3
L = [1,2,3]
# 列表L整体作为一个位置实参
# f(L)
f(L[0],L[1],L[2])
a = 1 b = 2 c = 3

调用函数时,可以在序列前面添加一个 * ,从而将序列中的每个元素都转换为一个单独的位置实参。

f(*L)  #相当于f(L[0],L[1],L[2])
a = 1 b = 2 c = 3

注意和个数可变的位置形参进行区分。个数可变的位置形参是在定义函数时使用,使用 * 将序列中的每个元素都转换为位置实参是在调用函数时使用。

def fun(*args):
    print(args)
fun(L) # 列表L整体作为一个位置实参
fun(*L) # 先将序列中的每个元素都转换为一个单独的位置实参
([1, 2, 3],)
(1, 2, 3)

十一.函数的定义之使用 ** 定义个数可变的关键字形参

与“函数的定义之使用 * 定义个数可变的位置形参”对比学习

定义函数时,可能无法事先确定传递的关键字实参的个数,在这这种情况下,可以在形参前添加一个 * ,将形参定义为个数可变的关键字形参,从而可以接收0个或任意多个关键字实参.

这些关键字实参会将个数可变的关键字形参初始化为一个字典。

def f(**kwargs):
    print(kwargs)
f()
f(a = 1)
f(a = 1,b = 2,c = 3)
{}
{'a': 1}
{'a': 1, 'b': 2, 'c': 3}

定义函数时,最多只能定义一个个数可变的关键字形参。

def fun1(**kwargs1,**kwargs2):
    print(kwargs1,kwargs2)
  File "<ipython-input-20-c65ef12239d3>", line 1
    def fun1(**kwargs1,**kwargs2):
                        ^
SyntaxError: invalid syntax

很多内置函数都定义了个数可变的关键字形参。例如,内置函数sorted()的定义为:def sorted(* args,** kwargs):

L = ['Python','Java','Swift']
print(sorted(L))
print(sorted(L, key = len))
print(sorted(L, key = len, reverse = True))
['Java', 'Python', 'Swift']
['Java', 'Swift', 'Python']
['Python', 'Swift', 'Java']

因为调用函数时位置实参必须位于关键字实参之前,所以个数可变的位置形参必须位于个数可变的关键字形参之前。

def fun(*args,**kwargs):
    print(kwargs,args)

十二.函数的调用之使用 ** 将字典中的每个键值对都转换为关键字实参

与“函数的调用之使用 * 将序列中的每个元素都转换为位置实参”对比学习

def f(a,b,c):
    print('a =',a,'b =',b,'c =',c)
f(a = 1,b = 2,c = 3)
a = 1 b = 2 c = 3
d = {'a':1,'b':2,'c':3}
# f(d)  #TypeError: f() missing 2 required positional arguments: 'b' and 'c'
f(a = d['a'],b = d['b'],c = d['c'])
a = 1 b = 2 c = 3

调用函数时,可以在字典前添加两个 ** ,从而将字典中的每个键值对都转换为一个单独的关键字实参。

f(**d)  #相当于f(a = d['a'],b = d['b'],c = d['c'])
a = 1 b = 2 c = 3

注意和个数可变的关键字形参进行区分。个数可变的关键字形参是定义函数时使用,使用 ** 将字典中的每个键值对都转换为关键字实参是在调用函数时使用。

def fun(**kwargs):
    print(kwargs)
# 先将字典中的每个键值对都转换为一个单独的关键字实参
# 再用这些关键字实参将个数可变的关键字形参初始化为一个字典。
fun(**d)
{'a': 1, 'b': 2, 'c': 3}

十三.函数的各种参数大总结

from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu15.jpg', width=400, height=200)
def f1(a, b = 5, *args, **kwargs):
    print('a =', a, 'b =', b, 'args =', args, 'kwargs =', kwargs)
f1(2, 6, 7, 8, c = 9)
f1(2)
from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu16.jpg', width=300, height=150)
a = 2 b = 6 args = (7, 8) kwargs = {'c': 9}
a = 2 b = 5 args = () kwargs = {}
def f2(a, b = 5, *, c, **kwargs):
    print('a =', a, 'b =', b, 'c =', c, 'kwargs =', kwargs)
f2(*(3, 6), **{'c':8, 'd':10})
f2(3, c = 8, d = 10)
from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu17.jpg', width=300, height=150)
a = 3 b = 6 c = 8 kwargs = {'d': 10}
a = 3 b = 5 c = 8 kwargs = {'d': 10}

十四.pass语句

pass语句什么都不做,它只是一个占位符,用在语法上需要语句的地方,例如:

1.if语句的条件执行体

2.for-in语句的循环体

3.定义函数时的函数体

有时候可能还没想好上述语句该怎么写,就可以先使用pass语句作为占位符,以确保程序可以运行,等想好了之后再把pass语句替换掉。

# 先把整体框架搭好,以后再补充pass内容
age = 23
if age > 18:
    pass
for i in range(8):
    pass
def do_something():
    pass

十五.函数的定义之文档字符串

1. 什么是文档字符串

对于函数、模块、类或方法,位于其第一行的字符串被称为文档字符串,通常用三个引号表示。

文档字符串用于对函数、模块、类或方法进行解释说明。

之所以称为“文档”字符串,是因为可以使用工具根据文档字符串自动地生成文档。

应该养成编写文档字符串地习惯,以提高程序的可读性。

通过属性_doc_可以访问文档字符串。

调用内置函数help()得到的帮助信息中会包含文档字符串。

print(len.__doc__)
Return the number of items in a container.
print(help(len))
Help on built-in function len in module builtins:

len(obj, /)
    Return the number of items in a container.

None

函数的文档字符串的常见内容和格式约定

1.第一行是简明扼要的总结

2.第一行的首字母大写,第一行以句号结尾

3.如果文档字符串包含多行,第二行是空行,从第三行开始是详细的描述

更多关于文档字符串的约定,可参考PEP 257: https://www.python.org/dev/peps/pep-0257/

def form_complex(real = 0.0, imag = 0.0):
    """Form a complex number.
    
    Keyword arguments:
    real -- the real part (default 0.0)
    imag -- the imaginary part (default 0.0) 
    """
    pass

十六.函数的定义之函数注解

什么是函数注解

定义函数时,为了让形参或返回值的类型或作用更加清晰,可以给形参或返回值添加函数注解,

从而对形参或返回值做解释说明,以帮助函数文档化。

函数注解时可选的,可以添加也可以不添加。

函数注解可以是任意的表达式。

解释器会忽略函数注解,因此解释器并不会使用函数注解来检查实参的类型和返回值的类型。

更多关于函数注解,可参考PEP 257: https://www.python.org/dev/peps/pep-3107/

添加函数注解

给形参添加函数注解的方式为:在形参后面添加:和任意的表达式。

给返回值添加函数注解的方式为:在 ) 的后面添加 -> 和任意的表达式。

def f(a: 'string type', b: int) -> 'join a with b':
    return a + str(b)
print(f('hello',12.3))
from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu18.jpg', width=300, height=150)
hello12.3

访问函数注解

通过属性 annotations 可以访问函数注解

调用内置函数help()得到的帮助信息中会包含函数注解

print(f.__annotations__)
{'a': 'string type', 'b': <class 'int'>, 'return': 'join a with b'}
print(help(f))
Help on function f in module __main__:

f(a: 'string type', b: int) -> 'join a with b'

None

十七.递归函数

什么是递归函数

在一个函数的函数体内,可以调用其它函数。

如果在一个函数的函数体内调用了该函数本身,该函数就是递归函数。

递归函数包含了一种隐私的循环,因此,递归函数必须有一个明确的递归结束条件,也称为递归出口。



能用递归来解决的问题必须满足两个条件:

1.可以通过递归调用来缩小问题的规模,且新问题与原问题有着相同的形式。

2.存在一种简单情境,可以使递归在简单情境下退出。



递归函数的优点是定义简单,逻辑清晰

例子:使用递归计算阶乘

n! = 1 * 2 * 3 … * n = (n - 1)! * n, 且 1! = 1

如果用函数表示fac(n)表示n!,那么fac(n) = fac(n - 1) * n = n * fac(n - 1),且fac(1) = 1

def fac(n):
    if n == 1:
        return 1
    return n * fac(n - 1)
print('fac(6) =', fac(6))
from IPython.display import Image
Image(filename = 'F:/Jupyter/Python/hanshu19.jpg', width=300, height=150)
fac(6) = 720

使用递归计算斐波那契数列

F0 = 0,F1 = 1,Fn = F(n - 1) + F(n - 2) (n >= 2)

如果用函数fib(n)表示Fn,那么fib(n) = fib(n - 1) + fib(n - 2),且fib(0) = 0,fib(1) = 1

def fib(n):
    if n == 0:
        return 0
    if n == 1:
        return 1
    return fib(n - 1) + fib(n - 2)
print('fib(6) =',fib(6))
fib(6) = 8

学习参考:

  1. 图解Python: http://e-learning.51cto.com/course/12803
  2. Python菜鸟教程:https://www.runoob.com/python/python-tutorial.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZPILOTE

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值