C中文网python编程基础chapter-7-Python函数和lambda表达式

2019-09-04

在C语言中文网上,学习了python编程的基础课程,此为学习笔记记录,备查,备翻阅复习。

1. Python函数(函数定义、函数调用)用法详解

Python函数的定义

定义函数,也就是创建一个函数,可以理解为创建一个具有某些用途的工具。定义函数需要用 def 关键字实现,具体的语法格式如下:

def 函数名(形参列表):
    //由零条到多条可执行语句组成的代码块
    [return [返回值]]

其中,用 [] 括起来的为可选择部分,即可以使用,也可以省略。

  • 函数名:从语法角度来看,函数名只要是一个合法的标识符即可;从程序的可读性角度来看,函数名应该由一个或多个有意义的单词连缀而成,每个单词的字母全部小写,单词与单词之间使用下画线分隔。
  • 形参列表:用于定义该函数可以接收的参数。形参列表由多个形参名组成,多个形参名之间以英文逗号(,)隔开。一旦在定义函数时指定了形参列表,调用该函数时就必须传入相应的参数值,也就是说,谁调用函数谁负责为形参赋值。

Python函数的调用

函数名([形参值])

其中,函数名即指的是要调用的函数的名称;形参值指的是当初创建函数时要求传入的各个形参的值。需要注意的是,创建函数有多少个形参,那么调用时就需要传入多少个值,且顺序必须和创建函数时一致。即便该函数没有参数,函数名后的小括号也不能省略。

为函数提供说明文档

可以使用 Python 内置的 help() 函数查看其他函数的帮助文档,我们也经常通过 help() 函数查看指定函数的帮助信息。

>>.help()
# opened interactive help
help>print()

## 或者直接输入
help(print)

我们还可以为函数编写说明文档,只要把一段字符串放在函数声明之后、函数体之前,这段字符串将被作为函数的部分,这个文档就是函数的说明文档。

程序既可通过 help() 函数查看函数的说明文档,也可通过函数的 doc 属性访问函数的说明文档。下面程序示范了为函数编写说明文档:

def my_max(x, y) :
    '''
    获取两个数值之间较大数的函数。

    my_max(x, y)
        返回x、y两个参数之间较大的那个
    '''
    # 定义一个变量z,该变量等于x、y中较大的值
    z = x if x > y else y
    # 返回变量z的值
    return z

# 上面程序使用多行字符串的语法为 my_max() 函数编写了说明文档,接下来程序既可通过 help() 函数查看该函数的说明文档,也可通过 __doc__ 属性访问该函数的说明文档。
# 使用help()函数查看my_max的帮助文档
help(my_max)
#或者 print(my_max.__doc__)

2. Python函数值传递和引用传递(包括形式参数和实际参数的区别)

通常情况下,定义函数时都会选择有参数的函数形式,函数参数的作用是传递数据给函数,令其对接收的数据做具体的操作处理。

  • 形式参数:在定义函数时,函数名后面括号中的参数就是形式参数
  • 实际参数:在调用函数时,函数名后面括号中的参数称为实际参数,也就是函数的调用者给函数的参数。

Python 中,根据实际参数的类型不同,函数参数的传递方式可分为 2 种,分别为值传递引用(地址)传递

  1. 值传递:适用于实参类型为不可变类型(字符串、数字、元组);
  2. 引用(地址)传递:适用于实参类型为可变类型(列表,字典);

3. Python函数参数传递机制(超级详细)

所谓值传递,实际上就是将实际参数值的副本(复制品)传入函数,而参数本身不会受到任何影响。

如果实际参数的数据类型是可变对象(列表、字典),则函数参数的传递方式将采用引用传递方式。需要注意的是,引用传递方式的底层实现,采用的依然还是值传递的方式。

有如下两个结论:

  1. 不管什么类型的参数,在 Python 函数中对参数直接使用“=”符号赋值是没用的,直接使用“=”符号赋值并不能改变参数。
  2. 如果需要让函数修改某些数据,则可以通过把这些数据包装成列表、字典等可变对象,然后把列表、字典等可变对象作为参数传入函数,在函数中通过列表、字典的方法修改它们,这样才能改变这些数据。

4. Python位置参数

位置参数,有时也称必备参数,指的是必须按照正确的顺序将实际参数传到函数中,换句话说,调用函数时传入实际参数的数量和位置都必须和定义函数时保持一致。

实参和形参数量必须一致

实参和形参位置必须一致

5. Python函数关键字参数及用法

目前为止,我们使用函数时所用的参数都是位置参数,即传入函数的实际参数必须与形式参数的数量和位置对应。而本节将介绍的关键字参数,则可以避免牢记参数位置的麻烦,令函数的调用和参数传递更加灵活方便。 关键字参数是指使用形式参数的名字来确定输入的参数值。通过此方式指定函数实参时,不再需要与形参的位置完全一致,只要将参数名写正确即可。

# 定义一个函数
def girth(width , height):
    print("width: ", width)
    print("height: ", height)
    return 2 * (width + height)
# 传统调用函数的方式,根据位置传入参数
print(girth(3.5, 4.8))
# 根据关键字参数来传入参数
print(girth(width = 3.5, height = 4.8))
# 使用关键字参数时可交换位置
print(girth(height = 4.8, width = 3.5))
# 部分使用关键字参数,部分使用位置参数
print(girth(3.5, height = 4.8))

# 位置参数必须放在关键字参数之前,下面代码错误
print(girth(width = 3.5, 4.8))

需要说明的是,如果希望在调用函数时混合使用关键字参数和位置参数,则关键字参数必须位于位置参数之后。换句话说,在关键字参数之后的只能是关键字参数。

6. Python函数默认参数设置(超级详细)

在调用函数时,如果不指定某个参数,解释器会抛出异常。为了解决这个问题,Python 允许为参数设置默认值,即在定义函数时,直接给形式参数指定一个默认值,这样的话,即便调用函数时没有给拥有默认值的形参传递参数,该参数可以直接使用定义函数时设置的默认值。

定义带有默认值参数的函数,其语法格式如下:

def 函数名(...形参名=默认值)
    代码块

注意,在使用此格式定义函数时,指定有默认值的形式参数必须在所有没默认值参数的最后,否则会产生语法错误。

再次强调,由于 Python 要求在调用函数时关键字参数必须位于位置参数的后面,因此在定义函数时指定了默认值的参数(关键字参数)必须在没有默认值的参数之后。例如以下函数:

# 定义一个打印三角形的函数,有默认值的参数必须放在后面
def printTriangle(char, height = 5) :
    for i in range(1, height + 1) :
        # 先打印一排空格
        for j in range(height - i) :
            print(' ', end = '')
        # 再打印一排特殊字符
        for j in range(2 * i - 1) :
            print(char, end = '')
        print()
printTriangle('@', 6)
printTriangle('#', height=7)
printTriangle(char = '*')

上面程序定义了 printTriangle() 函数,其中,有默认值的 height 形参,必须放在 char 形参的后面;反之,将会造成语法错误。

 在 Python 中,可以使用“函数名.__defaults__”查看函数的默认值参数的当前值,其返回值是一个元组。例如,显示上面定义的 printTriangle 函数的默认值参数的当前值,可以使用 printTriangle.__defaults__ ,其结果为 (5,)。 

7. Python可变参数函数用法详解

很多编程语言都允许定义个数可变的参数,这样可以在调用函数时传入任意多个参数。Python 也不例外,在定义函数时也可以使用可变参数。

可变参数,又称不定长参数,即传入函数中的实际参数可以是任意多个。Python 定义可变参数,主要有以下 2 种形式。

1) 可变参数:形参前添加一个 ‘*’

*parameter

其中,parameter 表示形参名。这种形式表示接受任意多个实际参数,并将其放到一个元组中。

2) 可变参数:形参前添加两个’*’

 **parameter

其中,parameter 表示形参名。这种形式可以接收任意多个以关键字参数赋值的实际参数,并将其放到一个字典中。

# 定义了支持参数收集的函数
def test(x, y, z=3, *books, **scores) :
    print(x, y, z)
    print(books)
    print(scores)
test(1, 2, 3, "C语言中文网" , "Python教程", 语文=89, 数学=94)

逆向参数收集

所谓逆向参数收集,指的是在程序己有列表、元组、字典等对象的前提下,把它们的元素“拆开”后传给函数的参数。逆向参数收集需要在传入的列表、元组参数之前添加一个星号,在字典参数之前添加两个星号。例如如下代码:

def test(name, message):
    print("用户是: ", name)
    print("欢迎消息: ", message)
my_list = ['孙悟空', '欢迎来C语言中文网']
test(*my_list)

程序中定义了一个需要两个参数的函数,而 my_list 列表包含两个元素,为了让程序将 my_list 列表的两个元素传给 test() 函数,程序在传入的 my_list 参数之前添加了一个星号。

如果不使用逆向收集(不在元组参数之前添加星号),整个元组将会作为一个参数,而不是将元组的元素作为多个参数。

8. Python return函数返回值详解

Python中,用 def 语句创建函数时,可以用 return 语句指定应该返回的值,该返回值可以是任意类型。需要注意的是,return 语句在同一函数中可以出现多次,但只要有一个得到执行,就会直接结束函数的执行。

函数中,使用 return 语句的语法格式如下:

return [返回值]

9. Python函数返回多个值的方法(入门必读)

其中,返回值参数可以指定,也可以省略不写(将返回空值 None)。

如果程序需要有多个返回值,则既可将多个值包装成列表之后返回,也可直接返回多个值。如果 Python 函数直接返回多个值,Python 会自动将多个返回值封装成元组。

def sum_and_avg(list):
    sum = 0
    count = 0
    for e in list:
        # 如果元素e是数值
        if isinstance(e, int) or isinstance(e, float):
            count += 1
            sum += e
    return sum, sum / count
my_list = [20, 15, 2.8, 'a', 35, 5.9, -1.8]
# 获取sum_and_avg函数返回的多个值,多个返回值被封装成元组
tp = sum_and_avg(my_list) #①
print(tp)

## 也可使用 Python 提供的序列解包功能,直接使用多个变量接收函数返回的多个值。例如如下代码:
s, avg = sum_and_avg(my_list) #②
print(s)
print(avg)

10. Python函数递归(带实例演示)

在一个函数体内调用它自身,被称为函数递归。函数递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。

例如有如下数学题。己知有一个数列:f(0) = 1,f(1) = 4,f(n + 2) = 2*f(n+ 1) +f(n),其中 n 是大于 0 的整数,求 f(10) 的值。这道题可以使用递归来求得。下面程序将定义一个 fn() 函数,用于计算 f(10) 的值。

def fn(n) :
    if n == 0 :
        return 1
    elif n == 1 :
        return 4
    else :
        # 函数中调用它自身,就是函数递归
        return 2 * fn(n - 1) + fn(n - 2)
# 输出fn(10)的结果
print("fn(10)的结果是:", fn(10))

在上面的 fn() 函数体中再次调用了 fn() 函数,这就是函数递归。注意在 fn() 函数体中调用 fn 的形式:

return 2 * fn(n - 1) + fn(n - 2)

仔细看上面递归的过程,当一个函数不断地调用它自身时,必须在某个时刻函数的返回值是确定的,即不再调用它自身:否则,这种递归就变成了无穷递归,类似于死循环。因此,在定义递归函数时有一条最重要的规定: 递归一定要向已知方向进行。

递归是非常有用的,例如程序希望遍历某个路径下的所有文件,但这个路径下的文件夹的深度是未知的,那么就可以使用递归来实现这个需求。系统可定义一个函数,该函数接收一个文件路径作为参数,该函数可遍历出当前路径下的所有文件和文件路径,即在该函数的函数体中再次调用函数自身来处理该路径下的所有文件路径。

11. Python变量作用域(全局变量和局部变量)

在程序中定义一个变量时,这个变量是有作用范围的,变量的作用范围被称为它的作用域。换句话说,变量的作用域指的是程序代码能够访问该变量的区域,如果超过该区域,将无法访问该变量。

根据定义变量的位置(有效范围),可以将变量分为局部变量和全局变量。

局部变量是指在函数内部定义并使用的变量,它只在函数内部有效。每个函数在执行时,系统都会为该函数分配一块“临时内存空间”,所有的局部变量都被保存在这块临时内存空间内。当函数执行完成后,这块内存空间就被释放了,这些局部变量也就失效了,因此离开函数之后就不能再访问局部变量了,否则解释器会抛出 NameError 错误。

全局变量和局部变量相对应,全局变量指的是能作用于函数内外的变量,即全局变量既可以在各个函数的外部使用,也可以在各函数内部使用。定义全局变量的方式有以下 2 种:

  • 在函数体外定义的变量,一定是全局变量
  • 在函数体内定义全局变量。即使用 global 关键字对变量进行修饰后,该变量就会变为全局变量。
# 在函数体外定义的变量
demo = "C语言中文网"
def text():
    print("函数体内访问:",demo)
text()
print('函数体外访问:',demo)

# 使用 global 关键字对变量进行修饰
def text():
    global demo
    demo = "C语言中文网"
    print("函数体内访问:",demo)
text()
print('函数体外访问:',demo)

注意,在使用 global 关键字修饰变量名时,不能直接给变量赋初值,否则会引发语法错误。

获取指定作用域范围中的变量

不管是在函数的局部范围内还是在全局范围内,都可能存在多个变量,每个变量“持有”该变量的值。从这个角度来看,不管是局部范围还是全局范围,这些变量和它们的值就像一个“看不见”的字典,其中变量名就是字典的 key,变量值就是字典的 value。实际上,python提供了如下三个工具函数来获取指定范围内的“变量字典”:

  1. globals():该函数返回全局范围内所有变量组成的“变量字典”。
  2. locals():该函数返回当前局部范围内所有变量组成的“变量字典”。
  3. vars(object):获取在指定对象范围内所有变量组成的“变量字典”。如果不传入object 参数,vars() 和 locals() 的作用完全相同。

变量和局部变量的遮蔽现象

全局变量默认可以在所有函数内被访问,但如果在函数中定义了与全局变量同名的变量,此时就会发生局部变量遮蔽(hide)全局变量的情形。

Python 语法规定,在函数内部对不存在的变量赋值时,默认就是重新定义新的局部变量,这会使得函数内部遮蔽重名的 name 全局变量。由于局部变量 name 在 print(name) 后才初始化,所以程序会报错。

12. Python局部函数及用法(包含nonlocal关键字)

前面所看到的函数都是在全局范围内定义的,它们都是全局函数。Python 还支持在函数体内定义函数,这种被放在函数体内定义的函数称为局部函数。

在默认情况下,局部函数对外部是隐藏的,局部函数只能在其封闭(enclosing)函数内有效,其封闭函数也可以返回局部函数,以便程序在其他作用域中使用局部函数。

def get_math_func(type, nn) :
    # 定义一个计算平方的局部函数
    def square(n) :  # ①
        return n * n
    # 定义一个计算立方的局部函数
    def cube(n) :  # ②
        return n * n * n
    # 定义一个计算阶乘的局部函数
    def factorial(n) :   # ③
        result = 1
        for index in range(2, n + 1) :
            result *= index
        return result
    # 调用局部函数
    if type == "square" :
        return square(nn)
    elif type == "cube":
        return cube(nn)
    else:
        return factorial(nn)
print(get_math_func("square", 3)) # 输出9
print(get_math_func("cube", 3)) # 输出27
print(get_math_func("", 3)) # 输出6

13. Python函数使用方法(高级用法)

Python的函数是“一等公民”,因此函数本身也是一个对象,函数既可用于赋值,也可用作其他函数的参数,还可作为其他函数的返回值。

使用函数变量

Python 的函数也是一种值:所有函数都是 function 对象,这意味着可以把函数本身赋值给变量,就像把整数、浮点数、列表、元组赋值给变量一样。 当把函数赋值给变量之后,接下来程序也可通过该变量来调用函数。

# 定义一个计算乘方的函数
def pow(base, exponent) :
    result = 1
    for i in range(1, exponent + 1) :
        result *= base
    return result
# 将pow函数赋值给my_fun,则my_fun可当成pow使用
my_fun = pow
print(my_fun(3 , 4)) # 输出81
# 定义一个计算面积的函数
def area(width, height) :
    return width * height
# 将area函数赋值给my_fun,则my_fun可当成area使用
my_fun = area
print(my_fun(3, 4)) # 输出12

使用函数作为返回值

Python 还支持使用函数作为其他函数的返回值。

def get_math_func(type) :
    # 定义一个计算平方的局部函数
    def square(n) :  # ①
        return n * n
    # 定义一个计算立方的局部函数
    def cube(n) :  # ②
        return n * n * n
    # 定义一个计算阶乘的局部函数
    def factorial(n) :   # ③
        result = 1
        for index in range(2 , n + 1):
            result *= index
        return result
    # 返回局部函数
    if type == "square" :
        return square
    elif type == "cube" :
        return cube
    else:
        return factorial
# 调用get_math_func(),程序返回一个嵌套函数
math_func = get_math_func("cube") # 得到cube函数
print(math_func(5)) # 输出125
math_func = get_math_func("square") # 得到square函数
print(math_func(5)) # 输出25
math_func = get_math_func("other") # 得到factorial函数
print(math_func(5)) # 输出120

使用函数作为函数形参

有时候需要定义一个函数,该函数的大部分计算逻辑都能确定,但某些处理逻辑暂时无法确定,这意昧着某些程序代码需要动态改变,如果希望调用函数时能动态传入这些代码,那么就需要在函数中定义函数形参,这样即可在调用该函数时传入不同的函数作为参数,从而动态改变这段代码。 Python 支持像使用其他参数一样使用函数参数,例如如下程序:

# 定义函数类型的形参,其中fn是一个函数
def map(data, fn) :   
    result = []
    # 遍历data列表中每个元素,并用fn函数对每个元素进行计算
    # 然后将计算结果作为新数组的元素
    for e in data :
        result.append(fn(e))
    return result
# 定义一个计算平方的函数
def square(n) :
    return n * n
# 定义一个计算立方的函数
def cube(n) :
    return n * n * n
# 定义一个计算阶乘的函数
def factorial(n) :
    result = 1
    for index in range(2, n + 1) :
        result *= index
    return result
data = [3 , 4 , 9 , 5, 8]
print("原数据: ", data)
# 下面程序代码3次调用map()函数,每次调用时传入不同的函数
print("计算数组元素的平方")
print(map(data , square))
print("计算数组元素的立方")
print(map(data , cube))
print("计算数组元素的阶乘")
print(map(data , factorial))

上面程序中定义了一个 map() 函数,该函数的第二个参数是一个函数类型的参数,这意味着每次调用函数时可以动态传入一个函数,随着实际传入函数的改变,就可以动态改变 map() 函数中的部分计算代码。

14. Python lambda表达式及用法

lambda 表达式是现代编程语言争相引入的一种语法,如果说函数是命名的、方便复用的代码块,那么 lambda 表达式则是功能更灵活的代码块,它可以在程序中被传递和调用。

lambda 表达式的语法格式如下:

 lambda [parameter_list] : 表达式

从上面的语法格式可以看出 lambda 表达式的几个要点:

  • lambda 表达式必须使用 lambda 关键字定义。
  • 在 lambda 关键字之后、冒号左边的是参数列表,可以没有参数,也可以有多个参数。如果有多个参数,则需要用逗号隔开,冒号右边是该 lambda 表达式的返回值。

lambda 表达式的本质就是匿名的、单行函数体的函数。因此,lambda 表达式可以写成函数的形式。 例如,对于如下 lambda 表达式:

 lambda x , y:x + y

可改写为如下函数形式:

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

总体来说,函数比 lambda 表达式的适应性更强,lambda 表达式只能创建简单的函数对象(它只适合函数体为单行的情形)。但 lambda 表达式依然有如下两个用途:

  • 对于单行函数,使用 lambda 表达式可以省去定义函数的过程,让代码更加简洁。
  • 对于不需要多次复用的函数,使用 lambda 表达式可以在用完之后立即释放,提高了性能。

本节所介绍的 lambda 表达式是 Python 编程的核心机制之一。Python 语言既支持面向过程编程,也支持面向对象编程。而 lambda 表达式是 Python 面向过程编程的语法基础,因此读者必须引起重视。

Python 的 lambda 表达式只是单行函数的简化版本,因此 lambda 表达式的功能比较简单。