Python装饰器和迭代器

1. 闭包函数

函数调用完之后,内部定义的变量直接销毁了,但是有时候我们需要保存函数内的这个变量,对这个变量进行一些其他操作,比如求和,因此产生了闭包函数

闭包函数形成条件:
1)函数嵌套
2)内部函数使用外部函数的变量
3)外部函数返回内部函数的名称

以下是一个简单的闭包函数:

def outer(num1):
    num1 += 5

    def inner(num2):
        return num1 + num2

    return inner


# 获得inner函数,此时num1=10,调用完之后自动保存num1的值
inner = outer(5)
# 调用inner函数,此时num1 + num2 = 10 + 5,结果为15
result = inner(5)
print(result)

如果需要修改外部函数变量的值,需要用到nonlocal修饰符

def outer(num1):
    num1 += 5

    def inner(num2):
    	# 不加nonlocal会报错
        nonlocal num1
        num1 += num2
        return num1

    return inner


inner = outer(5)
result = inner(5)
print(result)

nonlocal仅仅针对内部函数修改外部函数变量值的场景有效,如果想在函数内修改函数外的变量值,需要用到global修饰符

num = 100

def func():
	# 不加global会报错
    global num
    num *= 2
    return num

print(func())

2. 装饰器

装饰器给已有的函数增加额外的功能,类似于Spring的AOP
特点:
1)无需修改已有函数的代码,就能增加额外的功能
2)无需修改已有函数的调用方式,就能增加额外的功能

def check(fc):
    print('装饰器函数执行')

    def wrapper():
        """
        我是一个装饰函数
        """
        print('是否登录')
        fc()

    return wrapper


@check
def comment():
    """
    我是一个被装饰函数
    """
    print('发表评论')


# 运行结果是:装饰器函数执行 -> 是否登录 -> 发表评论
# 相当于:
# comment = check(comment)
# comment()等于wrapper()
comment()

# 结果是wrapper
print(comment.__name__)
# 结果是我是一个装饰函数
print(comment.__doc__)

此时被装饰的comment函数已经被替换为wrapper函数,访问comment函数的__name__和__doc__属性返回的是wrapper函数的,如果想要外界无感知 ,需要在wrapper函数内部进行属性的替换,以下是两种方法
1)手动替换属性

def check(fc):
    print('装饰器函数执行')

    def wrapper():
        """
        我是一个装饰函数
        """
        print('是否登录')
        fc()

    wrapper.__name__ = 'comment'
    wrapper.__doc__ = """
            我是一个被装饰函数
            """
    return wrapper

2)使用wraps装饰器

def check(fc):
    print('装饰器函数执行')

    @wraps(fc)
    def wrapper():
        """
        我是一个装饰函数
        """
        print('是否登录')
        fc()
    return wrapper

以上的例子是用函数作为装饰器,类也可以用作装饰器,实现的方式是用__init__和__call__方法

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

    def __call__(self, *args, **kwargs):
        print('是否登录')
        self.func()


@CHECK
def comment():
    print('发表评论')


comment()

在使用类作为装饰器的时候,会引出一个问题,@CHECK和@CHECK()的区别

class CHECK:
    def __init__(self):
        pass

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print('是否登录')
            func()

        return wrapper


@CHECK()
def comment():
    print('发表评论')


comment()

"""
@CHECK相当于CHECK(comment)(),先调用__init__方法接收comment后返回实例,然后再调用__call__方法
@CHECK()相当于CHECK()(comment),先调用__init__方法返回实例,然后再调用__call__方法接收comment
"""

刚才的被装饰函数没有参数,且不返回数据,考虑通用的场景,即被装饰的函数有参数,且返回数据

from functools import wraps


def check(fc):
    print('装饰器函数执行')

    @wraps(fc)
    def wrapper(*args, **kwargs):
        print('是否登录')
        return fc(*args, **kwargs)

    return wrapper


@check
def comment(username, message):
    return f'{username}发表了{message}'


print(comment(username="特朗普", message="让美国再次伟大"))

以上的例子都是对函数进行装饰,我们也可以对类进行装饰

from functools import wraps


def decorator(cls):
    @wraps(cls)
    def wrapper(*args, **kwargs):
        cls.name = "杜兰特"
        person = cls(*args, **kwargs)
        person.vocation = "NBA" + person.vocation
        return person

    return wrapper


@decorator
class Person:
    name = "詹姆斯"

    def __init__(self, vocation):
        self.vocation = vocation


# 相当于 Person = decorator(Person),调用Person()相当于调用wrapper() 
person = Person("篮球运动员")
print(person.name)
print(person.vocation)
"""
结果是:
杜兰特
NBA篮球运动员
"""

以上的装饰器函数都没有入参,如果装饰器函数需要传入参数,可以采用以下的形式:

from functools import wraps


def decorator(message):
    def inner(func):
        @wraps(func)
        def wrapper():
            print("装饰器的入参message={}".format(message))
            func()

        return wrapper

    return inner


@decorator("我是一个装饰器")
def hello_world():
    print("你好世界")


"""
执行顺序如下:
1)@decorator("我是一个装饰器")=@inner
2)@inner = wrapper
3)hello_world = wrapper
4)hello_word() = wrapper()
"""
hello_world()

前面的例子都是仅用一个装饰器函数来装饰特定的函数,我们也可以给一个函数添加多重装饰器

def decorator1(func):
    def wrapper(*args, **kwargs):
        print("1")
        func()
        print("5")

    return wrapper


def decorator2(func):
    def wrapper(*args, **kwargs):
        print("2")
        func()
        print("4")

    return wrapper


# 装饰过程是从下到上,即print_func = decorator1(decorator2(func))
# 调用过程是从上到下,结果是1,2,3,4,5
@decorator1
@decorator2
def print_func():
    print("3")


print_func()

最后再通过手写自动化测试中数据驱动的例子来感受一下装饰器的便捷之处

import unittest


def decorator(cls):
    l = list(cls.__dict__.items())
    for k, v in l:
        if hasattr(v, "TEST_DATA"):
            test_data = getattr(v, "TEST_DATA")
            for index, data in enumerate(test_data):
                test_method_name = f"{k}_{index}"
                test_method = decorate_test_method(v, data)
                setattr(cls, test_method_name, test_method)
            delattr(cls, k)
    return cls


# 由于unittest框架测试方法的参数只能是self,因此需要通过装饰器来传入参数
def decorate_test_method(method, data):
    def wrapper(self):
        method(self, data)

    return wrapper


def test_data(data):
    def wrapper(func):
        setattr(func, "TEST_DATA", data)
        return func

    return wrapper


@decorator
class TestCase(unittest.TestCase):
    cases1 = [
        {"case_name": "正确密码登录", "username": "admin", "password": "123456", "excepted": "登录成功"},
        {"case_name": "错误密码登录", "username": "admin", "password": "123123", "excepted": "密码错误"}
    ]

    cases2 = [
        {"case_name": "搜索存在的商品", "good_name": "巧克力", "excepted": "商品存在"},
        {"case_name": "搜索不存在的商品", "good_name": "力克巧", "excepted": "商品不存在"}
    ]

    @test_data(cases1)
    def test_login(self, data):
        print("登录用例")
        print(data)
        print("=========")

    @test_data(cases2)
    def test_search(self, data):
        print("搜索用例")
        print(data)
        print("=========")


if __name__ == '__main__':
    suite = unittest.TestLoader().loadTestsFromTestCase(TestCase)
    runner = unittest.TextTestRunner(buffer=True)
    runner.run(suite)

3. 可迭代对象

可迭代对象一般都可以通过for循环来迭代,for循环内部过程如下:
1 判断有没有__iter__()方法
2.1 如果有的话,先调用iter()方法获得迭代器,然后循环调用next()方法
2.2 如果没有的话,则循环调用getitem(索引)方法
3 碰到异常之后,停止迭代,2.1一般抛出StopIteration,2.2一般抛出IndexError

以下是for循环遍历列表的过程:

l = [1, 2, 3]

# 获得迭代器
iterator = iter(l)

# 获得迭代器中的每一个元素
print(next(iterator))
print(next(iterator))
print(next(iterator))

# StopIteration异常
print(next(iterator))

可以通过
1)实现__iter()__方法,一般__iter()__方法需要返回一个迭代器对象
2)实现__getitem()__方法
来得到一个可迭代对象

# 1. 实现__iter__()方法
class IterOne:
    def __iter__(self):
        return iter([1, 2, 3])


# 2. 实现__getitem__()方法
class IterTwo:
    l = [1, 2, 3]

    def __getitem__(self, item):
        return self.l[item]


if __name__ == '__main__':
    for i in IterOne():
        print(i)

    for j in IterTwo():
        print(j)

如何判断一个对象是否可迭代呢?
不能用isinstance(obj, Iterable)来判断,这样会忽略实现__getitem()__方法的对象

from collections.abc import Iterable


# 1. 实现__iter__()方法
class IterOne:
    def __iter__(self):
        return iter([1, 2, 3])


# 2. 实现__getitem__()方法
class IterTwo:
    l = [1, 2, 3]

    def __getitem__(self, item):
        return self.l[item]


if __name__ == '__main__':
    # 结果是True
    print(isinstance(IterOne(), Iterable))
    # 结果是False
    print(isinstance(IterTwo(), Iterable))

可以通过调用iter(obj)是否报错来判断

# 1. 实现__iter__()方法
class IterOne:
    def __iter__(self):
        return iter([1, 2, 3])


# 2. 实现__getitem__()方法
class IterTwo:
    l = [1, 2, 3]

    def __getitem__(self, item):
        return self.l[item]


def check_iterable(obj):
    try:
        iter(obj)
    except Exception:
        return False
    return True


if __name__ == '__main__':
    # 结果是True
    print(check_iterable(IterOne()))
    # 结果是True
    print(check_iterable(IterTwo()))
    # 结果是False
    print(check_iterable(10))

4. 迭代器

迭代器要求实现__iter()__和__next()__两个方法,因此迭代器一定是一个可迭代对象,而可迭代对象不一定是一个迭代器
以下实现了一个迭代器:

class Iterator:
    def __init__(self):
        self.num = 10

    def __iter__(self):
        return self

    def __next__(self):
        if self.num > 0:
            self.num -= 1
            return self.num
        else:
            raise StopIteration


if __name__ == '__main__':
    iterator = Iterator()
    for i in iterator:
        print(i)

以下是三种常见的获取迭代器的方式:
1)返回iter(内置可迭代对象,比如列表),list和string并不是迭代器,只实现了__iter()__方法,是一个可迭代对象

class IterOne:
    def __iter__(self):
        return iter([1, 2, 3])


if __name__ == '__main__':
    iter_one = IterOne()
    for i in iter_one:
        print(i)

2)自定义一个实现__iter()__和__next()__两个方法的类
3)返回一个已经实现的迭代器对象

class Iterator:
    def __init__(self):
        self.num = 10

    def __iter__(self):
        return self

    def __next__(self):
        if self.num > 0:
            self.num -= 1
            return self.num
        else:
            raise StopIteration


class IterTwo:
    def __iter__(self):
        return Iterator()


if __name__ == '__main__':
    iter_two = IterTwo()
    for i in iter_two:
        print(i)

5. 生成器

生成器是一个特殊的迭代器,使用yield的函数就被称为生成器,不是一个普通的函数了,它也实现了__iter()__和__next()__两个方法,以下是一个简单的生成器

def gen(n):
    print("开始生成")
    for i in range(n):
        # 每次遇到yield时候函数暂停并抛出当前的运行信息
        yield i*i

# 获取生成器
g = gen(5)

# 获得生成器中的每个元素,__next__()当执行完yield之后暂停,下一次__next__()调用的时候从上次暂停的位置继续运行
print(next(g))
# 结果是:
# 开始生成
# 0

print(next(g))
# 结果是:
# 1

print(next(g))
# 结果是:
# 4

print(next(g))
# 结果是:
# 9

print(next(g))
# 结果是:
# 16

# StopIteration异常
print(next(g))

也可以用推导式构造生成器

gen = (i ** 2 for i in range(5))

# <class 'generator'>
print(type(gen))

for i in gen:
    print(i)

生成器有三个内置的方法:
1)close(),关闭当前生成器,关闭后再次调用next()方法,会抛出StopIteration

def gen():
    yield 1
    yield 2


g = gen()

# 结果是1
print(next(g))

# 关闭当前生成器
g.close()

# 结果抛出StopIteration异常
print(next(g))

2)throw()方法,传入一个异常,生成器会抛出该异常

def gen():
    yield 1
    yield 2


g = gen()

# 结果是1
print(next(g))

# 抛出ValueError
g.throw(ValueError)

# 由于上面一行语句抛出异常,因此无法执行到这里
print(next(g))

3)send()方法,主要作用是和生成器进行内外部的数据交换

def gen():
    val1 = yield 1
    print(f'val1 = {val1}')
    val2 = yield 2
    print(f'val2 = {val2}')
    val3 = yield 3
    print(f'val3 = {val3}')


g = gen()

v1 = (next(g)) # v1 = (g.send(None))也可以
print(f'v1 = {v1}')
v2 = g.send(200)
print(f'v2 = {v2}')
v3 = g.send(300)
print(f'v3 = {v3}')
v4 = g.send(400)
"""
结果是:
v1 = 1
val1 = 200
v2 = 2
val2 = 300
v3 = 3
val3 = 400
StopIteration
"""

调用send方法前必须要先调用一次next()方法或者send(None)方法来使得生成器在运行状态
1)调用next(g)的时候,先执行yied 1,把1给了v1,程序停在val1 = 这
2)调用send(200)的时候,会先把200的值给val1,然后再执行yield 2,把2给了v2,程序停在val2 = 这
3)调用send(300)的时候,会先把300的值给val2,然后再执行yield 3,把3给了v3,程序停在val3 = 这
4)调用send(400)的时候,会先把300的值给val3,生成器执行完毕,抛出StopIteration

可以看出生成器是一种懒加载机制,不会一次把所有数据加载到内存中(列表等对象会),而是在要获得数据的时候加载,
因此生成器的内存空间占用更少,运行速度稍快,在处理较多数据的时候,优先考虑使用生成器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值