疯狂Python讲义-李刚编著-第5章函数和lambda表达式

第5章 函数和lambda表达式

5.1.函数入门

5.1.1.理解函数

5.1.2.定义函数和调用函数

5.1.3.为函数提供文档

程序既可通过 help() 函数查看函数的说明文档,也可以通过函数的 __doc__属性访问函数的说明文档。

def my_max(x, y):
    z = x if x > y else y
    return z

help(my_max)
print(my_max.__doc__)

5.1.4.多个返回值

如果程序需要有多个返回值,则既可将多个值包装成列表之后返回,也可直接返回多个值。如果 Python 函数直接返回多个值,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

# 使用序列解包来获取多个返回值
s, avg = sum_and_avg(my_list)
print(s)
print(avg)

5.1.5.递归函数

在一个函数体内调用它自身,被称为函数递归。

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

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

5.2.函数的参数

5.2.1.关键字(keyword)参数

按照形参位置传入的参数被称为位置参数。如果使用位置参数的方式来传入参数值,则必须严格按照定义函数时指定得得顺序来传入参数值;如果根据参数名来传入参数值,则无须遵守定义形参的顺序,这种方式被称为关键字(keyword)参数。

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(3.5, height = 4.8))

5.2.2.参数默认值

Python 规定:关键字参数必须位于位置参数的后面,因此在定义函数时指定了默认值的参数(关键字参数)必须在没有默认值得参数之后。

5.2.3.参数收集(个数可变的参数)

很多编程语言都允许定义个数可变的参数,这样可以在调用函数时传入任意多个参数。Python 允许在形参前面添加一个星号(*),这样就意味着该参数可接收多个参数值,多个参数值被当成元组传入。

def test(a, *books):
    print(books)
    for b in books:
        print(b)
    print(a)

# 调用
test(5, "疯狂 iOS 讲义", "疯狂 Android 讲义")

Python 允许个数可变的形参可以处于形参列表的任意位置(不要求时形参列表的最后一个参数),但 Python 要求一个函数最多只能带一个支持"普通"参数收集的形参。

def test(*books, num):
    print(books)
    for b in books:
        print(b)

# 调用 test() 函数
test("疯狂 iOS 讲义", "疯狂 Android 讲义", num = 20)

test() 函数的第一个参数就是个数可变的形参,由于该参数可接收个数不等的参数值,因此如果需要给后面的参数传入参数值,则必须使用关键字参数;否则,程序会把所传入的多个值都当成是传给 books 参数的。

Python 还可以收集关键字参数,此时 Python 会将这种关键字参数收集成字典。为了让 Python 能收集关键字参数,需要在参数前面添加两个星号。在这种情况下,一个函数可同时包含一个支持"普通"参数收集的参数和一个关键字参数收集的参数。

def test(x, y, z=3, *books, **scores):
    print(x, y, z)
    print(books)
    print(scores)

# 调用
test(1, 2, 3, "疯狂 iOS 讲义", "疯狂 Android 讲义", 语文=89, 数学=94)    

对于以上面方式定义的 test() 函数,参数 z 的默认值几乎不能发挥作用。

如果希望让 z 参数的默认值发挥作用,则需要只传入两个位置参数。

test(1, 2, 语文=89, 数学=94)

输出结果如下:

1 2 3
()
{'语文': 89, '数学': 94}

5.2.4.逆向参数收集

所谓逆向参数收集,指的是在程序已有列表、元组、字典等对象的前提下,把它们的元素 "拆开" 后传给函数的参数。

逆向参数收集需要在传入的列表、元组参数之前添加一个星号,在字典参数之前添加两个星号。

def test(name, message):
    print("用户是:", name)
    print("欢迎消息:", message)
my_list = ['孙悟空', '欢迎来疯狂软件']
test(*my_list)

实际上,即使是支持收集的参数,如果程序需要将一个元组传给该参数,那么同样需要使用逆向收集。

def foo(name, *nums):
    print("name 参数:", name)
    print("nums 参数:", nums)
my_tuple = (1, 2, 3)
foo('fkit', *my_tuple)  

字典也支持逆向收集,字典将会以关键字参数的形式传入。

def bar(book, price, desc):
    print(book, "这本书的价格是:", price)
    print('描述信息', desc)
my_dict = {'price':89, 'book':'疯狂 Python 讲义', 'desc':'这是一本系统全面的 Python 学习图书'}
bar(**my_dict)

5.2.5.函数的参数传递机制

5.2.6.变量作用域

Python 提供了如下三个工具函数来获取指定范围内的 "变量字典"

1.globals():该函数返回全局范围内所有变量组成的"变量字典"

2.locals():该函数返回当前局部范围内所有变量组成的"变量字典"

3.vars(object):获取在指定对象范围内所有变量组成的"变量字典"。如果不传入 object 参数,vars() 和 locals() 的作用完全相同。

globals() 和 locals() 看似完全不同,但它们实际上也是有联系的,关于这两个函数的区别和联系大致有以下两点。

1.locals() 总是获取当前局部范围内所有变量组成的 "变量字典",因此,如果在全局范围内(在函数之外)调用 locals() 函数,同样会获取全局范围内所有变量组成的 "变量字典";而 globals() 无论在哪里执行,总是获取全局范围内所有变量组成的 "变量字典"。

2.一般来说,使用 locals() 和 globals() 获取的 "变量字典" 只应该被访问,不应该被修改。但实际上,不管是使用 globals() 还是使用 locals() 获取的全局范围内的 "变量字典",都可以被修改,而这种修改会真正改变全局变量本身;但通过 locals() 获取的局部范围内的 "变量字典",即使对它修改也不会改变局部变量。

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

name = 'Charlie'
def test():
    print(name) # Charlie
test()
print(name) # Charlie


# 如果改成如下
name = 'Charlie'
def test():
    print(name) # 报错
    name = '孙悟空'
# 报错:UnboundLocalError: local variable 'name' referenced before assignment
test()
print(name)  

错误提示代码所访问的 name 变量还未定义,原因是由于程序 test() 函数中增加了 name = '孙悟空' 一行代码造成的。

Python 语法规定:在函数内部不存在的变量赋值时,默认就是重新定义新的局部变量。因为此这行代码相当于重新定义了 name 局部变量,这样 name 全局变量就被遮蔽了,所以上面程序代码会报错。

为了避免这个问题,可以通过两种方式来修改

1.访问被遮蔽的全局变量

name = 'Charlie'
def test():
    print(globals()['name']) # Charlie
    name = '孙悟空'
test()
print(name) # Charlie

2.在函数中声明全局变量

name = 'Charlie'
def test():
    global name
    print(name) # Charlie
    name = '孙悟空'
test()
print(name) # 孙悟空

5.3.局部函数

Python 还支持在函数体内定义函数,这种被放在函数体内定义的函数称为局部函数。

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))
print(get_math_func("cube", 3))
print(get_math_func("", 3))     

局部函数内的变量也会遮蔽它所在函数内的局部变量

def foo():
    name = 'Charlie'
    def bar():
        print(name) # Charlie
        name = '孙悟空'
    bar()

#提示错误
foo()     

该错误是由局部变量遮蔽变量导致的,在 bar() 函数中定义的 name 局部变量遮蔽了它所在 foo() 函数内的 name 局部变量,因此导致程序中粗体字代码报错。

为了声明 bar() 函数中的 "name='孙悟空'" 赋值语句不是定义新的局部变量,只是访问它所在 foo() 函数内的 name 局部变量,Python 提供了 nonlocal 关键字,通过 nonlocal 语句即可声明访问赋值语句只是访问该函数所在函数内的局部变量。

def foo():
    name = 'Charlie'
    def bar():
        nonlocal name
        print(name) # Charlie
        name = '孙悟空'
    bar()
foo()  

5.4.函数的高级内容

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

5.4.1.使用函数变量

5.4.2.使用函数作为函数形参

def map(data, fn):
    result = []
    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)
print("计算机组元素的平方")
print(map(data, square))
print("计算机组元素的立方")
print(map(data, cube))
print("计算机组元素的阶乘")
print(map(data, factorial))
print(type(map))

5.4.3.使用函数作为返回值

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
    if type == "cube":
        return cube
    else:
        return factorial      

#调用
math_func = get_math_func("cube")
print(math_func(5))
math_func = get_math_func("square")
print(math_func(5))
math_func = get_math_func("other")
print(math_func(5))

5.5.局部函数与 lambda 表达式

5.5.1.回顾局部函数  

5.5.2.使用 lambda 表达式代替局部函数

def get_math_func(type):
    result = 1
    if type == "square":
        return lambda n:n*n
    elif type == "cube":
        return lambda n:n*n*n
    else:
        return lambda n:(1+n)*n/2

#调用
math_func = get_math_func("cube")
print(math_func(5))
math_func = get_math_func("square")
print(math_func(5))
math_func = get_math_func("other")
print(math_func(5))

注意:lambda 表达式只能是单行表表达式,不允许使用更复杂的函数形式

lambda 表达式的语法格式

lambda [parameter_list]:表达式 

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

1.lambda 表达式必须使用 lambda 关键字定义

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

实际上,lambda 表达式的本质就是匿名的、单行函数体的函数。因此,lambda 表达式可以写成函数的形式。

lambda x, y:x + y

可改写为如下函数:

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

lambda 表达式依然有如下两个用途:

1.对于单行函数,使用 lambda 表达式可以省去定义函数的过程,让代码更加简洁

2.对于不需要多次复用的函数,使用 lambda 表达式可以在用完之后立即释放,提高了性能

x = map(lambda x:x*x, range(8))
print([e for e in x])
y = map(lambda x:x*x if x % 2 == 0 else 0, range(8))
print([e for e in y])

5.6.本章小结

  

 

posted on 2020-01-27 14:05  herisson_pan  阅读(17)  评论(0)    收藏  举报

导航