社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

Python基础语法全体系 | 函数基础、深入理解函数式编程(Lambda、@装饰器与偏函数)

ZY-JIMMY • 4 年前 • 109 次点击  

《Python基础语法全体系》系列博文第五篇,本篇博文将详细深入地讲解Python的函数部分,包括函数基础部分:函数入门、函数参数和局部函数;之后讲解函数的高级内容;最后讲解Python的函数式编程,包括高阶函数、lambda、装饰器与偏函数四大部分。函数部分是Python基础中最为重要的部分,也是实现代码复用的基础。本文整理自疯狂python编程、廖雪峰的Python教程。



函数是执行特定任务的一段代码,程序通过将一段代码定义成函数,并为该函数指定一个函数名,这样既可在需要的时候调用这段代码。因此,函数是代码复用的重要手段。


函数基础

定义函数和调用函数

在使用函数之前必须先定义函数,语法格式如下:

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

下面是程序示例:

# 定义一个函数,声明2个形参
def my_max(x, y) :
	# 定义一个变量z,该变量等于x、y中较大的值
	z = x if x > y else y
	# 返回变量z的值
	return z

# 定义一个函数,声明一个形参
def say_hi(name) :
	print("===正在执行say_hi()函数===")
	return name + ",您好!"

a = 6
b = 9
# 调用my_max()函数,将函数返回值赋值给result变量
result = my_max(a , b) # 9
print("result:", result)
# 调用say_hi()函数,直接输出函数的返回值
print(say_hi("ZYZMZM"))

# 运行结果
result: 9
===正在执行say_hi()函数===
ZYZMZM,您好!
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

在函数体中使用return语句可以显式地返回一个值, return语句返回的值即可是有值的变量,也可是一个表达式

例如上述程序中的my_max()函数,实际上可简写为如下形式:

def my_max(x, y):
	return x if x > y else y
  • 1
  • 2

多个返回值

如果程序需要有多个返回值,则既可将多个值包装成列表之后返回,也可直接返回多个值。 如果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) # (76.9, 12.816666666666668)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

此外, 可以使用Python提供的序列解包功能,直接使用多个变量接收函数返回的多个值

# 使用序列解包来获取多个返回值
s, avg = sum_and_avg(my_list)
print(s) # 76.9
print(avg) # 12.816666666666668
  • 1
  • 2
  • 3
  • 4

函数参数

关键字参数

Python函数的参数名不是无意义的, 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))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

如果希望在调用函数时混合使用关键字参数和位置参数,则关键字参数必须位于位置参数之后。

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

参数默认值

在某些情况下,程序需要在定义函数时为一个或多个形参指定默认值——这样在调用函数时就可以省略为该形参传入参数值,而是直接使用该形参的默认值。

为形参指定默认值的语法格式: 形参名 = 默认值

# 为两个参数指定默认值
def say_hi(name = "ZYZMZM", message = "欢迎来到CSDN"):
	print(name, ", 您好")


    

	print("消息是:", message)
	
# 全部使用默认参数
say_hi()

# 只有message参数使用默认值
say_hi("ZY")

# 两个参数都不使用默认值
say_hi("JIMMY", "欢迎学习Python")

# 只有name参数使用默认值
say_hi(message = "欢迎学习Python")

# 运行结果
ZYZMZM , 您好
消息是: 欢迎来到CSDN
ZY , 您好
消息是: 欢迎来到CSDN
JIMMY , 您好
消息是: 欢迎学习Python
ZYZMZM , 您好
消息是: 欢迎学习Python
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

注意,Python规定: 关键字参数必须位于位置参数的后面 ,因此下面的调用是错误的。

# 错误用法
say_hi(name = "ZY", "欢迎学习Python")
  • 1
  • 2

由于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 = '*')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Python要求将带默认值的参数定义在形参列表的最后。


参数收集(个数可变参数)

Python允许在 形参前面添加一个星号(*) ,这样就意味着该参数可以接收多个参数值, 多个参数值被当成元组传入

# 定义了支持参数收集的函数
def test(a, *blogs) :
    print(blogs) # ('ZYZMZM-blog', 'JIMMY-blog')
    # books被当成元组处理
    for b in blogs :
        print(b)
    # 输出整数变量a的值
    print(a) # 5
# 调用test()函数
test(5 , "ZYZMZM-blog" , "JIMMY-blog")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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




    
# 定义了支持参数收集的函数
def test(*blogs ,num) :
    print(blogs) # ('ZYZMZM-blog', 'JIMMY-blog')
    # books被当成元组处理
    for b in blogs :
        print(b)
    print(num) # 20
# 调用test()函数
test("ZYZMZM-blog" , "JIMMY-blog", num = 20)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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

# 定义了支持参数收集的函数
def test(x, y, z=3, *blogs, **scores) :
    print(x, y, z)
    print(blogs)
    print(scores)
test(1, 2, "ZYZMZM-blogs" , "JIMMY-blogs", 语文=89, 数学=94)

# 运行结果
1 2 3
('ZYZMZM-blogs', 'JIMMY-blogs')
{'语文': 89, '数学': 94}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

逆向参数收集

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

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

def test(name, message):
    print("用户是: ", name)
    print("欢迎消息: ", message)
my_list = ['ZYZMZM', '欢迎来到CSDN']
test(*my_list)

# 运行结果
用户是:  ZYZMZM
欢迎消息:  欢迎来到CSDN
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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

def foo(name, *nums):
    print("name参数: ", name)
    print("nums参数: ", nums)
my_tuple = (1, 2, 3)
# 使用逆向收集,将my_tuple元组的元素传给nums参数
foo('blog', *my_tuple)

# 运行结果
name参数:  blog
nums参数:  (1, 2, 3)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

也可以使用如下方法调用foo()函数:




    
# 使用逆向收集,将my_tuple元组的第一个元素
# 传给name参数,剩下参数传给nums参数
foo(*my_tuple)

# 运行结果
name参数:  1
nums参数:  (2, 3)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

如果不使用逆向收集,整个元组将会作为一个参数,而不是将元祖的元素作为多个参数。

# 不使用逆向收集,my_tuple元组整体传给name参数
foo(my_tuple)

# 运行结果
name参数:  (1, 2, 3)
nums参数:  ()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

def bar(name, nums, desc):
    print(name, " 博客的文章数量是: ", nums)
    print('描述信息', desc)
my_dict = {'nums': 220, 'name': 'ZYZMZM-blog', 'desc': '这是一个技术博客'}
# 按逆向收集的方式将my_dict的多个key-value传给bar()函数
bar(**my_dict)

# 运行结果
ZYZMZM-blog  博客的文章数量是:  220
描述信息 这是一个技术博客
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

变量作用域

在程序中定义一个变量时,这个变量是有作用范围的,变量的作用范围被称为它的作用域。根据定义变量的位置,变量分为两种:

  • 局部变量 。在函数中定义的变量,包括参数,都被称为局部变量。
  • 全局变量 。在函数外面、全局范围内定义的变量,都被称为全局变量。

实际上,Python提供了如下三个工具函数来获取指定范围内的“变量字典”。

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

注意以下两点:

  • 如果在全局范围内调用locals()函数,同样会获取全局范围内所有变量组成的“变量字典”;而globals()无论在哪里执行,总是获取全局范围内所有变量组成的“变量字典”。
  • 不管是使用globals()还是使用locals()获取的全局范围内的“变量字典”,都可以被修改,而 这种修改会真正改变全局变量本身 ;但通过locals()获取的局部范围内的“变量字典”,即使对它修改也不会影响局部变量。
def test ():
    age = 20
    # 直接访问age局部变量
    print(age) # 输出20
    
    # 访问函数局部范围的“变量数组”
    print(locals()) # {'age': 20}
    
    # 通过函数局部范围的“变量数组”访问age变量
    print(locals()['age']) # 20
    
    # 通过locals函数局部范围的“变量数组”改变age变量的值
    locals()['age'] = 12
    
    # 再次访问age变量的值
    print('xxx', age) # 依然输出20
    
    # 通过globals函数修改x全局变量
    globals()['x'] = 19
    
x = 5
y = 20
print(globals()) # {..., 'x': 5, 'y': 20}

# 在全局访问内使用locals函数,访问的是全局变量的“变量数组”
print(locals()) # {..., 'x': 5, 'y': 20}

# 直接访问x全局变量
print(x) # 5

# 通过全局变量的“变量数组”访问x全局变量
print(globals()


    
['x']) # 5

# 通过全局变量的“变量数组”对x全局变量赋值
globals()['x'] = 39
print(x) # 输出39

# 在全局范围内使用locals函数对x全局变量赋值
locals()['x'] = 99
print(x) # 输出99
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

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

name = 'JIMMY'
def test ():
    # 直接访问name全局变量
    print(name) # JIMMY
    name = 'ZYZMZM'
test()
print(name) # JIMMY
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

上述程序会出现错误,原因是Python语法规定, 在函数内部对不存在的变量赋值时,默认就是重新定义新的局部变量

我们可以通过两种方式来修改上面的程序。

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

name = 'JIMMY'
def test ():
    # 通过globals()函数访问name全局变量
    print(globals()['name'])  # JIMMY
    name = 'ZYZMZM'
test()
print(name)  # JIMMY
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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

name = 'JIMMY'
def test ():
    # 声明name是全局变量,后面的赋值语句不会重新定义局部变量
    global name
    # 直接访问name全局变量
    print(name)  # JIMMY
    name = 'ZYZMZM'
test()
print(name)  # ZYZMZM
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

增加了 global name 声明之后,程序会把name变量当成全局变量,这意味着test()函数里面对name赋值的语句是对全局变量的赋值,而不是重新定义局部变量。


局部函数

前面所看到的函数都是在全局范围内定义的,它们都是 全局函数 ,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)) # 输出9
print(get_math_func("cube", 3)) # 输出27
print(get_math_func("", 3)) # 输出6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

如上面程序所示,如果封闭函数没有返回局部函数,那么局部函数只能在封闭函数内部使用。但是如果封闭函数将局部函数返回,且程序使用变量保存了封闭函数的返回值,那么这些局部函数的作用域就会被扩大,我们之后将会讲解。

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

def foo ():
    # 局部变量name
    name = 'ZYZMZM'
    def bar ():
        # 访问bar函数所在的foo函数的name局部变量
        print(name) # ERROR
        name = 'JIMMY'
    bar()
foo()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

该错误是由 局部变量遮蔽局部变量 导致的,在bar()函数中定义的name局部变量遮蔽了它所在foo()函数内的name局部变量,因此导致 print(name) 语句报错。

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

def foo ():
    # 局部变量name
    name = 'ZYZMZM'
    def bar ():
        nonlocal name
        # 访问bar函数所在的foo函数的name局部变量
        print(name) # ZYZMZM
        name = 'JIMMY'
    bar()
    print(name) # JIMMY
foo()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

nonlocal 和之前介绍的global功能大致相似,区别只是 global用于声明访问全局变量 ,而 nonlocal用于声明访问当前函数所在函数内的局部变量


函数的高级内容

使用函数变量

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

通过对my_fun变量赋值不同的函数,可以让my_fun在不同的时间指向不同的函数,从而让程序更加灵活。


使用函数作为函数形参

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)) # [9, 16, 81, 25, 64]
print("计算数组元素的立方")
print(map(data , cube)) # [27, 64, 729, 125, 512]
print("计算数组元素的阶乘")
print(map(data , factorial)) # [6, 24, 362880, 120, 40320]
# 获取map的类型
print(type(map)) # <class 'function'>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

使用函数作为返回值

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
    if 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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

函数式编程

函数式编程就是一种 抽象程度很高的编程范式 ,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

函数式编程的一个特点就是, 允许把函数本身作为参数传入另一个函数,还允许返回一个函数 !我们之前在函数的高级内容中已经讲过。Python对函数式编程提供部分支持。由于Python允许使用变量,因此, Python不是纯函数式编程语言


高阶函数

高阶函数,就是让 函数的参数能够接收别的函数 。本部分我们主要介绍Python的内置函数。

接下来我们讲解map()和reduce()函数。首先来看map, map()函数 接收两个参数:一个是函数、一个是可迭代对象Iterable。 map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回

例如,我们有一个函数 f ( x ) = x 2 ,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map()实现如下:

def f(x):
    return x * x
r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
print(list(r)) # [1, 4, 9, 16, 25, 36, 49, 64, 81]
  • 1
  • 2
  • 3
  • 4

map()传入的第一个参数是f,即 函数对象本身 。由于结果r是一个Iterator,Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。

map()作为高阶函数,事实上 它把运算规则抽象了 ,因此,我们不但可以计算简单的 f ( x ) = x 2 ,还可以计算任意复杂的函数,比如,把这个list所有数字转为字符串,只需要一行代码:

print(list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])))
# ['1', '2', '3', '4', '5', '6', '7', '8', '9']
  • 1
  • 2

接下来了解一下 reduce() 的用法。reduce把一个函数作用在一个序列 [ x 1 , x 2 , x 3 , . . . ] [x1,x2,x3,...][x1, x2, x3, ...] 上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
r e d u c e ( f , [ x 1 , x 2 , x 3 , x 4 ] ) = f ( f ( f ( x 1 , x 2 ) , x 3 ) , x 4 )

比方说把序列[1, 3, 5, 7, 9]变换成整数13579,就可以用reduce实现:

from functools import reduce
def change(x, y):
    return x * 10 + y

print(reduce(change, [1,3,5,7,9])) # 13579
  • 1
  • 2
  • 3
  • 4
  • 5

str也是一个序列,对上面的例子稍加改动,配合map(),我们就可以写出 把str转换为int 的函数:

from functools import reduce
def change(x, y):
    return x * 10 + y

def digit(x):
    digits = {'0':0, '1':1, '2':2, '3':3, '4':4, \
              '5':5, '6':6, '7':7, '8':8, '9':9}
    return digits[x]

print(reduce(change, map(digit, '13579'))) # 13579
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

整理成一个stoi的函数就是:

from functools import reduce

digits = {'0': 0, '1': 1, '2': 2, '3': 


    
3, '4': 4, \
          '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def stoi(s):
    def change(x, y):
        return x * 10 + y
    def digit(x):
        return digits[x]
    return reduce(change, map(digit, s))

num = stoi('13579')
print(num, end=" ")
print(type(num)) # 13579 <class 'int'>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

还可以使用lambda函数进一步简化成:

def stoi(s):
    def digit(x):
        return digits[x]
    return reduce(lambda x,y:x*10+y, map(digit, s))
  • 1
  • 2
  • 3
  • 4

接下来我们介绍 filter()函数 ,它的作用是 用于过滤序列

和map()类似,filter()也接收一个函数和一个序列。和map()不同的是, filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素

下面程序是在一个过滤掉一个list中所有的偶数:

def odd(x):
    return x % 2 == 1

print(list(filter(odd, [1,2,3,4,5,6,7,8,9])))
# [1, 3, 5, 7, 9]
  • 1
  • 2
  • 3
  • 4
  • 5

下面程序删掉一个序列中的所有空字符串:

def not_empty(s):
    return s and s.strip()

print(list(filter(not_empty, ['A', '', 'B', None, 'C', '  '])))
# ['A', 'B', 'C']
  • 1
  • 2
  • 3
  • 4
  • 5

filter()函数返回的是一个Iterator ,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list。

接下来我们使用filter实现筛选法求素数:

def _odd_iter():
    n = 1
    while True:
        n += 2
        yield n

def choose(n):
    return lambda x:x % n > 0

def prime():
    yield 2
    it = _odd_iter()
    while True:
        n = next(it)
        yield n
        it = filter(choose(n), it)

for n in prime():
    if n < 100:
        print(n, end=" ")
    else:
        break
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

上述程序中我们使用到了 生成器 的概念,在Python中, 一边循环一边计算 的机制,称为生成器:generator。

我们之前讲过,要创建一个generator,有很多种方法。最简单的是 把一个列表生成式的[]改成() ,就创建了一个generator。我们需要通过 next()函数 获得generator的下一个返回值。

如果一个函数定义中包含 yield关键字 ,那么这个函数就不再是一个普通函数,而是一个generator。

最难理解的就是generator和函数的执行流程不一样:函数是顺序执行,遇到return语句或者最后一行函数语句就返回。 而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行


Lambda表达式

当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入 匿名函数 更方便。

在Python中,对匿名函数提供了有限支持。还是以map()函数为例,计算 f ( x ) = x 2 时,除了定义一个 f ( x ) 的函数外,还可以直接传入匿名函数:

print(list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])))
# [1, 4, 9, 16, 25, 36, 49, 64, 81]
  • 1
  • 2

通过对比可以看出,匿名函数 lambda x: x * x 实际上就是:

def f(x):
    return x * x
  • 1
  • 2

关键字lambda表示匿名函数,冒号前面的x表示函数参数

匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

f = lambda x: x * x
print(f(5)) # 25
  • 1
  • 2

同样,也可以 把匿名函数作为返回值返回 ,比如:

def build(x, y):
    return lambda: x * x + y * y
  • 1
  • 2

函数装饰器

装饰器(Decorators)是 Python 的一个重要部分。简单地说: 它们是修改其他函数的功能的函数

使用@符号引用已有的函数后,可用于修饰其他函数,装饰被修饰的函数。当程序使用“@函数”装饰另一个函数时,实际上完成如下两步:

  • 将被修饰的函数作为参数传给@符号引用的函数
  • 将被修饰的函数装饰(替换)成第一步的返回值

被“@函数”修饰的函数不再是原来的函数,而是被替换成一个新的东西。

def funA(fn):
    print('A')
    fn() # 执行传入的fn参数
    return 'blog'
'''
下面装饰效果相当于:funA(funB),
funB将会替换(装饰)成该语句的返回值;
由于funA()函数返回blog,因此funB就是blog
'''
@funA
def funB():
    print('B')
print(funB) # blog
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

上述程序使用@funA修饰funB,这意味着程序要完成两步操作。

  • 将funB作为funA()的参数,也就是执行 funA(funB)
  • 将funB替换成第一步执行的结果,由于funA()执行完毕后返回blog,因此funB就不再是函数,而是被替换成一个字符串。

被修饰的函数总是被替换成@符号引用的函数的返回值,因此被修饰的函数会变成什么样,完全是由于@符号所引用的函数的返回值所决定的,如果@符号所引用的函数的返回值是函数,那么被修饰的函数在替换之后还是函数。

def foo(fn):
    # 定义一个嵌套函数
    def bar(*args):
        print("===1===", args)
        n = args[0]
        print("===2===", n * (n - 1))
        # 查看传给foo函数的fn函数
        print(fn.__name__)
        fn(n * (n - 1))
        print(


    
"*" * 15)
        return fn(n * (n - 1))
    return bar
'''
下面装饰效果相当于:foo(my_test),
my_test将会替换(装饰)成该语句的返回值;
由于foo()函数返回bar函数,因此my_test就是bar
'''
@foo
def my_test(a):
    print("==my_test函数==", a)
# 打印my_test函数,将看到实际上是bar函数
print(my_test) # <function foo.<locals>.bar at 0x00000000021FABF8>
# 下面代码看上去是调用my_test(),其实是调用bar()函数
my_test(10)
my_test(6, 5)

# 运行结果
<function foo.<locals>.bar at 0x000001857EA52430>
===1=== (10,)
===2=== 90
my_test
==my_test函数== 90
***************
==my_test函数== 90
===1=== (6, 5)
===2=== 30
my_test
==my_test函数== 30
***************
==my_test函数== 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

上述程序定义了一个装饰器函数foo,该函数执行完成后并不是返回普通值,而是返回bar函数(这是关键),这意味着 被该@foo修饰的函数最终都会被替换成bar函数

通过@符号来修饰函数是Python的一个非常实用的功能, 它既可以在被修饰函数的前面添加一些额外的处理逻辑(比如权限检查),也可以在被修饰函数的后面添加一些额外的处理逻辑(比如记录日志),还可以在目标方法抛出异常时进行一些修复操作

上面介绍的这种在被修饰函数之前、之后、抛出异常后增加某种处理逻辑的方式,就是其他编程语言中的AOP。

下面示例了如何通过函数装饰器为函数添加权限检查的功能。

def auth(fn):
    def auth_fn(*args):
        # 用一条语句模拟执行权限检查
        print("----模拟执行权限检查----")
        # 回调要装饰的目标函数
        fn(*args)
    return auth_fn
@auth
def test(a, b):
    print("执行test函数,参数a: %s, 参数b: %s" % (a, b))
# 调用test()函数,其实是调用装饰后返回的auth_fn函数
test(20, 15)

# 运行结果
----模拟执行权限检查----
执行test函数,参数a: 20, 参数b: 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

上面程序使用@auth修饰了test()函数,这会使得test()函数被替换成auth()函数所返回的auth_fn函数,而auth_fn函数的执行流程是:① 先执行权限检查;②回调被修饰的目标函数。简单来说,auth_fn函数就是为被修饰函数添加了一个权限检查的功能。


偏函数 partial function

Python的 f u n c t o o l s 模块提供了很多有用的功能,其中一个就是 偏函数(Partial function) 。。

偏函数,是一种高级的函数形式。简单来说, 偏函数其实就是没有定义好明确的输入参数的函数

functools.partial(func, *args,**keywords):该函数用于为func函数的部分参数指定参数从而得到一个转换后的函数,程序以后调用转换后的函数时,就可以少传入那些已指定的参数。

在介绍函数参数的时候,我们讲到,通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点。举例如下:

int()函数 可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换,如果传入base参数,就可以做N进制的转换::

print(int('12345')) # 12345
print(int('12345', base=16)) # 74565
  • 1
  • 2

假设要转换大量的二进制字符串,我们可以定义一个int2()的函数,默认把base=2传进去:

def int2(x, base=2):
    return int(x, base)
  • 1
  • 2

f u n c t o o l s . p a r t i a l 就是帮助我们创建一个偏函数的,我们可以直接使用下面的代码创建一个新的函数int2():

import functools
int2 = functools.partial(int, base = 2)
print(int2('1000')) # 8
print(int2('1011')) # 11
  • 1
  • 2
  • 3
  • 4

简单总结 f u n c t o o l s . p a r t i a l 的作用就是: 把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单

注意到上面的新的int2函数,仅仅是把base参数重新设定默认值为2,但也可以在函数调用时传入其他值:

print(int2('1a2b', base = 16)) # 6699
  • 1

最后,创建偏函数时,实际上可以接收 函数对象 *args **kw 这3个参数,当传入:
int2 = functools.partial(int, base=2)
实际上固定了int()函数的关键字参数base,也就是:

int2('10010')
相当于:
kw = { 'base': 2 }
int('10010', **kw)
  • 1
  • 2
  • 3
  • 4

当传入: max2 = functools.partial(max, 10) ,实际上会把10作为*args的一部分自动加到左边,也就是:

max2(5, 6, 7)
相当于:
args = (10, 5, 6, 7)
max(*args) # 10
  • 1
  • 2
  • 3
  • 4

当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/52006
 
109 次点击