Python中的闭包与装饰器学习笔记
文章目录
1.闭包
学习闭包之前我们需要先理解函数、变量作用域、嵌套函数等概念。
1.函数:
1.函数的定义:
定义函数使用 def
关键字,后接函数名和圆括号 ()
。函数体从下一行开始,必须有缩进。函数的格式如下:
def function_name( parameters ):
"函数_文档字符串"
function_suite
return [expression]
在这里, “函数_文档字符串” 是可选的,用来描述函数的功能。“function_suite” 是一系列 Python 语句,代表了函数的主体。“return [expression]” 也是可选的,表示函数执行完毕后返回的结果。
2.变量作用域:
调用函数只需要使用函数名,后接括号,如果有参数的话,参数放在括号里。比如:
def print_hello(name):
print(f"Hello, {name}")
print_hello("World") # 输出:Hello, World
3.参数传递:
函数中,可能需要外部传入一些值来进行处理,这些值被称为参数。Python 提供了多种参数传递方式,包括位置参数、默认参数、可变参数、关键字参数等。
def print_info(name, age = 35):
"打印任何传入的字符串"
print("Name: ", name)
print("Age ", age)
return
# 调用函数
print_info(age=50, name="mathilda") # Name: mathilda Age 50
print_info(name="mathilda") # Name: mathilda Age 35
4.返回值:
函数中可以使用 return
语句来返回值。当函数执行到 return
语句,函数的执行会立即终止,并将 return
后的表达式的值,返回给函数的调用者。
def sum(a, b):
return a + b
result = sum(1, 2) # result 的值现在是 3
需要注意的是,如果函数没有 return
语句,或者 return
语句后没有任何表达式,那么这个函数的返回值为 None
。
2.变量作用域:
变量的可见性或作用域,决定了在哪些地方可以访问到这个变量。在 Python 中,变量分为两种作用域:
1.局部变量:
如果一个变量在一个函数中定义,则它就是局部变量。它只在这个函数内部有作用。也就是说,你只能在函数内部访问到这个变量,一旦出了函数的范围,这个变量就不存在了。
例如:
def my_function():
local_variable = "I'm local"
print(local_variable)
my_function() # 输出:I'm local
print(local_variable) # 报错,因为 local_variable 在函数外是没有定义的
2.全局变量:
如果一个变量在函数外部定义,那么它就是全局变量。全局变量可以在程序的任何地方被访问,包括所有的函数内部。
例如:
global_variable = "I'm global"
def my_function():
print(global_variable)
my_function() # 输出:I'm global
print(global_variable) # 输出:I'm global
但是,如果在一个函数内部,你想改变全局变量的值,你必须使用 global
关键字来声明。
例如:
global_variable = "I'm global"
def my_function():
global global_variable
global_variable = "I've been changed"
my_function()
print(global_variable) # 输出:I've been changed
在这个例子中,你可以看到全局变量的值被函数 my_function
改变了。如果你没有使用 global
关键字,那么 global_variable = "I've been changed"
这行代码会创建一个新的局部变量,而不是修改全局变量。
3.嵌套函数:
1.嵌套函数的定义:
嵌套函数的定义非常简单,就像平常定义函数一样,只不过它是在一个函数的内部进行定义的。
def outer_function(text):
print(text)
def inner_function():
print('Inner function')
inner_function()
outer_function('Outer function')
在这个例子中,inner_function
就是一个嵌套函数。
2. nonlocal:
在 Python 中,nonlocal
关键字用于在一个嵌套的函数中声明一个变量不是局部变量或全局变量,而是跟最近的外层函数绑定在一起。
nonlocal
的主要用途是允许赋值给外部函数(但非全局)的变量。如果没有nonlocal
关键字,我们只能读取外部函数的变量,而不能改变他们的值。
下面来看一个nonlocal
关键字的常见用法:
def outer():
x = 0
def inner():
nonlocal x # 使用外部函数的 x
x += 1
return x
return inner
counter = outer()
print(counter()) # 输出 1
print(counter()) # 输出 2
在这个例子中,outer
函数返回了inner
函数。当我们调用counter()
时,它实际上运行了inner
函数,这个函数使用并修改了outer
函数中的x
值。这就是nonlocal
关键字如何在闭包中工作的。
4.闭包:
看完了函数、变量作用域和嵌套函数,相信你对函数有了更深层次的理解,那么接下来理解闭包会更加轻松!
1.闭包的三要素:
-
一个嵌套函数。
-
这个嵌套函数引用了它的外部函数的值或变量。
-
外部函数返回了这个嵌套函数。
def outer_function(text): def inner_function(): print(text) return inner_function # 注意这里我们返回了函数 my_function = outer_function('Hello, World!') my_function() # 输出: Hello, World!
在这个例子中,我们看到
inner_function
能够记住并访问到它的外部函数outer_function
的局部变量text
,即使outer_function
已经完成了执行。
2.稍微复杂一点的闭包示例:
def outer_function(x):
def middle_function(y):
def inner_function(z):
return x + y + z
return inner_function
return middle_function
closure = outer_function(10)
closure = closure(20)
# 输出: 40
print(closure(10))
# 输出: 50
print(closure(20))
这是一个二层闭包函数。
首先,让我们看看这行代码:
closure = outer_function(10)
在这行代码中,我们调用outer_function
并传递参数10
给它。这个函数返回middle_function
函数对象,并且middle_function
函数对象记住了outer_function
的参数x
的值10
,然后我们将这个返回的函数赋值给变量closure
。
接着,我们再看看这行代码:
closure = closure(20)
在这行代码中,我们调用closure
并传递参数20
给它。因为此时的closure
实际上就是middle_function
函数(请注意,这个函数记住了outer_function
的参数x
的值10
),所以实际上这行代码等同于middle_function(20)
。这个函数返回inner_function
函数对象,这个返回的函数记住了middle_function
的参数y
的值20
。然后,我们将这个返回的函数再次赋值给变量closure
。
因此,通过上述两行代码,我们得到的closure
实际上是inner_function
函数,它记住了middle_function
的参数y
的值20
,以及其外层作用域outer_function
的参数x
的值10
。
这样,当我们再调用closure
时:
print(closure(10))
实际上等同于inner_function(10)
,这个函数会把参数z
的值10
以及它记住的参数x
的值10
和参数y
的值20
相加,然后返回这个结果40
。
2.装饰器:
1.函数是一等公民:
"函数作为一等公民"的概念是指在一种编程语言中,函数被看做与其他数据类型一样的实体,可以被创建、赋值、作为参数传递,以及由其他函数返回。在支持一等公民函数的语言(如Python)中,函数的行为就像任何其他的变量或对象一样。这是因为函数本身就是一种对象(在Python中是function对象)。
-
函数可以被赋值:
意味着你可以将函数赋值给一个变量,然后通过这个变量来使用这个函数。示例:
def hello(): return "Hello, World!" #将函数hello赋值给变量greet greet = hello print(greet()) # 输出: "Hello, World!"
-
函数可以作为参数传递:
某个函数可以接受另外一个函数作为参数。这在构建某些具有高级功能的函数时非常有用,比如装饰器。示例:
def greet(func): print(func()) def hello(): return "Hello, World!" greet(hello) # 输出: "Hello, World!"
-
函数可以作为返回值:
函数可以返回另一个函数。这在构建一些动态行为的函数时非常有用。示例:
def wrapper(): def inner(): return "Hello, World!" return inner greet = wrapper() print(greet()) # 输出: "Hello, World!"
-
函数可以存储在数据结构中:
函数可以作为列表或字典等数据结构的一部分存储和处理。示例:
def hello():
return "Hello, World!"
def hi():
return "Hi, World!"
func_list = [hello, hi] #将函数存储在list中
for func in func_list:
print(func())
# 输出:
# "Hello, World!"
# "Hi, World!"
2.高阶函数:
1.作为参数的函数:
这个例子中,我们定义了一个 apply_to_list()
函数,它接受一个函数 f
和一个列表 l
,然后使用 f
函数来处理 l
列表中的每一个元素:
def apply_to_list(f, l):
return [f(x) for x in l]
现在,你可以将任何你想要的函数(只要它接受一个参数)传递给 apply_to_list()
,并且让那个函数应用到列表的元素上。如:
def square(x):
return x ** 2
numbers = [1, 2, 3, 4, 5]
squares = apply_to_list(square, numbers)
print(squares) # 输出 [1, 4, 9, 16, 25]
2.返回一个函数:
在这个例子中,我们定义了一个名为 make_multiplier_of()
的函数。这个函数接收一个参数 n
,然后返回一个新的函数,这个新的函数会把它的输入乘以 n
:
def make_multiplier_of(n):
def multiplier(x):
return x * n
return multiplier
现在,你可以创建并使用新的“乘法器”函数,例如:
times_two = make_multiplier_of(2)
print(times_two(5)) # 输出 10
times_three = make_multiplier_of(3)
print(times_three(5)) # 输出 15
这个例子也展示了闭包的一个重要概念:内部函数 multiplier
记住了外部函数 make_multiplier_of
的参数 n
的值,即使外部函数已经完成了执行。
3.装饰器:
1.什么是装饰器:
装饰器(decorator)是一种特殊类型的函数,它允许用户在不更改函数源代码的情况下,向函数中添加额外的功能。简单来说,它可以用于装饰或包裹另一个函数,并动态地修改那个被包裹的函数的行为。装饰器的核心原理是闭包和高阶函数。
装饰器可以通过 Python 的语法糖 @ 符号来简洁地使用,如:@decorator
def my_decorator(func):
def wrapper():
print("Before function call")
func()
print("After function call")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
结果为:
Before function call
Hello!
After function call
在这个例子中,我们定义了一个装饰器 my_decorator
,它接收一个函数作为参数,并返回一个新的函数 wrapper
,wrapper
函数先打印一条消息,然后调用原始函数,然后再打印一条消息。于是,当我们在 say_hello
函数前面使用 @my_decorator
标记并调用 say_hello()
时,我们实际上是在调用被装饰后的函数。
2.带参数的装饰器:
我们先来看一下这个例子:
代码块1:
def log(level):
def wrapper(func):
print(f'[{level}]:{func.__name__}')
def wrapper_inner(*args,**kwargs):
result = func(*args,**kwargs)
return result
return wrapper_inner
return wrapper
@log('INFO')
def print_info(name, age, gender):
print(f'name:{name}, age:{age}, gender:{gender}')
return name, age, gender
print_info('anna', '20', 'female')
代码块2:
def log(level):
def wrapper(func):
print(f'[{level}]:{func.__name__}')
def wrapper_inner(*args,**kwargs):
return func(*args,**kwargs)
return wrapper_inner
return wrapper
def print_info(name, age, gender):
print(f'name:{name}, age:{age}, gender:{gender}')
return name, age, gender
wrapper = log('INFO')
inner = wrapper(print_info)
print(inner('anna', '20', 'female'))
这两个代码块输出结果均相同,为:
[INFO]:print_info
name:anna, age:20, gender:female
('anna', '20', 'female')
代码块2中的后半部分实际上是代码块1中装饰器的运行逻辑。装饰器函数 wrapper
接收一个函数 func
作为参数,然后创建并返回一个新的内嵌函数 wrapper_inner
。这个新的内嵌函数 wrapper_inner
接收任意数量的位置参数和关键字参数,调用原始的 func
函数并返回其结果。
而在 print_info
函数定义前面,我们用 @
符号应用了 log
函数返回的装饰器。实际上,@log('INFO')
的作用相当于 print_info = log('INFO')(print_info)
。这行代码的意思是,先调用 log('INFO')
创建一个装饰器,然后用这个装饰器包装 print_info
函数,并且将包装后的函数赋值回 print_info
。于是,后续当我们调用 print_info
函数的时候,实际上调用的是装饰器函数 wrapper
创建的 wrapper_inner
函数。
3.基于类的装饰器:
class Log:
def __init__(self,level):
self.level = level
def __call__(self, func):
def wrapper(*args,**kwargs):
print(f'[{self.level}]:function_name:{func.__name__}-[{args,kwargs}]')
return func(*args,**kwargs)
return wrapper
@Log(level='INFO')
def say_hello(name,age,gender):
print(f'name:{name},age:{age},gender:{gender},hello!')
say_hello('anna','19',gender='female')
在这段代码中,Log
是基于类的装饰器。在它的初始化方法__init__
中,它接收一个等级参数level
。当我们执行@Log(level='INFO')
时,我们实际上是创建了一个新的Log实例,传入level
参数为INFO
,也就是Log('INFO')
。
定义的__call__
方法接收一个函数func
作为参数,内部定义了新的嵌套函数wrapper
,并将其返回。这个wrapper
函数接收任何输入参数,打印一条消息,然后调用原始函数func
。注意这里wrapper
函数内部可以访问到self.level
和func
的值,形成了闭包。
当我们使用@Log(level='INFO')
装饰一个函数,比如say_hello
时,这实际上等同于执行了say_hello = Log('INFO')(say_hello)
。也就是利用Log('INFO')
创建的实例去“调用”了say_hello
函数,返回的结果再赋值给say_hello
。
所以后续我们调用say_hello('anna','19',gender='female')
时,实际上先执行的是Log
实例的__call__
方法,即wrapper
函数,它输出了一条信息,并调用了原始函数。这样我们就在不修改原始函数的情况下,给其添加了新的功能。
(__call__
方法的使用:简单来说__call__
方法是在实例对象后打上()后,会自动调用此方法。以下是代码示例:
class Hello:
def __call__(self, name):
return f"Hello, {name}!"
say_hello = Hello()
print(say_hello("John")) # 输出:Hello, John!
在这里,我们定义了一个名为Hello的类,它有一个__call__
方法,这个方法会返回一个问候语。然后,我们创建了一个Hello类的实例say_hello
,注意这个时候,__call__
并没有被执行。直到我们像调用函数一样调用这个实例(say_hello("John")
),__call__
方法才被运行。)
4.@property
在Python中,@property.setter装饰器的优先级高于@property装饰器。当一个方法同时被@property.setter和@property装饰器修饰时,@property.setter装饰器的优先级更高,它将被视为属性的设置方法。这意味着,当您使用@property.setter装饰器定义了一个属性的设置方法时,它将覆盖由@property装饰器定义的同名方法,从而提供自定义的设置行为。
例如以下代码示例:
class Goods:
def __init__(self):
self._good_price = 100
self.discount = 0.8
@property
def price(self):
new_price = self._good_price * self.discount
return new_price
@price.setter
def price(self, value): #调用了属性设置方法
self._good_price = value
@price.deleter
def price(self):
self.discount = 1
good = Goods() # 实例化
print(good.price) # 输出:80.0
good.price = 200 #调用了@price.setter
print(good.price) # 输出:160.0
del good.price #调用了@price.deleter
print(good.price)