python函数扩展

1. lambda函数

用于创建匿名函数,可以作为排序函数中的自定义排序规则,或者作为高阶函数的参数

例如:
按照元组的第二个元素进行排序

numbers=[(1,2),(3,1),(5,4)]
numbers.sort(key=lambda x:x[1])

和map函数一起使用,实现对可迭代对象的操作

numbers=[1,2,3,4,5]
sq=list(map(lambda x:x**2,numbers))

2. 函数装饰器

通过装饰器可以扩展或者修改函数的行为
例如:

def log(func):
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__} 函数")
        result = func(*args, **kwargs)
        print(f"{func.__name__} 函数执行完毕")
        return result
    return wrapper

@log
def add(x, y):
    print x + y
    return x + y

result = add(3, 5)

该代码实现了一个用于记录函数的调用和执行情况的装饰器
其中log是一个装饰器函数,接受一个函数func作为参数
直观上理解,在后续定义函数add时,在声明语句前有一个@log,表明log时一个装饰器同时add作为log的函数参数
在装饰器内部定义了一个wrapper函数,具体实现了打印被装饰函数执行前后的日志信息
执行该代码,将会输出

调用 add 函数
8
add 函数执行完毕

注意到wrapper函数中有两个参数*args和**kwargs
这两个参数是通过被修饰函数(如这里的add)传入的参数自动捕获的,作用是接受被修饰参数函数的所有参数,并原样传递给原函数(func)
在上面这个例子中3和5是位置参数,因此args=(3,5),而kwargs={},因为没有关键字参数

3. python函数的参数

python函数的参数可以分为

参数类型 语法 作用
位置参数 def f(a,b) 按顺序传递,且传入顺序固定
默认参数 def f(a=1) 调用时可省略
可变位置参数 *args 接受任意数量的位置参数,打包为元组
可变关键字参数 **kwargs 接受任意数量的关键字参数,打包为字典
仅关键字参数 *,a 要求a必须以关键字形式传递

可变位置参数和可变关键字参数使得函数编写时的灵活性更强,例如前一小节中的装饰器函数中复制参数并原样传递给原函数
同时*和**还可以用作参数解构
具体来说
*解构可迭代对象为位置参数,例如列表、元组、字符串等,然后将序列元素逐个传给函数
例如

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

numbers = [1, 2, 3]
result = add(*numbers)  # 等价于 add(1, 2, 3)
print(result)  # 输出: 6
def greet(first, second):
    print(f"Hello, {first} and {second}!")

names = "Alice,Bob"
greet(*names.split(','))  # 等价于 greet("Alice", "Bob")
# 输出: Hello, Alice and Bob!

类似的,**可以解构字典作为关键字参数

def person_info(name, age, city):
    print(f"{name}, {age}岁, 来自{city}")

data = {"name": "Alice", "age": 25, "city": "北京"}
person_info(**data)  # 等价于 person_info(name="Alice", age=25, city="北京")
# 输出: Alice, 25岁, 来自北京

4. 闭包

def outer_function(x):
    def inner_function(y):
        return x + y
    return inner_function

closure = outer_function(10) # x=10 被闭包记住
result = closure(5)
print(result)  # 输出:15

闭包(Closure)是指一个函数(内部函数)可以访问并记住其外部函数(enclosing function)的变量,即使外部函数已经执行完毕。
闭包的两个关键特点:

  1. 内部函数引用外部函数的变量(inner_function 使用了 outer_function 的 x)。
  2. 外部函数返回内部函数(outer_function 返回 inner_function)。

闭包实现的原理是__closure__属性,换言之,如果如果函数是闭包,func.__closure__ 会自动返回一个包含 cell 对象的元组,每个 cell 存储了外部变量的值。
根据闭包的形式,可以发现我们上面提到的装饰器实际上也是闭包的一种特殊应用

闭包的关键特点同时也是Python如何识别闭包的方式,也就是当函数满足

  1. 嵌套函数:外部函数 (outer_function) 内部定义了另一个函数 (inner_function)。
  2. 内部函数引用外部变量:inner_function 使用了 outer_function 的变量(如 x)。
  3. 外部函数返回内部函数:outer_function 返回 inner_function,使得内部函数可以在外部作用域被调用。

函数被识别为闭包
同时一定程度上,可以理解为把外部函数的调用赋给一个变量时,这个变量相当于是存储了外部函数参数的内部函数实例,下次再调用这个变量的时候就相当于调用了内部函数
于是闭包可以用于实现函数工厂(动态生成函数)

def make_multiplier(n):
    def multiplier(x):
        return x * n
    return multiplier

double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5))  # 输出: 10
print(triple(5))  # 输出: 15

但是在循环中创建闭包时,所有闭包可能共享同一个变量(这时需用默认参数或 lambda 绑定当前值)

# 错误示例:所有闭包共享最终的 i
functions = []
for i in range(3):
    def func():
        return i
    functions.append(func)
print([f() for f in functions])  # 输出: [2, 2, 2]

# 正确做法:用默认参数绑定当前值
functions = []
for i in range(3):
    def func(i=i):  # 绑定当前 i
        return i
    functions.append(func)
print([f() for f in functions])  # 输出: [0, 1, 2]

5. 生成器

生成器是一种特殊的迭代器,通过 yield 关键字实现逐步生成值,而不是一次性计算所有值
这在处理大数据流或无限序列(如斐波那契数列)时非常高效,因为可以仅在需要的时候进行调用
核心特点:

  1. 每次只在调用next()时才会执行yield后的语句(这个特征也可以记作是惰性计算)
  2. 生成器会记住上次执行的位置和局部变量状态

例如:

def fibonacci():
    a, b = 0, 1       # 初始化前两个斐波那契数
    while True:       # 无限循环,生成无限长的序列
        yield a       # 生成当前值 a,并暂停执行
        a, b = b, a + b  # 更新 a 和 b 为下一个斐波那契数

# 创建生成器对象
fib = fibonacci()

# 打印前10个斐波那契数
for _ in range(10):
    print(next(fib))  # 输出: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

每次调用next()语句(或者初次执行时),函数都会从之前停止的地方继续运行,直到再次遇到yield语句,执行完yield语句后,代码停止,等待下次触发
除了next()外,也可以用send()进行触发,不同的是send还会想生成器发送值x,将作为yield表达式返回值,也就是可以用一个左值来接受,如

def interactive_gen():
    print("Start")
    x = yield "Please send a value"  # 第一次 yield 返回提示,等待 send
    print(f"Received: {x}")
    yield f"You sent: {x}"

gen = interactive_gen()
print(next(gen))          # 输出: "Please send a value"(执行到第一个 yield)
print(gen.send(42))       # 输出: "Received: 42" → "You sent: 42"

不过注意第一次调用时,必须用 next() 或 send(None)(因为首次没有 yield 可以接收值)
对于要使用send()的函数来说yield 既是返回点,也是接收点
通过传输消息,可以实现一般代码所不具有的交互性
例如
协程(通过 send() 实现双向通信)

def coroutine():
    while True:
        received = yield  # 接收外部发送的值
        print(f"Received: {received}")

c = coroutine()
next(c)        # 启动协程(执行到第一个 yield)
c.send("Hello")  # 输出: "Received: Hello"

委托生成器(yield from)

def sub_gen():
    yield 1
    yield 2

def main_gen():
    yield from sub_gen()  # 委托给 sub_gen
    yield 3

for num in main_gen():
    print(num)  # 输出: 1, 2, 3

通过yield的惰性计算,可以实现对大数据、无限序列等的读取,因为不会因为一直执行而导致死循环或者直接耗尽内存
例如用生成器读取超大文件:

def read_large_file(file_path):
    with open(file_path, 'r') as file:
        for line in file:
            yield line.strip()  # 逐行生成,避免内存溢出

# 使用示例
for line in read_large_file("huge_file.txt"):
    process(line)  # 每次处理一行

yield 本质上实现了一种交互式、分步执行的程序控制方

6.函数式编程

高阶函数

高阶函数是可以将其他函数作为自身函数参数或者返回的函数
例如:

def apply(func, x):
    return func(x)

def square(x):
    return x ** 2

result = apply(square, 5)
map函数

map函数可以将函数应用于可迭代对象的每个元素,并返回结果
例如:

numbers = [1, 2, 3, 4, 5]
squared = map(lambda x: x ** 2, numbers)
reduce函数

一定程度上和map比较相似,都是用于可迭代对象的,但是reduce函数是逐步迭代合并结果,例如实现累加

from functools import reduce

numbers = [1, 2, 3, 4]
sum_result = reduce(lambda x, y: x + y, numbers)
print(sum_result)  # 输出: 10
numbers = [1, 2, 3]
sum_with_init = reduce(lambda x, y: x + y, numbers, 10)  # 初始值为10
print(sum_with_init)  # 输出: 16 (10 + 1 + 2 + 3)

可以理解为从可迭代对象中依次取出元素,通过指定的二元函数不断更新累积值,最终返回最后一次计算的结果,也就是y从numbers中取出,然后每次更新x=x+y,最后输出x
累加等功能大部分时候都是使用内置函数更为方便,大家如果想要自定义累加逻辑,例如加权和时,可以考虑使用reduce
例如:

data = [(1, 0.5), (2, 1.5), (3, 2.5)]
total = reduce(lambda acc, (val, weight): acc + val * weight, data, 0)
print(total)  # 输出: 1*0.5 + 2*1.5 + 3*2.5 = 11.0
posted @ 2025-03-29 00:50  rdcamelot  阅读(10)  评论(0)    收藏  举报