匿名函数

lambda函数

匿名函数(也称为lambda函数)是一种可以在需要时快速定义的小型函数。

1
2
sq = lambda x: x * x
sq(2)

这里用到的 lambda 表达式与下面的函数定义有着相同的功能:

1
2
3
4
5
def sq(x):
return(x * x)
sq(2)

print((lambda x: x * x)(2))

lambda表达式的基本语法如下:

         lambda arg1,arg2,arg3...: <表达式>
                    
   其arg1/arg2/arg3为函数的参数
   <表达式>相当于函数体
   函数返回值:表达式的计算结果

注意

  1. lambda实际生成了一个函数对象。
  2. lambda表达式只允许包含一个表达式。

lambda函数的使用:

可以作为另一个函数的参数传递,用于定制特定的行为。

1
2
3
nums = [1, 2, 3, 4, 5]
result = list(map(lambda x: x + 1, nums))
print(result)

参数中可以使用lambda作为参数的还有map()等。

使用lambda表达式反转拼写,然后依此给单词列表排序

1
2
fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
sorted(fruits, key=lambda word: word[::-1])

除了作为参数传递给高阶函数之外,Python 很少使用匿名函数。

lambda句法只是语法糖:与 def语句一样,lambda表达式会创建函数对象。

总结:

  1. 匿名函数是Python中的一种快速定义小型函数的方式
  2. 它没有函数名,使用lambda关键字定义
  3. 匿名函数可以简化代码、作为函数参数传递以及在列表推导中应用

闭包

  1. 闭包的定义 在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。
  2. 闭包的构成条件 在函数嵌套的前提下 内部函数使用了外部函数的变量或者参数 外部函数返回了内部函数
  3. 闭包的作用 可以保存外部函数内的变量,不会随着外部函数调用完而销毁 由于闭包引用了外部函数的变量,则外部函数的变量没有及时释放,消耗内存。
1
2
3
4
5
6
7
8
9
10
11
12
#在函数嵌套的前提下
def outer():
num1=10
def inner():
re=num1+10 #内部函数使用了外部函数的变量或者参数
print(re)
return inner #外部函数返回了内部函数,这个使用外部函数变量的内部函数称为闭包

#获取闭包对象
new_fun=outer() #这个new_fun就是闭包
#执行闭包
new_fun()

举例:

假如有个名为 avg 的函数,它的作用是计算不断增加的系列值的均值。

例如,商品历史周期内的平均价格,每天添加一个新的价格,目前为止均价的计算要考虑商品全部价格。

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
# 计算移动平均值的类
class Averager():

def __init__(self):
self.series = []

def __call__(self, new_value):
self.series.append(new_value)
total = sum(self.series)
return total/len(self.series)

# 计算移动平均值的高阶函数。
# 当调用make_averager函数时,返回的是averager函数对象。
# 每次averager被调用时,它会将传递的参数追加到series,并计算当前的平均数。
def make_averager():
total = 0
count = 0

def averager(new_value):

count = count + 1 #本意想修改外部函数,其实是再闭包内定义了一个局部变量
total += new_value
return total/count

return averager

# Averager的实例是可调用对象
avg = Averager()
print(avg(10))
print(avg(11))
print(avg(12))

avg2 = make_averager()
print(avg2(10))
print(avg2(11))
print(avg2(12))

averager 是定义在 make_averager 内部的函数,形成了闭包

在averager内部,series是一个自由变量。

这是一个技术术语,指未在本地作用域中绑定的变量。

image-20250514153301357
1
2
3
4
5
6
7
8
9
'''Python 在 __code__ 属性(表示编译后的函数定义体)中
保存局部变量和自由变量的名称。
series 的绑定在返回的 avg 函数的 __closure__ 属性中。
avg.__closure__ 中的各个元素对应于avg.__code__.co_freevars 中的一个名称。
这些元素是 cell 对象,有个 cell_contents 属性,保存着真正的值。'''
print(avg.__code__.co_varnames)
print(avg.__code__.co_freevars)
print(avg.__closure__) # doctest: +ELLIPSIS
print(avg.__closure__[0].cell_contents)

示例:

创建一个闭包实现计数器

编写一个闭包函数,它应该提供一个计数器功能。每次调用该闭包时,它应返回下一个整数。

例如,首次调用返回 1,下一次调用返回 2,依此类推。

提示: 使用一个非局部变量来存储当前计数。

1
2
3
counter() 1
counter() 2
counter() 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def create_counter():
count = 0 # 非局部变量,用于存储当前计数值

def counter():
nonlocal count # 声明 count 是外层函数的变量
count += 1 # 每次调用时增加计数
return count # 返回新的计数值

return counter # 返回闭包函数


# 创建计数器
counter = create_counter()

# 测试计数器
print(counter()) # 输出 1
print(counter()) # 输出 2
print(counter()) # 输出 3

总结

  1. 闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。

  2. 注意,只有嵌套在其他函数中的函数才可能需要处理不在全局作用域中的外部变量。

装饰器

装饰器是 Python 中一种强大且灵活的语法结构,本质上是一个闭包函数。它可以在不改变原函数代码和调用方式的前提下,为函数增添新的功能。这就好比给一个物品添加装饰,在不改变物品本身核心功能的基础上,让它具备更多特性。

装饰器在代码的可维护性、复用性和可读性方面都有很大帮助,广泛应用于日志记录、性能测试、权限验证等场景。

  1. 是一种函数,有内置的装饰器,也可以自定义装饰器,本质上就是一个闭包函数。
  2. 可以在不改变函数调用方式的情况下给函数增加对应功能
  3. 通过@加上命名进行使用,作用在函数声明的前面,如下:
1
2
3
@property
def value(self):
return self._value

简单来说,装饰器函数接收一个函数作为参数,然后返回一个新的函数。新函数通常会在执行原函数前后添加额外的逻辑,从而实现功能增强。

内置装饰器示例

Python 有一些内置装饰器,例如@property ,它用于将类中的方法转换为属性调用的形式,让代码更加简洁直观。

1
2
3
4
5
6
7
8
9
10
class Person:
def __init__(self, age):
self._age = age

@property
def age(self):
return self._age

person = Person(30)
print(person.age) # 像访问属性一样访问方法

@property 装饰器把age方法装饰成了一个属性,调用person.age时,实际上调用的是被装饰的age方法,但看起来就像在访问一个普通属性,提升了代码的易用性。

自定义装饰器示例

下面通过一个简单的例子来展示如何自定义装饰器,实现函数执行时间的计算功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import time

def timer(func):
def wrapper():
start_time = time.time()
func()
end_time = time.time()
print(f"函数 {func.__name__} 执行耗时: {end_time - start_time} 秒")
return wrapper

@timer
def say_hello():
time.sleep(2)
print("Hello!")

say_hello()
  1. 定义装饰器函数 timer
    • timer 函数接收一个函数 func 作为参数,这是装饰器的基本形式,即接收被装饰的函数。
    • 内部定义了一个 wrapper 函数,wrapper 函数就是闭包函数,它可以访问外部函数 timer 作用域内的 func 变量。
    • wrapper 函数中,首先记录开始时间 start_time = time.time() ,然后调用原函数 func() ,再记录结束时间 end_time = time.time() ,最后计算并打印函数执行的耗时。
    • timer 函数最后返回 wrapper 函数对象。
  2. 使用装饰器
    • @timer 语法将 timer 装饰器应用到 say_hello 函数上,这等同于执行 say_hello = timer(say_hello)
    • 调用 say_hello() 时,实际上调用的是 wrapper 函数,wrapper 函数会在执行 say_hello 函数的前后添加计算时间的逻辑,从而实现了在不改变 say_hello 函数本身代码和调用方式的情况下,为其增添了计算执行时间的功能。

带参数的装饰器

有时候我们需要给装饰器传递参数,来定制不同的装饰逻辑。下面是一个带参数的装饰器示例,用于根据不同的日志级别打印函数调用信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def logger(level):
def decorator(func):
def wrapper():
if level == "DEBUG":
print(f"DEBUG: 调用函数 {func.__name__}")
elif level == "INFO":
print(f"INFO: 调用函数 {func.__name__}")
func()
return wrapper
return decorator

@logger(level="INFO")
def greet():
print("Welcome!")

greet()
  1. 定义带参数的装饰器外层函数 logger
    • logger 函数接收一个参数 level ,用于指定日志级别。
    • 它返回另一个函数 decoratordecorator 函数才是真正接收被装饰函数的装饰器函数。
  2. decorator 函数
    • 接收被装饰的函数 func ,内部定义 wrapper 函数。
    • wrapper 函数根据传入的 level 参数进行不同的日志打印,然后调用原函数 func
    • decorator 函数返回 wrapper 函数。
  3. 使用带参数的装饰器
    • @logger(level="INFO") 这种形式先调用 logger 函数并传入 level 参数,得到具体的装饰器函数,再将其应用到 greet 函数上。这样就实现了根据不同参数定制装饰器行为的功能。

总结:

  1. 装饰器可以在不改变原函数的定义和调用方式的基础上,增强函数的功能。
  2. 严格来说,装饰器只是语法糖。装饰器可以像常规的可调用对象那样调用,其参数是另一个函数。
  3. 装饰器能把被装饰的函数替换成其他函数。