Bootstrap

【新人系列】Python 入门(十三):函数进阶

✍ 个人博客:https://blog.csdn.net/Newin2020?type=blog
📝 专栏地址:https://blog.csdn.net/newin2020/category_12801353.html
📣 专栏定位:为 0 基础刚入门 Python 的小伙伴提供详细的讲解,也欢迎大佬们一起交流~
📚 专栏简介:在这个专栏,我将带着大家从 0 开始入门 Python 的学习。在这个 Python 的新人系列专栏下,将会总结 Python 入门基础的一些知识点,方便大家快速入门学习~
❤️ 如果有收获的话,欢迎点赞 👍 收藏 📁 关注,您的支持就是我创作的最大动力 💪

1. 匿名函数

在定义函数的时候,不想给函数起一个名字,这个时候就可以用 lambda 来定义一个匿名函数,lambda 表达式,又称匿名函数。

语法:

  • 参数:可选,通常以逗号分隔的变量表达式形式,也就是位置参数
  • 表达式:不能包含循环、return,可以包含 if…else…
变量名 = lambda 参数: 表达式

可以理解 lambda 表达式,就是简单函数(函数体仅是单行的表达式)的简写版本。相比函数,lamba 表达式具有以下 2 个优势:

  • 对于单行函数,使用 lambda 表达式可以省去定义函数的过程,让代码更加简洁。
  • 对于不需要多次复用的函数,使用 lambda 表达可以在用完之后立即释放,提高程序执行的性能。
# 示例 1:使用函数,不带参数
def func():
    return 1 == 2

result = func()
print(result)    # False

# 使用匿名函数
result = lambda: 1 == 2
print(result)       # <function <lambda> at 0x10444cea0>
print(result())     # False


# 实例 2:使用函数,带有参数
def func_a(x, y, z):
    return x + y + z

result = func_a(1, 2, 3)
print(result)      # 6

# 使用匿名函数
a = lambda x, y, z: x + y + z
print(a(1, 2, 3))       # 6


# 示例 3:使用函数,含有if...else语法
def func(x, y):
    if x > y:
        return x
    else:
        return y

result = func(2, 6)
print(result)       # 6

# 使用匿名函数
func = lambda x, y: x if x > y else y
print(func(2, 6))       # 6

2. 高阶函数

函数式编程

函数式编程将一个问题分解成一系列函数,通过把大段代码拆成函数,通过一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。
函数式编程还具有一个特点:允许把函数本身作为参数传入另一个函数,还允许返回一个函数。

高阶函数

在 Python 中,高阶函数是指可以接受一个或多个函数作为参数,或者能够返回一个函数的函数。比如 map( )、filter( ) 和 reduce( ) 这些内置函数就是常见的高阶函数。

2.1 内置高阶函数

map( ) 函数

map( ) 函数接受一个函数和一个可迭代对象作为参数,然后将这个函数应用到可迭代对象的每个元素上,并返回一个新的可迭代对象。

下面这个例子就可以看出方法二中使用了 map( ) 函数之后,操作就方便简洁了许多,不用再自己去手动遍历列表。

# 对传入参数的值进行翻倍
def double_num(x):
    return x * 2
    
numbers = [1, 2, 3, 4, 5]
number_new = []

# 方法一
for i in numbers:
    number_new.append(double_num(i))
print(number_new)   # [2, 4, 6, 8, 10]

# 方法二
print(list(map(double_num, numbers)))   # [2, 4, 6, 8, 10]

filter( ) 函数

filter( ) 函数的基本语法格式:

filter(function, iterable)

funcition 参数表示要传入一个函数,iterable表示一个可选代对象。

filter( ) 函数的功能是对 iterable 中的每个元素,都使用 function 函数判断,并返回 True 或者 False,最后将返回 True 的元素组成一个新的可遍历的集合。

例如,我想过滤一个字符串列表,筛选出以 “A” 开头的字符串,则可以先自定义一个判断规则,然后传入 filter 函数并过滤指定的列表。

# 打印列表中以"A"开头的名字
def first_name(x):
    if x.startswith("A"):
        return True
    else:
        return False
name_list = ["Alex", "Hana", "Anny", "Sunny"]
f = filter(first_name, name_list)
print(list(f))    # ['Alex', 'Anny']

2.2 functools 模块

各个函数应用场景:

  • 偏函数:传入的参数存在大量重复的值
  • cmp_to_key:需要自定义排序规则的场景
  • singledispatch:判断分支比较多的场景
  • wraps:需要用到装饰器的场景
  • lru_cache:涉及到大量计算的场景

偏函数

在 Python 中,偏函数(Partial Function)是通过 functools 模块的 partial 函数创建。偏函数允许固定一个函数的某些参数,从而创建一个新的函数,这个新函数只需要接收剩余未固定的参数。

偏函数可以使代码更简洁、更具可读性,并且在某些情况下可以避免重复编写相似的函数调用代码。

格式:int1 = functools.partial(参数1, 参数2)

  • 参数 1:需要转变的函数名
  • 参数 2:需要设置的默认参数

举个例子,我在某个场景中会频繁的调用下面的 add 函数,并且传入的参数中总是有一个为 10,那么就可以创建一个偏函数 add_new1 将 10 这个参数固定下来。之后每次调用 add_new1 就只用传递两个参数,10 这个参数会自动帮我们传入。

import functools

def add(a, b, c):
    print(a, b, c)
    return a + b + c

add_new1 = functools.partial(add, 10)
result = add_new1(2, 3)  # 10 2 3
print(result)   # 15

add_new2 = functools.partial(add, 10, 2)
result = add_new2(4)  # 10 2 4
print(result)   # 16

cmp_to_key

在 python 中我们可以使用 sorted 方法对指定列表进行排序,如果传入的是字符串列表,则会按照 ASCII 码值进行排序,但如果我有其它需求例如按照字符数量大小进行排序,则这种方法就行不通了。

numbers = [1, 200, 33, 4, 50, 10]
print(sorted(numbers))

str_list = ['apple', 'banana', 'test', 'python']
print(sorted(str_list))  # ['apple', 'banana', 'python', 'test']

而 functools 中的 cmp_to_key 函数就可以做到这点,我们只需先定义好一个排序规则。然后通过下面这种传参的方式传入 sorted 函数中即可,这相当于指定了自定义的排序规则。

import functools

# 按照字符数量进行排序
def compare_length(a, b):
    if len(a) < len(b):
        return -1
    elif len(a) > len(b):
        return 1
    else:
        return 0
print(sorted(str_list, key=functools.cmp_to_key(compare_length)))   # ['test', 'apple', 'banana', 'python']

singledispatch

singledispatch 是 Python 3.4 引入的标准库,它提供了一种将函数转换为多态函数的方法,可以根据函数的第一个参数类型的不同来调用不同的实现。

具体来说,使用 @singledispatch 装饰器可以定义一个函数,并针对第一个参数的类型定义不同的实现。当函数被调用时,Python 会自动查找对应类型的实现来执行,如果找不到对应类型的实现,则会调用默认实现。

举个例子,下面定义了一个名为 func 的函数,并使用 @singledispatch 装饰器将其转换为多态函数。然后,使用 @func.register 装饰器针对不同的参数类型分别定义了不同的实现。当调用 func 函数时,Python 会自动根据传入参数的类型来选择对应的实现。

from functools import singledispatch

@singledispatch
def func(data):
    print("default implementation")

@func.register(int)
def func_int(data):
    print(f"arg: {data} is an integer")

@func.register(str)
def func_string(data):
    print(f"arg: {data} is a string")

func(1)          # arg: 1  is an integer
func("hello")    # arg: hello  is a string
func([1, 2])     # default implementation

我们通过这种方式可以使得我们的代码更清晰更具有结构化,而不是通过写一堆的 if else 语句来进行分支的判断。

wraps

wraps 用于在装饰器中保留被装饰函数的元数据(如函数名和文档字符串),以避免装饰器对原始函数的影响。
我们来看看不加 wraps 的结果是什么,可以发现原始函数名称被改变了。

def add_exclamation(func):
    # @wraps(func)
    def wrapper(name):
        return func(name) + '!'
    return wrapper

@add_exclamation
def greet(name):
    return 'hello ' + name

print(greet.__name__)  # 输出:wrapper

下面我们在装饰器中使用 @wraps(func),它就会用 func 的元数据覆盖了 wrapper 函数的元数据,这样就可以保留原始函数的元数据,不会因为装饰器的存在而改变原始函数的名字和文档字符串。

from functools import wraps

def add_exclamation(func):
    @wraps(func)
    def wrapper(name):
        return func(name) + '!'
    return wrapper

@add_exclamation
def greet(name):
    return 'hello ' + name

print(greet.__name__)  # 输出:greet

lru_cache

lru_cache 用于实现缓存功能,即 “Least Recently Used”(最近最少使用)缓存策略。

当使用 lru_cache 装饰一个函数时,它会自动缓存该函数的调用结果。对于相同的参数组合,如果函数已经被调用过,那么下次调用时会直接返回缓存的结果,而无需重新计算,从而提高函数的执行效率。

举个例子,下面 fibonacci 函数计算斐波那契数列,如果没有 lru_cache 装饰器,对于每个 n 的值都会重新计算。有了 lru_cache 装饰器后,已经计算过的结果会被缓存起来,下次相同参数调用时直接返回缓存结果。

from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
    if n <= 1:
        return n
    else:
        return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(10))

lru_cache 函数接受一个可选的参数 maxsize ,用于指定缓存的最大大小。如果 maxsize 设为 None ,则缓存大小没有限制,默认值通常是 128。当缓存达到最大容量时,最近最少使用的缓存结果会被删除,为新的结果腾出空间。

2.3 collections 模块

统计对象个数

在需要统计对象个数的场景下,我们不用自己通过很多代码去实现统计方法,而是可以直接调用 collections 模块中的 Counter 函数,直接通过一行代码就能获取对象的个数。

from collections import Counter

list = [1, 2, 3, 2, 1, 3, 5, 2]
str1 = "hello world"

# 统计每个元素出现的次数
print(Counter(list))    # Counter({2: 3, 1: 2, 3: 2, 5: 1})
print(Counter(str1))    # Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})

# 统计出现次数最多的前x个元素
print(Counter(list).most_common(3))  # [(2, 3), (1, 2), (3, 2)]
print(Counter(str1).most_common(2))  # [('l', 3), ('o', 2)]

合并多个字典

在需要处理多个字典的场景下,可以通过 collections 中的 ChainMap 函数先将多个字典合并成一个字典,然后就能很方便的进行后续处理操作。

from collections import ChainMap

dict1 = {'python': 100}
dict2 = {'java': 90}
dict3 = ChainMap(dict1, dict2)  # 合并dict1和dcit2为一个字典
for key, value in dict3.items():
    print(key, value)
;