跳转至

函数进阶

add_circle2025-03-03update2025-03-03

1. 高阶函数

高阶函数其实就是一个函数作为参数传递到另一个函数中进行调用的一种特殊嵌套函数。

1.1 高阶函数的声明

一个高阶函数应该具备下面至少一个特点:

  • 将函数作为参数。
  • 讲函数作为结果。
# 普通函数
def add(x,y):
    return x+y

# 高阶函数
def calc(x,y,o):
    return o(x,y)

ret = calc(10,20,add)
print(ret)

计算一个函数功能的耗时时间。

import time

def fn1():
    print("fn1功能")
    time.sleep(2)

def fn2():
    print("fn2功能")
    time.sleep(3)

# 高阶函数
def timer(func):
    start = time.time()
    func()
    end = time.time()
    print("时耗",end-start)

timer(fn1)
timer(fn2)
"""
fn1功能
时耗 2.0106124877929688
fn2功能
时耗 3.001829147338867
"""

2. 闭包函数

闭包(Closure),也叫函数闭包(function closures),是内函数引用了外函数的局部变量的嵌套函数

2.1 闭包声明

def outer():
    x = 0  # 外函数中声明的的变量,可以有一个到多个,也可以没有。
    def inner():
        nonlocal x # 引用外函数的变量,形成闭包
        x += 1
        print(f"x=")
    # 不一定要把内函数返回,也可以直接在外函数中调用内函数
    # 当然,如果把内函数作为返回值,则该函数既属于高阶函数,也属于闭包函数。
    return inner

func = outer()
func()  # inner函数!x=1
func()  # inner函数!x=2
func()  # inner函数!x=3
func()  # inner函数!x=4

闭包函数声明和执行过程中,外函数作用域下的局部变量不会因为闭包函数执行结束而被内存回收删除,而是与内部函数进行引用绑定,导致该变量会继续保留在内存中,并且闭包函数还可以在其定义环境外被执行,并保持与该变量的关系。

2.2 闭包的特点

  1. 必须是一个嵌套函数。
  2. 外函数和内函数都可以拥有属于自己的参数。
  3. 内函数必须引用外函数作用域下声明的局部变量。

2.3 闭包的属性

# __closure__ 获取闭包函数使用过的外函数作用域下的局部变量,如果值是None则表示当前函数不是闭包
# cell_contents 是 单元cell的一个属性, 通过他可以获取外函数作用域下的局部变量的内容
def foo():
    x = 0  # 外函数作用域下的局部变量,可以有一个到多个,也可以没有。
    y = 10
    def inner():  # 这就是闭包
        nonlocal x, y
        x += 1
        y -= 1
        print(f"inner函数!x={x}")
    return inner

func = outer()
print(func.__closure__)
print(func.__closure__[0].cell_contents)
print(func.__closure__[1].cell_contents)

2.4 闭包的应用场景

  1. 闭包可以优先使用外函数中的变量,并对闭包中的值起到了封装保护的作用,外界无法访问,也无法修改。
  2. 闭包执行以后,因为内函数与外函数作用域的变量绑定引用的关系,所以这些变量可以在多次调用闭包函数时仍然保持原值或者状态。

因此闭包的应用场景非常广泛,以下是一些常见的使用场景:

2.4.1 状态保持

2.4.1.1 封装数据

闭包可以用来封装数据,同时隐藏内部实现细节,可以类似于面向对象编程中的封装效果。

from typing import Callable

def make_counter() -> Callable:
    count: int = 0
    def counter() -> int:
        nonlocal count
        count += 1
        return count
    return counter

# 使用闭包封装计数器
counter1: Callable = make_counter()
print(counter1())  # 输出: 1
print(counter1())  # 输出: 2

counter2: Callable = make_counter()
print(counter2())  # 输出: 1

在上面代码中,counter 函数返回了一个闭包 increment,它能够访问并修改外部函数的局部变量 count。每个闭包都有自己的独立状态,实现了数据的封装。

2.4.1.2 缓存机制

闭包可以用来实现简单的缓存功能,记录避免重复计算。这种技术也称为“记忆化”(Memoization )。

from typing import Callable, Any, Dict, Tuple


def cached_function(func: Callable) -> Callable:
    cache: dict[tuple, ...] = {}

    def wrapper(*args: tuple) -> ...:
        if args in cache:
            print(f"从缓存中获取结果: {cache[args]}")
            return cache[args]
        result: ... = func(*args)
        cache[args] = result
        print(f"计算结果并缓存: {result}")
        return result

    return wrapper


def expensive_computation(x: int) -> int:
    return x * x

# 使用缓存机制
cache_expensive_computation = cached_function(expensive_computation)
cache_expensive_computation(4)  # 输出: 计算结果并缓存: 16
cache_expensive_computation(4)  # 输出: 从缓存中获取结果: 16

在上面代码中,memoized_function 使用了一个字典 cache 来存储已经计算过的结果,避免重复计算。

2.4.1.3 惰性执行

闭包可以用来实现延迟计算,即在需要时才计算结果。这种特性在某些场景下可以提高效率,尤其是在处理复杂计算或资源密集型任务时。

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

# 延迟计算
lazy_sum = lazy_add(2, 3)
print(lazy_sum())  # 输出 5

2.4.2 函数增强

2.4.2.1 工厂函数

闭包可以根据传入的参数动态生成具有不同特定行为的函数。

from datetime import datetime
def get_logger(level: str):
    colors: dict[str, int] = {
        'DEBUG': 92,
        'INFO': 97,
        'WARNING': 94,
        'ERROR': 93,
        'CRITICAL': 91,
    }
    def wrapper(errors):
        print(f"\033[{colors[level.upper()]}m{level.upper():10} - [{datetime.now()}]: {errors}\033[m")
    return wrapper

# 配置函数
debug = get_logger("debug")
debug('调试信息')
info = get_logger("info")
info("服务器正在运行在 http://127.0.0.1:80")
info('"GET / HTTP/1.1" code 200')
warning = get_logger("warning")
warning('"GET /logo.png HTTP/1.1" code 404')

在上面代码中,get_logger 是一个函数工厂,它根据传入的 level 参数生成不同等级的日志函数,用于实现不同的打印日志效果。

2.4.2.2 带帽函数

带帽函数在很多编程语言中都得到了广泛的引用,Python的装饰器本质上就是带帽函数,用于增强或修改函数的行为。

def decorator(func):
    def wrapper(*args, **kwargs):
        print("在函数执行前做一些操作")
        result = func(*args, **kwargs)
        print("在函数执行前做一些操作")
        return result
    return wrapper

def say_hello(name):
    print(f"Hello, {name}!")

say_hello_func = decorator(say_hello)
say_hello_func("moluo")

@decorator
def say_hello(name):
    print(f"Hello, {name}!")

say_hello("墨落")

在上面代码中,decorator 是一个带帽函数,它返回了一个闭包 wrapper,用于在调用原始函数前后添加额外的逻辑。

3. 递归函数

递归(Recursion)是解决编程问题的一种编程思维或解决思路,是程序不断重复调用自身的一种代码写法,所谓递归函数(Recursion Function),就是一种自己不断调用自己的函数。递归函数也属于高阶函数的一种用法。

3.1 声明递归函数

# 方式1:
def fn(参数列表1):
    fn(参数列表2)

# 方式2,也叫尾递归,尾递归的运行速度和消耗性能比普通递归要好,可以的话,有限使用尾递归,但是CPython不支持尾递归:
def fn(参数列表1):
    return fn(参数列表2)

3.2 递归使用

接下来,我们如果要编写一段代码输入5-1之间的整数:

"""
输出10到1之间自然数
"""
# 使用循环实现
for i in range(100,0,-1):
    print(i)

# 递归:不断调用自身,不断展开新的一个函数空间,完成重复的操作,直到不满足
def fn(n):
    print(n)
    if n > 1:
        return fn(n-1)

fn(10)

递归可以理解为在空间上不断重复的操作,在某种程度来说,也可以理解为循环。

print(5)
    print(4)
        print(3)
            print(2)
                print(1)

3.3 步骤分解

对于零基础的同学,递归是比较难理解的函数使用方式。学习递归过程中需要依靠画图分析代码和脑海想象模拟代码运行的方式来辅助理解。诀窍:把递归过程中的函数理解成另一个新的同样代码的函数。当然,如果对递归不熟悉,也可以改用循环,凡是递归能完成的,循环都可以做到。所以暂时不清晰也没关系,过一段时间再回来看就清晰了。

前面我们已经清楚了递归的基本能使用,那如果要使用递归输出543212345这样的回文数字,怎么实现呢?

# 使用循环来实现:
for i in range(5,0,-1):
    print(i, end="")
for i in range(2,5+1):
    print(i, end="")
print()

# 使用递归来实现:
def digui(n):
    if n > 1:
        print(n, end="")
        digui(n-1)
    print(n, end="")
digui(5)
print(5)
    print(4)
        print(3)
            print(2)
                print(1)
            print(2)
        print(3)
    print(4)
print(5)

3.4 栈桢空间

python在内存中是使用栈桢空间是用来运行函数的。调用函数就是开辟栈桢空间的过程,调用结束之后,会自动释放栈桢空间。

每调用一次函数,就开辟一个栈桢空间,每结束一个函数,就释放一层栈桢空间,而递归本质上就是开辟和释放栈桢空间的过程。

使用递归时务必在函数调用前添加一个判断条件,如果递归的层数过多,层数达到1000层则报错,将会被python解释器强制终止递归。(经过测试在python3.9版本中最大递归层是996。嗯,很吉利的一个数字。)

根据上述所言,因为递归函数的运行需要占用的空间内存等性能消耗远比循环要高。因此建议大家少用递归,多用循环,除非这个问题真的很适合使用递归。而针对复杂操作多用递归,少用循环,因为复杂操作情况下的循环代码(4层以上嵌套循环),很容易让人懵逼,增加代码的维护成本。

3.5 课堂练习

1. 编写递归函数计算任意数n的阶乘

2编写递归函数输出斐波那契数列的第n项

3. 编写递归函数输出类似123454321这样的回文数字
"""
1. 编写递归函数,计算任意数n的阶乘。
1*2*3*4...*n  n 的阶乘
采用递归的思路:
假设n=4
1*2*3*4        要计算4的阶乘,先要计算3的阶乘是多少
1*2*3          要计算3的阶乘,先要计算2的阶乘是多少
1*2            要计算2的阶乘,先要计算1的阶乘是多少
1              1的阶乘就是1呀!!!
得到1的阶乘1以后,
则2的阶乘就是
fn(2) ---> fn(1)*2
则3的阶乘就是
fn(3) ---> fn(2)*3
"""
# def fn(n):
#     if n == 1:
#         return 1
#     else:
#         return n*fn(n-1)
#
# ret = fn(4)
# print(ret)


"""
2,编写递归函数,输出斐波那契数列的第n项。
斐波那契数列:
0,1,1,2,3,5,8,13,21,34,55...
计算第n项的数列
fa(n)  ---> fa(n-1) + fa(n-2)
假设n=5
fn(5)  ---> fa(4) + fa(3)
            fa(4) ----> fa(3) + fa(2)
                    fa(3) ---> fa(2) + fa(1)
                               fa(2) ---> fa(1) + fa(0)
                                             1       0
"""
# def fa(n):
#     if n == 0:
#         return 0
#     if n == 1:
#         return 1
#     return fa(n-2) + fa(n-1)
#
# print( fa(4) ) # 3
# print( fa(10)) # 55


"""
3. 编写递归函数,输出类似123454321这样的回文数字。
"""


def fn(n, s=1):
    if s < n:
        print(s, end="")
        fn(n, s + 1)
    print(s, end="")


fn(5)

4. 内置函数

官方文档:https://docs.python.org/zh-cn/3.9/library/functions.html#built-in-functions

python解释器提供了一些常用的内置函数给开发者在python任意位置使用,我们可以通过以下代码查看。

print( dir(__builtins__) )

4.1 基础函数

函数 描述
all(iterable) 判断可迭代参数 iterable 中的所有元素是否都为 True,
如果是返回 True,否则返回 False。
除了是 0、“”、None、False 外都算 True。
any(iterable) 判断可迭代参数 iterable 中的所有元素是否至少有一个为 True,
如果是返回 True,否则返回 False。
除了是 0、“”、None、False 外都算 True。
bytes([source[, encoding[, errors]]]) 将参数source转换成bytes字节流,是一个不可变序列,
相当于str(source).encode()。
bytearray([source[, encoding[, errors]]]) 将参数source转换成bytes字节数组,是一个可变序列
可以理解是字节组成的列表。
chr(i) 返回当前整数对应的 ASCII 字符。
ord© 返回当前ASCII字符对应的整数。
exec(object[, globals[, locals]]) python内置代码解释器,可以执行字符串或文件内容中的python代码,
存在安全性问题。
eval(expression[, globals[, locals]]) python内置代码解释器,可以执行储存在字符串中的Python表达式,
功能没有exec强大,存在安全性问题。
compile(source, filename, mode[, flags[, dont_inherit]]) 将一个字符串编译为字节代码。一般都是compile配合exec使用。
complex([real[, imag]]) 转化一个字符串或数为复数。复数是python中数值类型之一。
dir([object]) 收集当前作用域范围内的信息(变量,函数等)
enumerate 将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,
同时列出数据和数据下标,常用于 for 循环中。
reversed 反转序列,可以把字符串、列表、元组等序列进行反转排列顺序。
frozenset 返回一个冻结集合,冻结后集合不能再添加或删除任何元素。
hash 获取取一个对象(字符串或者数值等)的哈希值。工作中一般使用hashlib
zip 将可迭代的对象作为参数,将对象中对应的元素压缩成一个个元组,
然后返回由这些元组组成的列表。加*号则表示解压。
locals 获取当前作用域的所有标记符(变量,函数,类等...)
globals 获取全局作用域的所有标记符(变量,函数,类等...)
"""all"""
data = [1, 2, 3, 4]
print(all(data))  # True
data = [1, 2, 3, 4, 0]
print(all(data))  # False

"""any"""
data = [1, 2, 3, 4]
print(any(data))  # True
data = [0, False]
print(any(data))  # False
data = [0, False, 1]
print(any(data))  # True,因为1是True

"""
bytes字节流与bytearray字节数组,属于二进制的字节数据
string字符串与bytes字节流的区别:
1. 字符串是字符组成的有序序列
2. bytes是字节组成的有序的不可变序列
3. bytearray是字节组成的有序的可变序列
"""
# 声明一个bytes字节流数据
b0 = b'abc'
for i in b0:
    print(i, chr(i))  # 97 a    98 b   99 c

# 声明一个bytearray字节流数据
ba0 = bytearray(b'abc')
for i in ba0:
    print(i, chr(i))  # 97 a    98 b   99 c

# 字符串按照不同的字符集进行编码可以得到字节序列bytes或bytearray字节数组
# 字符串 ---> bytes字节流
#      字符串.encode()
#      bytes(字符串, encoding="utf-8")
# 字符串 ---> bytearray字节数组
#      bytearray(字符串,encoding="utf-8")

s1 = "abc"
b1 = s1.encode()  # encode默认转换时的默认编码集是utf-8
# b1 = bytes(s1, encoding="utf-8") # 与上面是等价的
print(b1, type(b1) is bytes)  # b'abc' True

s2 = "abc"
b2 = bytearray(s2, encoding="utf-8")
print(b2, type(b2) is bytearray)  # bytearray(b'abc') True

# bytes字节流或bytearray字节数组按照不同的字符集调用decode()进行解码可以得到字符串
b3 = b'hello'
s3 = b3.decode()
print(s3, type(s3) is str)  # hello True

b4 = bytearray(b'abc')
s4 = b4.decode()
print(s4, type(s4) is str)  # abc True

# bytes字节流属于不可变类型,与字符串的操作一样,只是一个bytes字节流,一个是字符串而已。
# bytearray字节数组属于可变类型,与列表的操作类似,支持append, insert等操作
b5 = bytearray(b'bcd')
b5.append(ord("e"))
print(b5)  # bytearray(b'bcde')

b5.insert(0, ord('a'))
print(b5)  # bytearray(b'abcde')

"""compile与exec、eval"""
num = 10
ret = eval("num+5")
print(ret)  # 15

# 执行一行代码
exec('print("Hello World")')  # Hello World
# 执行多行代码
exec("""
for i in range(5):
    print(i)
""")

# 结合comple一起使用
str = """
for i in range(0,10):
    print(i)
"""
content = compile(str, 'main', 'exec')
exec(content)

# 扩展:传入数据到exec解释器
namespace = {'name': 'moluo', 'data': [18, 73, 84]}

code = '''
def fn1():
    return  f"name: {name} age:{data[0]}"
'''
func = compile(code, '', "exec")
exec(func, namespace)
result = namespace["fn1"]()
print(result)

"""复数 complex"""
# python中数值类型除了整数、浮点数、布尔值以外,还有一个相对少用的复数complex。
# 声明一个复数
print(1 + 2j)  # (1+2j)
num = complex(1, 2)
print(num)  # (1+2j)

# 复数的运算,遵循实部与实部计算,虚部与虚部计算。
a = 1 + 2j
b = 3 + 2j
print(a * b)  # (4+0j)

"""enumerate"""
data = ["A", "B", "C"]
for index, value in enumerate(data):
    print(index, value)

"""reversed 反转序列 """
ret = reversed((1, 2, 3, 4))
ret = list(ret)
print(f"ret={ret}")

"""frozenset 冰冻集合"""
set0 = set(range(5, 11))
print(set0)  # {5, 6, 7, 8, 9, 10}
set0.add(11)
print(set0)  # {5, 6, 7, 8, 9, 10, 11}
set1 = frozenset(set0)
print(set1)  # frozenset({5, 6, 7, 8, 9, 10})
# set1.add(12) # 此处会报错,没有add操作

"""hash 求指定对象的哈希值"""
data = "hello"
print(hash(data))  # 2990426454566097731

"""zip 压缩数据
注意:这不是操作系统的zip压缩 
"""
list_data1 = [1, 2, 3]
list_data2 = [4, 5, 6]
data = zip(list_data1, list_data2)
print(data)  # <zip object at 0x000001E292EC5D00> 压缩对象
ret = list(data)
print("压缩效果:", ret)  # [(1, 4), (2, 5), (3, 6)]

list_data1 = [1, 2, 3]
list_data2 = [4, 5, 6]
data1, data2 = zip(*zip(list_data1, list_data2))
print("解压效果:", data1, data2)  # (1, 2, 3) (4, 5, 6)

滑动序列

# 所谓的滑动序列,指可以根据指定一个任意长度n,对列表进行划分成n个子序列的序列容器。
data = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# n = 2
# data = [(1, 2), (3, 4), (5, 6), (7, 8)]
# n = 3
# data = [(1, 2, 3), (4, 5, 6), (7, 8, 9)]
# n = 4
# data = [(1, 2, 3, 4), (5, 6, 7, 8)]
# n = 5
# data = [(1, 2, 3, 4, 5)]

def fn(data, n):
    data_list = []
    for i in range(n):
        print(f"data[{i}::{n}]={data[i::n]}")
        data_list.append(data[i::n])
    return zip(*data_list)


ret = fn(data, 2)
# print(list(ret))

基于globals获取当前全局作用域下可以使用的标记符。

"""demo.py"""

num = 100
age = 13
data = [1,2,3,4]
print(globals())
"""
{
    # 以下属于内置作用域提供的变量,默认可以直接使用的,当然工作中仅有几个是比较常用。
    '__name__': '__main__',   # 当前文件名,也叫当前模块名,如果值为__main__则表示当前文件是程序运行入口文件
    '__doc__': 'demo.py',     # 首行注释,一般用于描述当前python模块的用途
    '__builtins__': <module 'builtins' (built-in)>,  # python解释器内置作用域模块
    '__file__': 'C:/Users/Administrator/PycharmProjects/pythonProject/demo.py',  # 当前文件所在的绝对路径
    'num': 100, 
    'age': 13, 
    'data': [1, 2, 3, 4]
}
"""

基于locals获取当前局部作用域的可用标记符。

a = 100
def func1():
    b = 200
    print(locals()) # {'b': 200}
    def func2():
        print(locals()) # {}
    func2()
func1()
print(locals())
"""
{
    '__name__': '__main__', 
    '__doc__': None, 
    '__package__': None, 
    '__builtins__': <module 'builtins' (built-in)>, 
    '__file__': 'C:/Users/Administrator/PycharmProjects/pythonProject/demo.py', 
    'a': 100, 
    'func1': <function func1 at 0x000001DF46597280>
}
"""

主要:在模块层级上,globals 和 locals 的结果是同一个字典

把函数内部出现的所有局部变量集体返回给外界。

def fn():
    title = "小标题"
    user  = "小明"
    age   = 15
    return locals()

ret = fn()
print(ret) # {'title': '小标题', 'user': '小明', 'age': 15}

利用globals可以快速批量创建全局变量。

# 动态创建5个变量p1~p5
def fn():
    for i in range(1,6):
        dictvar = globals()
        dictvar["p%d" % (i)] = i
fn()
print(p1)
print(p2)
print(p3)
print(p4)

4.2 高阶函数

函数 描述
filter(function, iterable) 过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。
map(function, iterable, ...) 根据提供的函数对指定序列做映射处理。
sorted(iterable, cmp=None, key=None, reverse=False) 对所有可迭代的对象进行排序操作。
functools.reduce(function, iterable[, initializer]) 对参数序列中元素进行叠加/累积操作,返回一个最终结果

注意:在python2中,reduce属于内置函数,直接可以使用,在python中被官方移至functools模块中了,必须导入functools才能使用。

"""
filter过滤函数
例如:剔除一个列表中所有奇数成员,保留偶数成员.
"""


def fn(x):
    return not x % 2

data = [1, 2, 3, 4]
ret = filter(fn, data)  # [2, 4]
print(list(ret))
ret = filter(lambda x: not x % 2, data)
print(list(ret))  # [2, 4]


"""
map 映射处理函数
例如:对列表的每一个成员都数值处理,保留2位小数
"""


def fn(x):
   return float(f"{x:.2f}")


data = [99.333, 55, 60, 30.560]
print(list(map(fn, data)))
print(list(map(lambda x: float(f"{x:.2f}"), data)))


"""
sorted 高阶排序
sort是 list的操作方法,修改的是原数据
而sorted可以对所有可迭代的对象进行排序操作,返回修改后的新数据。
"""
# 基本使用
data = [5, 2, 6, 3, 4]
data.sort()
print(data)

data = [5, 2, 6, 3, 4]
ret = sorted(data)
print(ret)

ret = sorted({5: 'D', 2: 'B', 6: 'B', 3: 'E', 4: 'A'})
print(ret)


# 对字典数据按键排序
data = {
   "name": "moluo",
   "age": 16,
   "sex": True,
   "mobile": "13928835901"
}
# 得到二级等长容器
ret = sorted(data.items(), key=lambda item: item[0])
print(ret) # [('age', 16), ('mobile', '13928835901'), ('name', 'moluo'), ('sex', True)]
dict_data = dict(ret)
print(dict_data) # {'age': 16, 'mobile': '13928835901', 'name': 'moluo', 'sex': True}