自学Python第七天- 闭包、装饰器、推导式、迭代器、生成器

本文详细介绍了Python编程中的高级概念,包括闭包、装饰器、推导式和迭代器。闭包允许在外部函数中保留局部变量,装饰器用于在不修改函数源代码的情况下添加功能,如日志记录或性能优化。推导式简化了创建新数据结构的过程,而迭代器提供了遍历集合的高效方式。此外,还探讨了带参数的装饰器、类装饰器和装饰器的叠加使用。通过这些概念,开发者可以写出更加灵活和高效的Python代码。
摘要由CSDN通过智能技术生成

闭包

当满足以下三个条件时就形成了闭包:

  • 在一个外函数中定义了一个内函数
  • 内函数里运用了外函数临时变量
  • 外函数返回值内函数的引用
# 闭包
def out(i):      # 一个外层函数
    def inner(j):    # 内层函数
        return i*j
    return inner

使用时候先调用外层函数,返回结果就是内层函数。

func = out(3)	# 此时,func 相当于是函数 inner ,只是实参 3 传入函数形参 i
res = func(4)	# 相当于 inner(4),并且形参已经赋值 3 
print(res)		# 结果 12	

闭包较普通函数有以下优点:

  • 间接实现在外部使用局部变量,保留下了局部变量的信息
    一个函数结束的时候,会把自己的临时变量都释放给内存。通过闭包可以实现保留这些信息并继续使用。
  • 构造一些结构清晰、语义简单的函数,提升部分代码和变量的复用性
# 没用闭包
def mypow(x, y):
	return x**y
print(mypow(5, 2))
print(mypow(2, 3))
# 使用闭包
def anypow(y):
	def inner(x):
		return x**y
	return inner
square = anypow(2)
cube = anypow(3)
print(square(5))
print(cube(3))

装饰器

装饰器 (@) 是一种语法糖,主要用于在函数或类的基础上添加一些可重用的额外功能。从应用开发的角度来开,使用装饰器来对我们的应用代码进行功能性扩展和包装,以提高代码的可重用性可维护性

装饰器可以在不改变函数源代码的情况下,为函数添加新的功能。常用于日志记录、在Web应用中进行登录验证或路由注册、调用跟踪管理及参数验证、性能优化中记录任务执行时间等。

创建装饰器

# 正常的将函数作为参数传递式写法
def print_hw(f):
    print('hello world')
    return f


def test():
    sum_li = sum((12, 3, 4))
    print(sum_li)
    return sum_li


test2 = print_hw(test)  # 函数引用作为实参,即 test2 = f = test
print(test2())  # 相当于 test2() = f() = test()

使用装饰器,可以改成

# 直接改装饰器
def print_hw(f):
    print('hello world')
    return f

@print_hw  # 装饰器
def test():
    sum_li = sum((12, 3, 4))
    print(sum_li)
    return sum_li

print(test())  # 直接调用函数,就可以先调用 print_hw

装饰器函数也可以这样写:

# 通用装饰器
def print_hw(f):
    def wrapper(*args, **kwargs):
        print('hello world')
        return f()
    return wrapper

@print_hw  # 装饰器
def test():
    sum_li = sum((12, 3, 4))
    print(sum_li)
    return sum_li

print(test())  # 直接调用

由此,可以写成:

def print_hw(func):
    def print_in():
        print('hello world')
        return func()
    return print_in

def test():
    sum_li = sum((12, 3, 4))
    print(sum_li)
    return sum_li

test1 = print_hw(test)
print(test1())

这四个例子是等价的。由最后一个列子可以看到,形成了一个闭包。所以装饰器是传递的参数不是变量而是函数的特殊形式的闭包。装饰器的两种写法(第2/3种)的区别与优劣:

  • 上一种(示例中第2种写法)结构明晰,传参明确,但是扩展的功能只能在原函数之前(因为原函数定义在 return 里)。
  • 下一种(示例中第3种写法)可以在原函数执行完成后增加扩展功能,但是如果是写通用函数时,需注意传参的问题(因为实参传给了 *args**kwargs

带参数的装饰器

装饰器本身可以附带参数

def a(msg):
    def wrapper(func):
        def inner(*args, **kwargs):
            print('装饰器的参数内容:', msg)
            func(*args, **kwargs)
        return inner
    return wrapper

@a('123')
def b(msg):
    print('调用函数的参数内容:', msg)

b(456)
"""
输出结果
装饰器的参数内容: 123
调用函数的参数内容: 456
"""

类作为装饰器使用

class A:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print('类装饰器输出的内容')
        self.func(*args, **kwargs)

@A
def b(msg):
    print('函数的参数内容:', msg)

b('456')
"""
输出结果:
类装饰器输出的内容:
函数的参数内容: 456
"""

带参数的类作为装饰器使用

class A:
    def __init__(self, msg):
        self.msg = msg

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print('装饰器的参数内容:', self.msg)
            func(*args, **kwargs)
        return wrapper

@A('123')
def b(msg):
    print('函数的参数内容:', msg)

b('456')
"""
输出结果:
装饰器的参数内容: 123
函数的参数内容: 456
"""

装饰器叠加

装饰器可以叠加使用。叠加使用时,会将被叠加的装饰器及原函数作为统一的一个函数,被外层的装饰器调用

# 装饰器的嵌套使用
def decorator_outer(func):  # 外部装饰器
    def wrapper(*args, **kwargs):			# 通用不定长参数
        print('--外部装饰器开始--')
        res = func(*args, **kwargs)
        print('--外部装饰器结束--')
        return res
    return wrapper

def decorator_inner(func):  # 内部装饰器
    def wrapper(*args, **kwargs):
        print('--内部装饰器开始--')
        res = func(*args, **kwargs)
        print('--内部装饰器结束__')
        return res
    return wrapper

@decorator_outer
@decorator_inner
def orig_func(a):
    print(f'这是原函数,参数是{a}')
    return a * 3

print(f'返回结果为:{orig_func(5)}')

# 执行结果
# --外部装饰器开始--
# --内部装饰器开始--
# 这是原函数,参数是5
# --内部装饰器结束__
# --外部装饰器结束--
# 返回结果为:15

装饰器常用于将现有的函数增加功能,而不改变函数使用方式和代码。例如增加时间记录、日志记录等。如果在原有函数内部更改,可能会有混乱,写新的函数在新函数内部调用旧函数,则需要在所有调用的地方更改调用函数名。使用装饰器则可以避免这些问题。

推导式

推导式(又称为解析式)是 python 的一种特有的语法,可以从一个数据序列构建另一个新的数据序列的结构体。其主结构是一个简单的循环判断语句。Python 支持各种数据结构的推导式:

  • 列表(list)推导式
  • 字典(dict)推导式
  • 集合(set)推导式
  • 元组(tuple)推导式

列表推导式

列表推导式的格式为:

  • [表达式 for 变量 in 列表]
  • [表达式 for 变量 in 列表 if 条件]
  • [表达式 for 可迭代对象 in 矩阵 for 变量 in 可迭代对象]
  • [表达式 for 变量1 in 列表1 for 变量2 in 列表2]
>>> names = ['Bob','Tom','alice','Jerry','Wendy','Smith']
>>> new_names = [name.upper() for name in names if len(name)>3]
>>> print(new_names)
['ALICE', 'JERRY', 'WENDY', 'SMITH']
>>> multiples = [i for i in range(30) if i % 3 == 0]
>>> print(multiples)
[0, 3, 6, 9, 12, 15, 18, 21, 24, 27]
li = [i+j for i in '123' for j in 'abc']
table1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
li = [i*i for row in table1 for i in row]

列表推导式的嵌套没有数量限制,但是嵌套越多复杂性越高,使用时需注意。

字典推导式

字典推导式的格式为

  • { 键表达式 : 值表达式 for 变量 in 集合 }
  • { 键表达式 : 值表达式 for 变量 in 集合 if 条件 }
listdemo = ['Google','Runoob', 'Taobao']
# 将列表中各字符串值为键,各字符串的长度为值,组成键值对
>>> newdict = {key:len(key) for key in listdemo}
>>> newdict
{'Google': 6, 'Runoob': 6, 'Taobao': 6}
>>> dic = {x: x**2 for x in (2, 4, 6)}
>>> dic
{2: 4, 4: 16, 6: 36}
>>> type(dic)
<class 'dict'>
dic = {k:v for k,v in i.items()}

集合推导式

集合推导式的格式为:

  • { 表达式 for 元素 in 序列 }
  • { 表达式 for 元素 in 序列 if 条件 }
>>> setnew = {i**2 for i in (1,2,3)}
>>> setnew
{1, 4, 9}
>>> a = {x for x in 'abracadabra' if x not in 'abc'}
>>> a
{'d', 'r'}
>>> type(a)
<class 'set'>

元组推导式

元组本身没有推导式,实际上是生成器推导式,但是可以将其转为元组。
元组推导式可以利用 range 区间、元组、列表、字典和集合等数据类型,快速生成一个满足指定需求的元组。其格式为:

  • ( 表达式 for 元素 in 序列 )
  • ( 表达式 for 元素 in 序列 if 条件 )
>>> a = (x for x in range(1,10))
>>> a
<generator object <genexpr> at 0x7faf6ee20a50>  # 返回的是生成器对象
>>> tuple(a)       # 使用 tuple() 函数,可以直接将生成器对象转换成元组
(1, 2, 3, 4, 5, 6, 7, 8, 9)

需要注意的是元组推导式返回的结果是一个生成器对象,需要转换类型使用。

迭代器

迭代是Python最强大的功能之一,是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。字符串,列表或元组对象都可用于创建迭代器。

需要注意的是,迭代器对象因为不会后退,所以只能遍历1次。当该迭代器对象遍历完成后就无法重新使用了。可以说迭代器对象是一次性的。

可迭代对象和迭代器

迭代器是一个可迭代对象,可迭代对象不一定是迭代器,可以是定义了迭代器的对象。使用 for in 遍历对象必须是可迭代对象,也就是定义了迭代器的对象。

迭代器的使用

迭代器有两个基本的方法:iter()next(),分别用于创建迭代器对象和调用迭代器的下一个元素:

>>> list=[1,2,3,4]
>>> it = iter(list)    # 创建迭代器对象 it
>>> print (next(it))   # 输出迭代器的下一个元素
1
>>> print (next(it))
2

迭代器对象可以使用常规for语句进行遍历:

list=[1,2,3,4]
it = iter(list)    # 创建迭代器对象
for x in it:
    print (x, end=" ")

# 执行结果:
# 1 2 3 4

迭代器使用 next() 函数遍历,并通过 StopIteration 结束遍历。

import sys         # 引入 sys 模块
 
list=[1,2,3,4]
it = iter(list)    # 创建迭代器对象
 
while True:
    try:
        print (next(it))
    except StopIteration:	# 完成遍历
        sys.exit()

自定义对象创建迭代器

把一个类作为一个迭代器使用需要在类中实现两个方法 __iter__()__next__()__iter__() 方法返回一个特殊的迭代器对象, 这个迭代器对象实现了 __next__() 方法并通过 StopIteration 异常标识迭代的完成。__next__() 方法会返回下一个迭代器对象。

class MyNumbers:
  def __iter__(self):
    self.a = 1
    return self		# 必须要有返回值,可迭代对象
 
  def __next__(self):
    x = self.a
    self.a += 1
    return x
 
myclass = MyNumbers()
myiter = iter(myclass)
 
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))

# 执行结果:
# 1
# 2
# 3
# 4
# 5

StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 __next__() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。

class MyNumbers:
  def __iter__(self):
    self.a = 1
    return self

  def __next__(self):
    if self.a <= 20:
      x = self.a
      self.a += 1
      return x
    else:
      raise StopIteration

myclass = MyNumbers()
myiter = iter(myclass)

for x in myiter:
  print(x,end=' ')

# 执行结果:
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

可迭代对象的循环遍历

python 中遍历循环实际上就是循环使用可遍历对象的迭代器,可迭代对象只是告诉我们支持循环,真正在循环中取值的是迭代器。

# 遍历循环的本质
nums = iter([1, 2, 3, 4])	# 创建一个迭代器
while True:
	try:
		print(nums.__next__())
	except StopIteration:
		break

虽然列表、字符串等对象没有 __iter__()__next__() 方法,但是使用 for 进行循环遍历时,在底层会直接遍历其转换的迭代器对象(因为迭代器对象是一次性的,所以每次遍历时会新生成一个迭代器对象)。

生成器

在 Python 中,使用了 yield 的函数被称为生成器(generator)。跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。调用一个生成器函数,返回的是一个迭代器对象

创建生成器

import sys
 
def fibonacci(n): # 生成器函数 - 斐波那契
    a, b, counter = 0, 1, 0
    while True:
        if (counter > n): 
            return
        yield a		# 暂停,并返回 yield 的值 a
        a, b = b, a + b
        counter += 1
        
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
 
while True:
    try:
        print (next(f), end=" ")
    except StopIteration:
        sys.exit()

# 执行结果:
# 0 1 1 2 3 5 8 13 21 34 55

元组推导式的方式能够生成一个生成器对象,所以可以用此方法快速的创建生成器对象。当其中的元素特别巨大时,能够快速的生成,并且不占用存储空间。

myList = [i for i in range(99999999)]		# 执行后立即生成列表对象,耗时长,立即占用大量空间

myGenerator = (i for i in range(99999999))		# 生成一个生成器对象,不占空间和时间,可以遍历,但是不支持下标索引

生成器的 send() 方法和 close() 方法

生成器有2个默认方法 send() 用来发送信息给生成器, close() 用来关闭生成器

def my_range(num):
	i = 0
	while i < num:
		value = yield i		# 每次生成器运行时,执行 yield i 时停止,下次执行才会进行赋值运算
		print('value = ', value, end = ' ---- ')
		i += 1

my_ = my_range(5)
print(next(my_))
print(next(my_))
print(my_.send('执行send方法'))		# 执行 yield,并发送数据
my_.close()		# 关闭生成器对象,再进行迭代会有 StopIteration 错误
"""
执行结果:
0
value = None ---- 1		# yield 本身并没有值,所以是 None
value = 执行send方法 ---- 2		# send()方法发送了数据给 yield,可以赋到值了
"""

协程

协程因其特征就是执行到特殊操作时(通常是耗时的IO操作)暂停当前操作转而执行其他操作,这种机制就是一个特殊的生成器。具体的可以参考协程。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值