news 2026/4/17 21:05:05

Python_迭代器、生成器、装饰器、闭包

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python_迭代器、生成器、装饰器、闭包

迭代器

引入

如果开发中有以下需求,如何解决?

class StuSystem(object): """`在这里插入代码片` 学生管理系统 """ def __init__(self): self.stus = [] def add(self): """ 添加一个新的学生 :return: """ name = input("请输入新学生的姓名:") tel = input("请输入新学生的手机号:") address = input("请输入新学生的住址:") new_stu = dict() new_stu["name"] = name new_stu["tel"] = tel new_stu["address"] = address self.stus.append(new_stu) # 创建管理系统对象 stu_sys = StuSystem() # 添加3个学生信息到系统中 stu_sys.add() stu_sys.add() stu_sys.add() # 问题1:怎样才能实现用for循环遍历系统中所有的学生信息呢?下面的方式能实现吗? for temp in stu_sys: print(temp) # 问题2:如果需要一个列表,这个列表 样子例如 [{'name': '张三', 'id': 10086}, {'name': '李四': 'id': 10087}] # stu_list = [ ...列表推导式...] # 这个列表推导式该怎样写才能实现呢?

在实际开发工作中,经常需要快速的将对象转化问其他的不同的数据类型,此时如果能快速的遍历出自定义的对象,这样大大减少代码的冗余,而且可读性会更优美。问题是,怎样实现呢?
以上问题我们可以使用迭代器完成。

迭代概述

迭代是访问序列类型元素的一种方式。

nums = [11, 22, 33] # 可以通过for循环将nums列表中的每个数据依次获取 for num in nums: print(num) name = "teacher" for temp in name: print(temp)

我们已经知道可以对list、tuple、str等类型的数据使用for…in…的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代

可迭代对象

是否所有的数据类型都可以放到for…in…的语句中,然后让for…in…每次从中取出一条数据供我们使用呢?

weight = 160 for item in weight: print(item) # 报错

通过运行可以发现,并不是所有的类型都可以通过for…in…的方式进行遍历。我们可以通俗的认为:只要是可以通过for…in…的形式进行遍历的,那么这个数据类型就是可以迭代的。

  • 可以迭代的数据类型:列表、元组、字典、字符串
  • 不可迭代的数据类型:整型、浮点型、布尔类型

那是否可以通过某种方式能够测量出一个数据类型到底是否是可以迭代呢?

In [50]: from collections.abc import Iterable In [51]: isinstance([], Iterable) Out[51]: True In [52]: isinstance({}, Iterable) Out[52]: True In [53]: isinstance('abc', Iterable) Out[53]: True In [54]: isinstance(mylist, Iterable) Out[54]: False In [55]: isinstance(100, Iterable) Out[55]: False

只要是通过isinstance来判断出是Iterable类的实例,即isinstance的结果是True,那么就表示这个数据类型是可以迭代的数据类型。

迭代器概述

迭代器是一个可以记住遍历的位置的对象。迭代器对象从第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

可迭代对象的本质

分析可迭代对象进行迭代的过程,发现每迭代一次(即在for…in…中每循环一次)都会返回对象中的下一条数据,一直向后读取数据直到迭代了所有数据后结束。那么,在这个过程中就应该有一个人去记录每次访问到了第几条数据,以便每次迭代都可以返回下一条数据。我们把这个能帮助我们进行数据迭代的人称为迭代器(Iterator)。可迭代对象的本质就是可以向我们提供一个这样的中间人,即迭代器帮助我们对其进行迭代遍历使用。list、tuple等都是可迭代对象,我们可以通过iter()函数获取这些可迭代对象的迭代器,然后我们可以对获取到的迭代器不断使用next()函数来获取下一条数据。

获取可迭代对象的迭代器

from collections.abc import Iterator nums = [11, 22, 33, 44] print(type(nums)) nums_iter = iter(nums) print(type(nums_iter)) print("nums", isinstance(nums, Iterator)) print("nums_iter", isinstance(nums_iter, Iterator))

获取迭代器的数据

上面提到,通过iter()能够得到一个可迭代对象的迭代器,可以通过next()函数多次提取迭代器中的数据,让我们完成代码测试。
测试代码如下:

from collections.abc import Iterator nums = [11, 22, 33, 44] nums_iter = iter(nums) print("nums", isinstance(nums, Iterator)) print("nums_iter", isinstance(nums_iter, Iterator)) num1 = next(nums_iter) print(num1) num2 = next(nums_iter) print(num2) num3 = next(nums_iter) print(num3) num4 = next(nums_iter) print(num4)

StopIteration异常

如果将上面的代码,多写一次的next()会怎样呢?看如下测试代码:

from collections.abc import Iterator nums = [11, 22, 33, 44] nums_iter = iter(nums) print("nums", isinstance(nums, Iterator)) print("nums_iter", isinstance(nums_iter, Iterator)) num1 = next(nums_iter) print(num1) num2 = next(nums_iter) print(num2) num3 = next(nums_iter) print(num3) num4 = next(nums_iter) print(num4) num5 = next(nums_iter) # 这里会产生异常 print(num5)

可以看到第二十三行,即第五次调用next()时,产生了异常。因为列表nums中只有四个数据,也就是说可以调用四次next是完全合理的,但是如果调用的次数多了肯定是不合理,都没有五个数据,怎么可以能取五次呢!显然是不对的。当前产生的异常其实就是一种告知迭代结束的标志而已。添加try…except…即可解决刚刚遇到的问题。

try: num5 = next(nums_iter) print(num5) except StopIteration as e: print(f'迭代结束: {e}')

自定义迭代器

大家是否还记得在刚开始学习今天的知识时,我们引入了一个学生管理系统的问题,该怎样实现呢?
我们下面来谈谈:

  • __iter__方法
  • __next__方法

__iter__方法

上面提到iter()方法必须是可迭代对象才能提取到迭代器对象,但是怎样保证自定义的对象是可迭代对象呢?
答:只要在类中定义__iter__方法,那么这个类创建出来的对象一定是可迭代对象。

无__iter__方法

from collections.abc import Iterable class MyList(object): def __init__(self): self.container = [] def add(self, item): self.container.append(item) mylist = MyList() mylist.add(11) mylist.add(22) mylist.add(33) print("mylist是否是可以迭代对象", isinstance(mylist, Iterable)) for temp in mylist: print(temp)

运行结果:

mylist是否是可以迭代对象 False Traceback (most recent call last): File "/home/ubuntu/Desktop/stu_code/测试代码.py", line 19, in <module> for temp in mylist: TypeError: 'MyList' object is not iterable

有__iter__方法

from collections.abc import Iterable class MyList(object): def __init__(self): self.container = [] def add(self, item): self.container.append(item) def __iter__(self): pass mylist = MyList() mylist.add(11) mylist.add(22) mylist.add(33) print("mylist是否是可以迭代对象", isinstance(mylist, Iterable)) for temp in mylist: print(temp)

运行结果:

mylist是否是可以迭代对象 True Traceback (most recent call last): File "/home/ubuntu/Desktop/stu_code/测试代码.py", line 21, in <module> for temp in mylist: TypeError: iter() returned non-iterator of type 'NoneType'

能够看出一个类只要有__iter__方法,那么这个类创建出来的对象就是可以迭代对象。其实,当我们调用iter()函数提取一个可迭代对象的迭代器时,实际上会自动调用这个对象的__iter__方法,并且这个方法返回迭代器。

__next__方法

通过上面的分析,我们已经知道,迭代器是用来帮助我们记录每次迭代访问到的位置,当我们对迭代器使用next()函数的时候,迭代器会向我们返回它所记录位置的下一个位置的数据。实际上,在使用next()函数的时候,调用的就是迭代器对象的__next__方法(Python3中是对象的__next__方法,Python2中是对象的next()方法)。
所以,我们要想构造一个迭代器就要实现它的__next__方法。但这还不够,Python要求迭代器本身也是可迭代的,所以我们还要为迭代器实现__iter__方法,而__iter__方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的__iter__方法返回自身即可。一个实现了__iter__方法和__next__方法的对象,就是迭代器。

如何判断一个对象是否是迭代器?
可以使用isinstance()判断一个对象是否是Iterator对象:

In [56]: from collections.abc import Iterator In [57]: isinstance([], Iterator) Out[57]: False In [58]: isinstance(iter([]), Iterator) Out[58]: True In [59]: isinstance(iter("abc"), Iterator) Out[59]: True

自定义迭代器

from collections.abc import Iterable from collections.abc import Iterator class MyList(object): """自定义的一个可迭代对象""" def __init__(self): self.items = [] def add(self, val): self.items.append(val) def __iter__(self): return MyIterator() class MyIterator(object): """自定义的供上面可迭代对象使用的一个迭代器""" def __init__(self): pass def __next__(self): pass def __iter__(self): pass mylist = MyList() mylist_iter = iter(mylist) print("mylist是否是可以迭代对象", isinstance(mylist, Iterable)) print("mylist是否是迭代器", isinstance(mylist, Iterator)) print("mylist_iter是否是可以迭代对象", isinstance(mylist_iter, Iterable)) print("mylist_iter是否是迭代器", isinstance(mylist_iter, Iterator))

运行结果:

mylist是否是可以迭代对象 True mylist是否是迭代器 False mylist_iter是否是可以迭代对象 True mylist_iter是否是迭代器 True

自定义迭代器案例

class MyList(object): """自定义的一个可迭代对象""" def __init__(self): self.items = [] def add(self, val): self.items.append(val) def __iter__(self): myiterator = MyIterator(self) return myiterator class MyIterator(object): """自定义的供上面可迭代对象使用的一个迭代器""" def __init__(self, mylist): self.mylist = mylist # current用来记录当前访问到的位置 self.current = 0 def __next__(self): if self.current < len(self.mylist.items): item = self.mylist.items[self.current] self.current += 1 return item else: raise StopIteration def __iter__(self): return self if __name__ == '__main__': mylist = MyList() mylist.add(1) mylist.add(2) mylist.add(3) mylist.add(4) mylist.add(5) for num in mylist: print(num)

运行结果:

1 2 3 4 5

可迭代对象通过__iter__方法向我们返回一个迭代器。我们在迭代一个可迭代对象的时候,实际上就是先获取该对象提供的一个迭代器,然后通过这个迭代器来依次获取对象中的每一个数据。

循环执行迭代器

  1. 先调用iter()函数,它会自动调用可迭代对象中的__iter__方法,此方法返回这个可迭代对象的迭代器对象
  2. 对获取到的迭代器不断调用next()函数,它会自动调用迭代器中的__next__方法来获取下一个值
  3. 当遇到StopIteration异常后循环结束

迭代器对象转容器类型

除了for循环能接收可迭代对象,list、tuple等也能接收。
测试代码如下:

class MyList(object): """自定义的一个可迭代对象""" def __init__(self): self.items = [] def add(self, val): self.items.append(val) def __iter__(self): myiterator = MyIterator(self) return myiterator class MyIterator(object): """自定义的供上面可迭代对象使用的一个迭代器""" def __init__(self, mylist): self.mylist = mylist # current用来记录当前访问到的位置 self.current = 0 def __next__(self): if self.current < len(self.mylist.items): item = self.mylist.items[self.current] self.current += 1 return item else: raise StopIteration def __iter__(self): return self if __name__ == '__main__': mylist = MyList() mylist.add(1) mylist.add(2) mylist.add(3) mylist.add(4) mylist.add(5) nums = list(mylist) print(nums)

运行结果:

[1, 2, 3, 4, 5]

简单总结

  • 凡是可作用于for循环的对象都是Iterable 类型
  • 凡是可作用于 next() 函数的对象都是Iterator 类型
  • 序列数据类型如list 、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象

随堂作业

既然已经学习过了迭代器,那么今天刚开始的知识点也就自然有了答案。实现用for循环遍历学生系统中的所有学生信息:

class StuSystem(object): """ 学生管理系统 """ def __init__(self): self.stus = [] self.current_num = 0 def add(self): """ 添加一个新的学生 :return: """ name = input("请输入新学生的姓名:") tel = input("请输入新学生的手机号:") address = input("请输入新学生的住址:") new_stu = dict() new_stu["name"] = name new_stu["tel"] = tel new_stu["address"] = address self.stus.append(new_stu) def __iter__(self): return self def __next__(self): if self.current_num < len(self.stus): ret = self.stus[self.current_num] self.current_num += 1 return ret else: # self.current_num = 0 当前代码可省略 raise StopIteration # 创建管理系统对象 stu_sys = StuSystem() # 添加3个学生信息到系统中 stu_sys.add() stu_sys.add() stu_sys.add() # 问题1:怎样才能实现用for循环遍历系统中所有的学生信息呢?下面的方式能实现吗? for temp in stu_sys: print(temp)

运行结果:

(base) ubuntu@VM-16-5-ubuntu:~/Desktop/stu_code$ /home/ubuntu/miniconda3/bin/python /home/ubuntu/Desktop/stu_code/测试代码.py 请输入新学生的姓名:顾安 请输入新学生的手机号:13711111111 请输入新学生的住址:长沙 请输入新学生的姓名:安娜 请输入新学生的手机号:13711111112 请输入新学生的住址:长沙 请输入新学生的姓名:双双 请输入新学生的手机号:13711111113 请输入新学生的住址:长沙 {'name': '顾安', 'tel': '13711111111', 'address': '长沙'} {'name': '安娜', 'tel': '13711111112', 'address': '长沙'} {'name': '双双', 'tel': '13711111113', 'address': '长沙'}
  • 对输入的数据进行格式转换

    class StuSystem:
    “”"
    学生管理系统
    “”"
    definit(self):
    self.stus = []
    self.current_num = 0

    def add(self): """ 添加一个新的学生 :return: """ name = input("请输入新学生的姓名:") tel = input("请输入新学生的手机号:") address = input("请输入新学生的住址:") new_stu = dict() new_stu["name"] = name new_stu["tel"] = tel new_stu["address"] = address self.stus.append(new_stu) def __iter__(self): return self def __next__(self): if self.current_num < len(self.stus): ret = self.stus[self.current_num] self.current_num += 1 return ret else: self.current_num = 0 raise StopIteration

    stu_sys = StuSystem()

    stu_sys.add()
    stu_sys.add()
    stu_sys.add()

    stu_list = [x for x in stu_sys]
    print(stu_list)

运行结果:

请输入新学生的姓名:顾安 请输入新学生的手机号:13711111111 请输入新学生的住址:长沙 请输入新学生的姓名:安娜 请输入新学生的手机号:13711111112 请输入新学生的住址:南京 请输入新学生的姓名:双双 请输入新学生的手机号:13711111113 请输入新学生的住址:上海 [{'name': '顾安', 'tel': '13711111111', 'address': '长沙'}, {'name': '安娜', 'tel': '13711111112', 'address': '南京'}, {'name': '双双', 'tel': '13711111113', 'address': '上海'}]

生成器

概述

在Python中,使用生成器可以很方便的支持迭代器协议。生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用return返回,而是用yield一次返回一个结果,在每个结果之间挂起和继续它们的状态,来自动实现迭代协议。也就是说,yield是一个语法糖,内部实现支持了迭代器协议,同时yield内部是一个状态机,维护着挂起和继续的状态。

def my_range(n): i = 0 while i < n: yield i i += 1 my_range = my_range(3) print(my_range) print(next(my_range)) # print([i for i in my_range])

在这个例子中,定义了一个生成器函数,函数返回一个生成器对象,然后就可以通过for语句进行迭代访问了。其实,生成器函数返回生成器的迭代器。 生成器的迭代器这个术语通常被称作生成器。要注意的是生成器就是一类特殊的迭代器。作为一个特殊的迭代器,生成器必须要定义一些方法,其中一个就是__next__()。如同迭代器一样,我们可以使用__next__()函数来获取下一个值。

生成器工作流程

下面就仔细看看生成器是怎么工作的。从上面的例子也可以看到,生成器函数跟普通的函数是有很大差别的。结合上面的例子我们加入一些打印信息,进一步看看生成器的执行流程:

def my_range(n): print('开始迭代...') i = 0 while i < n: print('迭代中...') yield i i += 1 print('迭代结束...') my_range = my_range(3) # print(my_range) print(next(my_range)) print(next(my_range)) print(next(my_range))

通过结果可以看到:

  1. 当调用生成器函数的时候,函数只是返回了一个生成器对象,并没有执行。
  2. 当next()方法第一次被调用的时候,生成器函数才开始执行,执行到yield语句处停止: next()方法的返回值就是yield语句处的参数(yielded value) 。
  3. 当继续调用next()方法的时候,函数将接着上一次停止的yield语句处继续执行,并到下一个yield处停止。如果后面没有yield value就抛出StopIteration异常。

生成器表达式

在开始介绍生成器表达式之前,先看看我们比较熟悉的列表解析(List comprehensions),列表解析一般都是下面的形式。

[expr for iter_var in iterable if cond_expr]

迭代iterable里所有内容,每一次迭代后,把iterable里满足cond_expr条件的内容放到iter_var中,再在表达式expr中应该iter_var的内容,最后用表达式的计算值生成一个列表。

例如,生成一个list包含50以内的所有奇数:

[i for i in range(50) if i % 2]

生成器表达式是在Python2.4中引入的,当序列过长, 而每次只需要获取一个元素时,应当考虑使用生成器表达式而不是列表解析。生成器表达式的语法和列表解析一样,只不过生成器表达式是被()括起来的,而不是[],如下:

(expr for iter_var in iterable if cond_expr)

生成器表达式并不是创建一个列表, 而是返回一个生成器,这个生成器在每次计算出一个条目后,把这个条目yield出来。 生成器表达式使用了惰性计算(lazy evaluation),只有在检索时才被赋值,所以在列表比较长的情况下使用会节约内存。

gen = (i for i in range(10000) if i % 2) print("__iter__" in dir(gen)) print("__next__" in dir(gen)) # 使用sum求和之后会导致下次迭代所获取的值为空 print(sum(gen)) print([i for i in gen])

生成器中的send()与close()方法

生成器中还有两个很重要的方法:send()和close()。

  • send(value):从前面了解到,next()方法可以恢复生成器状态并继续执行,其实send()是除next()外另一个恢复生成器的方法。Python 2.5中,yield语句变成了yield表达式,也就是说yield可以有一个值,而这个值就是send()方法的参数,所以send(None)和next()是等效的。同样,next()和send()的返回值都是yield语句处的参数(yielded value)。关于send()方法需要注意的是:调用send传入非None值前,生成器必须处于挂起状态,否则将抛出异常。也就是说,第一次调用时,要使用next()语句或send(None),因为没有yield语句来接收这个值。
  • close():这个方法用于关闭生成器,对关闭的生成器后再次调用next或send将抛出StopIteration异常。

代码案例:

def my_range(n): i = 0 while i < n: val = yield i print('val is: ', val) i += 1 my_range = my_range(5) print(my_range.__next__()) print(my_range.__next__()) print(my_range.send('hello')) my_range.close() print(my_range.send('world'))

总结:

  • 生成器是一种特殊的迭代器,内部支持了生成器协议,不需要明确定义__iter__()和__next__()方法。
  • 生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用return返回,而是用yield一次返回一个结果。

装饰器

概述

装饰器是python语言中的语法糖,可以通过装饰器对函数的功能进行拓展。

为什么需要装饰器

我们假设你的程序实现了say_hello()和say_goodbye()两个函数。

def say_hello(): print("hello!") def say_goodbye(): print("hello!") # 此处应打印goodbye if __name__ == '__main__': say_hello() say_goodbye()

假设上述代码中的say_goodbye函数出现了bug,为了之后能更好的维护,现在需要在调用方法前记录函数调用名称,以定位出错位置。

[DEBUG]: Enter say_hello() Hello! [DEBUG]: Enter say_goodbye() Goodbye!

实现方式:

def say_hello(): print("[DEBUG]: enter say_hello()") print("hello!") def say_goodbye(): print("[DEBUG]: enter say_goodbye()") print("hello!") if __name__ == '__main__': say_hello() say_goodbye()

对上述代码进行优化:

def debug(): import inspect caller_name = inspect.stack()[1][3] # 返回函数名 print("[DEBUG]: enter {}()".format(caller_name)) def say_hello(): debug() print("hello!") def say_goodbye(): debug() print("goodbye!") if __name__ == '__main__': say_hello() say_goodbye()

上述代码需要在每个业务函数里都要调用一下debug()函数,是不是很难受?万一老板说say相关的函数不用debug,do相关的才需要呢?

那么装饰器这时候应该登场了。

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

概括的讲,装饰器的作用就是:为已经存在的函数或对象添加额外的功能。

如何实现一个装饰器

在早些时候(Python Version < 2.4,2004年以前),为一个函数添加额外功能的写法是这样的。

def debug(func): def wrapper(): print("[DEBUG]: enter {}()".format(func.__name__)) return func() return wrapper def say_hello(): print("hello!") say_hello = debug(say_hello) say_hello()

上面的debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖,下面代码等同于早期的写法。

def debug(func): def wrapper(): print("[DEBUG]: enter {}()".format(func.__name__)) return func() return wrapper @debug def say_hello(): print("hello!") say_hello()

这是最简单的装饰器,但是有一个问题,如果被装饰的函数需要传入参数,那么这个装饰器就无法正常使用。因为返回的函数并不能接收参数,你可以指定装饰器函数wrapper接收和原函数一样的参数:

def debug(func): def wrapper(something): # 指定一毛一样的参数 print("[DEBUG]: enter {}()".format(func.__name__)) return func(something) return wrapper # 返回包装过函数 @debug def say(something): print("hello {}!".format(something)) say('顾安')

这样你就解决了一个问题,但又多了若干个问题。因为函数有千千万,你只管你自己的函数,别人的函数参数是什么样子,鬼知道?还好Python提供了可变参数*args和关键字参数**kwargs,有了这两个参数,装饰器就可以用于任意目标函数了。

def debug(func): def wrapper(*args, **kwargs): print("[DEBUG]: enter {}()".format(func.__name__)) return func(*args, **kwargs) return wrapper @debug def say(something): print("hello {}!".format(something)) say('顾安')

带参数的装饰器

假设我们前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出log信息,而且还需指定log的级别,那么装饰器就会是这样的。

def logging(level): def wrapper(func): def inner_wrapper(*args, **kwargs): print("[{level}]: enter function {func}()".format( level=level, func=func.__name__)) return func(*args, **kwargs) return inner_wrapper return wrapper @logging(level='INFO') def say(something): print("say {}!".format(something)) # 如果没有使用@语法,等同于 # say = logging(level='INFO')(say) @logging(level='DEBUG') def do(something): print("do {}...".format(something)) if __name__ == '__main__': say('hello') do("my work")

是不是有一些晕?你可以这么理解,当带参数的装饰器被打在某个函数上时,比如@logging(level=‘DEBUG’),它其实是一个函数,会马上被执行,只要它返回的结果是一个装饰器时,那就没问题。细细再体会一下。

基于类的装饰器

装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重载了__call__()方法,那么这个对象就是callable的。

class Test: def __call__(self): print('call me!') t = Test() t() # call me

像__call__这样前后都带下划线的方法在Python中被称为内置方法,有时候也被称为魔法方法。重载这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个类对象拥有了被调用的行为。回到装饰器上的概念上来,装饰器要求接受一个callable对象,并返回一个callable对象。那么用类来实现也是也可以的。我们可以让类的构造函数__init__()接受一个函数,然后重载__call__()并返回一个函数,也可以达到装饰器函数的效果。

class Logging: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print("[DEBUG]: enter function {func}()".format( func=self.func.__name__)) return self.func(*args, **kwargs) @Logging def say(something): print("say {}!".format(something)) say('木木')

带参数的类装饰器

如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。那么在构造函数里接收的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后在重载__call__方法是就需要接收一个函数并返回一个函数。

class Logging: def __init__(self, level='INFO'): self.level = level def __call__(self, func): # 接收函数 def wrapper(*args, **kwargs): print("[{level}]: enter function {func}()".format( level=self.level, func=func.__name__)) func(*args, **kwargs) return wrapper # 返回函数 @Logging(level='INFO') def say(something): print("say {}!".format(something)) say('木木')

总结

装饰器是Python的一个非常强大而又灵活的功能,它允许程序员在不修改原始函数或类代码的情况下,为函数或类增加新的功能。装饰器实际上是一个函数,它接收一个函数作为参数并返回一个新的函数。使用装饰器可以在代码执行过程中动态地增加功能,这使得装饰器在很多情况下非常有用。装饰器的作用有很多,包括但不限于以下几点:

  1. 增加函数功能
  2. 代码复用
  3. 权限校验
  4. 缓存与延迟计算
  5. 参数类型检查

闭包

引入

想想看怎样用程序实现下面的功能呢?在一个聊天软件中显示是谁发送了这条信息,即:一条信息标记了是谁发送的。今天我们要研究的知识点是闭包,实现上述功能的方式可能有多种,但是闭包会更简单。

问题解决

普通方式

def say(user_name, content): print("(%s):%s" % (user_name, content)) user_name1 = "安娜" user_name2 = "双双" say(user_name1, "今天吃了么?") say(user_name2, "吃了~") say(user_name1, "吃了啥?") say(user_name2, "半只牛~") say(user_name1, "为啥不给我吃?") say(user_name2, "我一个人刚刚好~~") say(user_name1, "友谊的小船说翻就翻!") say(user_name2, "我会游泳~~~")

运行效果如下:

(安娜):今天吃了么? (双双):吃了~ (安娜):吃了啥? (双双):半只牛~ (安娜):为啥不给我吃? (双双):我一个人刚刚好~~ (安娜):友谊的小船说翻就翻! (双双):我会游泳~~~

总结:上述代码已经实现了要求,但是每次发送信息时需要将用户名称传递到say函数中,相对比较麻烦

面向对象的方式解决上述问题

class Person(object): def __init__(self, name): self.user_name = name def say(self, content): print("(%s):%s" % (self.user_name, content)) p1 = Person("安娜") p2 = Person("双双") p1.say("今天吃了么?") p2.say("吃了~") p1.say("吃了啥?") p2.say("半只牛~") p1.say("为啥不给我吃?") p2.say("我一个人刚刚好~~") p1.say("友谊的小船说翻就翻!") p2.say("我会游泳~~~")

运行结果:

(安娜):今天吃了么? (双双):吃了~ (安娜):吃了啥? (双双):半只牛~ (安娜):为啥不给我吃? (双双):我一个人刚刚好~~ (安娜):友谊的小船说翻就翻! (双双):我会游泳~~~

总结:通过面向对象的方式能够实现上述要求,但是发现使用了类以及对象,总体感觉还是较为复杂,再者说继承的object类中有很多默认的方法,既然这个程序不需要,显然会造成一定的浪费。

使用闭包解决上述问题

def person(name): def say(content): print("(%s):%s" % (name, content)) return say p1 = person("安娜") p2 = person("双双") p1("今天吃了么?") p2("吃了~") p1("吃了啥?") p2("半只牛~") p1("为啥不给我吃?") p2("我一个人刚刚好~~") p1("友谊的小船说翻就翻!") p2("我会游泳~~~")

函数引用

# 定义函数可以理解为: # 定义了一个全局变量,其变量名字是函数的名字,即test # 这个test变量指向了一个代码块,这个代码块是函数 # 其实就是说test保存了一个代码块的地址,即引用 def test(): print("--- in test func----") test() # 这是调用函数 ret = test # 用另外一个变量 复制了 test这个引用,导致ret变量也指向那个 函数代码块 # 下面输出的2个地址信息是相同的 print(id(ret)) print(id(test)) # 通过引用调用函数 ret()

运行结果:

--- in test func---- 140212571149040 140212571149040 --- in test func----

闭包的概念

闭包(closure)定义非常抽象,很难看懂。下面尝试从概念上去理解一下闭包:在一些语言中,在函数中可以嵌套定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组私有变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。 —— 维基百科https://zh.wikipedia.org/wiki/闭包_(计算机科学)
用比较容易懂的人话说:就是当某个函数被当成对象返回时,夹带了外部变量,就形成了一个闭包。可以这样理解,闭包就是能够读取其他函数内部变量的函数。

看如下案例,便于理解什么是闭包:

def make_printer(msg): # 可以认为是 外部函数 def printer(): # 可以认为是 内部函数 print(msg) return printer # 返回的内部函数的引用 printer = make_printer('Good!') printer()

运行结果:

Good

闭包案例

def test(number): def test_in(number_in): print("in test_in 函数, number_in is %d" % number_in) return number + number_in return test_in # 给test函数赋值,这个20就是给参数number ret = test(20) # 注意这里的100其实给参数number_in print(ret(100)) # 注意这里的200其实给参数number_in print(ret(200))

运行结果:

in test_in 函数, number_in is 100 120 in test_in 函数, number_in is 200 220

使用闭包需要注意的问题

由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。因此可以手动解除对匿名函数的引用,以便释放内存。

def make_closure(): # 一个大的变量,比如一个大列表 big_list = list(range(100000)) def the_closure(): return sum(big_list) return the_closure # 创建一个闭包 closure = make_closure() # 闭包不再需要时,可以删除引用来释放内存 closure = None # 或者使用 del closure

使用闭包修改外部函数中的变量

def counter(start=0): def add_one(): nonlocal start # nonlocal 关键字用于在嵌套函数内部使用变量,其中变量不应属于内部函数。 start += 1 return start return add_one c1 = counter(5) # 创建一个闭包 print(c1()) print(c1()) c2 = counter(50) # 创建另外一个闭包 print(c2()) print(c2()) print(c1()) print(c1()) print(c2()) print(c2())

运行结果:

6 7 51 52 8 9 53 54

多个闭包

如上面的代码中,调用了两次counter,也就意味着创建了两个闭包,并且每个闭包之间没有任何关系。
大家是否有种感觉,好像闭包与对象有些类似。确实是这样的,对象其实可通俗的理解为数据(属性) + 功能(方法),而闭包也可以理解为数据 + 功能,只不过此时数据是外部函数中的那些局部变量或者形参,而功能则是内部函数。对象适合完成较为复杂的功能,而闭包则更轻量。

闭包总结

  1. 闭包定义是在函数内再嵌套函数
  2. 闭包是可以访问另一个函数局部作用域中变量的函数
  3. 闭包可以读取另外一个函数内部的变量
  4. 闭包可以让参数和变量不会被垃圾回收机制回收,始终保持在内存中(而普通的函数调用结束后会被Python解释器自动释放局部变量)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 23:18:18

Python线程、队列、生产者与消费者、线程池

线程 线程概念 我们在日常开发中经常会听到使用多线程/多进程的方式完成并发任务。那么什么是进程&#xff1f;什么是线程&#xff1f;进程与线程之间有什么关系&#xff1f;接下来我们通过日常场景简单的了解一下进程与线程。 一个工厂&#xff0c;至少有一个车间&#xff…

作者头像 李华
网站建设 2026/4/17 1:04:35

科哥出品CAM++镜像,让AI声纹识别开箱即用

科哥出品CAM镜像&#xff0c;让AI声纹识别开箱即用 1. 为什么你需要一个“开箱即用”的声纹识别系统&#xff1f; 你有没有遇到过这些场景&#xff1a; 想快速验证一段录音是不是某位同事说的&#xff0c;但翻遍GitHub找不到能直接跑起来的模型&#xff1f;在做智能门禁原型…

作者头像 李华
网站建设 2026/4/16 11:35:23

如何突破文件预览困境?浏览器预览解决方案让办公效率提升300%

如何突破文件预览困境&#xff1f;浏览器预览解决方案让办公效率提升300% 【免费下载链接】kkFileView Universal File Online Preview Project based on Spring-Boot 项目地址: https://gitcode.com/GitHub_Trending/kk/kkFileView 文件在线预览工具正在改变我们处理文…

作者头像 李华
网站建设 2026/4/17 7:55:35

hardfault_handler问题定位:快速理解故障前状态的核心要点

以下是对您提供的博文内容进行 深度润色与结构重构后的专业级技术文章 。我已严格遵循您的所有要求: ✅ 彻底去除AI痕迹,语言自然、老练、有“人味”,像一位深耕嵌入式十余年的工程师在茶歇时跟你掏心窝子讲经验; ✅ 删除所有模板化标题(如“引言”“总结”),改用 …

作者头像 李华
网站建设 2026/4/17 0:40:09

Z-Image-Turbo部署全流程,附完整命令和截图

Z-Image-Turbo部署全流程&#xff0c;附完整命令和截图 Z-Image-Turbo不是又一个“跑得快但画得糊”的文生图模型。它把速度、质量、易用性三者真正拧成一股绳——8步出图&#xff0c;16GB显存就能稳稳跑满&#xff0c;中英文提示词都能精准渲染文字&#xff0c;生成的照片级人…

作者头像 李华
网站建设 2026/4/17 0:17:15

图层导出总失败?Qwen-Image-Layered问题解决方案

图层导出总失败&#xff1f;Qwen-Image-Layered问题解决方案 你是不是也遇到过这样的情况&#xff1a;明明已经成功运行了 Qwen-Image-Layered&#xff0c;上传了一张图&#xff0c;点击“导出图层”&#xff0c;结果弹出报错、空白输出、JSON解析失败&#xff0c;或者 ComfyU…

作者头像 李华