...

Python进阶

装饰器

装饰器是Python中的一个重要概念,多用于在不修改原函数的基础上,为函数增加额外的功能。

基础装饰器

例如小李给女朋友买了一款iPhone12作为生日礼物,手机原封未拆封。

def gift():
     print('iPhone12')

gift()   # 运行显示礼物信息

但还是觉得礼物太单薄,于是又买了一盒德芙巧克力,一支dior的口红,并找了个精美的礼品盒包装了一下,盒子里放满了泡沫球。

def gift():
     print('iPhone12')

def box(gift):
    print('='*5 + '礼物盒' + '='*5)
    print('一盒泡沫球')
    print('好多巧克力')
    print('一支dior口红')
    return gift

gift = box(gift)   # 将礼物包装后作为礼物
gift()  # 显示礼物信息

运行后显示如下:

=====礼物盒=====
一盒泡沫球
好多巧克力
一支dior口红
iPhone12

这个box便是一个装饰器,它的参数是一个函数对象,同数字、字符串、列表、字典等数据类型一样,函数和类也可以作为函数的参数使用,毕竟在Python里人人平等,一切皆对象。
box在使用时依然返回了原来的gift,只是在拿到这个gift之前增加了两个额外的惊喜,然后我们把box作为gift使用即可。

装饰器本质上就是以函数作为参数,对函数做一些处理,并替换原函数的一种高阶函数。
上例中,使用装饰器表示为如下。

def box(gift):   #  以函数为参数的装饰器
    print('='*5 + '礼物盒' + '='*5)
    print('一盒泡沫球')
    print('好多巧克力')
    print('一支dior口红')
    return gift

@box        # 挂载装饰器,会自动替换原函数
def gift():
     print('iPhone12')

gift()   # 这里面得到的gift实际上是装饰后的gift即`box(gift)`

运行后显示和上例相同。

处理函数参数

小李突然想到,买哪个颜色应该征询下女友的意见,也就是原来的gift应支持一个可供选择的颜色参数。

def gift(color):
    print(f'iPhone12{color}版')

作为一个细心的boyfriend,小李需要根据对应的手机颜色选择同样颜色的泡沫球,也就是需要能获取到,被装饰的gift函数的参数。
这时候我们需要在盒子内部(box装饰器),重新准备一个新的礼物,根据颜色参数做不同的处理,然后根据颜色拿到指定的iPhone12礼物。

def box(gift):
    print('='*5 + '礼物盒' + '='*5)

    def new_gift(color):  # 准备一个新的礼物,参数和原gift参数一致
        print(f'一盒{color}泡沫球')  # 根据颜色准备泡沫球
        print('好多巧克力')
        print('一支dior口红')
        return gift(color)  # 根据颜色拿到指定的iPhone12

    return new_gift   # 返回新礼物,新礼物调用时,增加一些惊喜,并返回原有礼物gift(color)的结果。

@box
def gift(color):
    print(f'iPhone12{color}版')

gift('红色')   # 实际上这里的gift是被box装饰后狸猫换太子的new_gift函数,而new_gift('红色'),返回原gift('红色')的结果。

在box内部为了根据参数做对应处理,我们新建了一个函数,函数内部也可以定义内部函数,内部函数new_gift可以获取并使用外部box函数的参数,如gift。
为了能获取到原有函数gift的参数,我们需要建立一个傀儡函数new_gift,这个函数和原函数gift的参数一致、返回结果一致,即new_gift('红色')返回的就是gift('红色')。
然后狸猫换太子,不再返回原来的gift函数对象,而是返回替换的new_gift函数对象。

运行后显示

=====礼物盒=====
一盒红色泡沫球
好多巧克力
一支dior口红
iPhone12红色版

注意:在装饰器box里,要返回一个函数对象,如上例中的return gift或本例中的return new_gift。而在傀儡函数new_gift中,为了和原函数gift结果一致,要返回原函数的调用结果即gift(color)。

从普遍意义上讲,作为商家,为了装饰器box可以包装任何形式的礼物,无论礼物有什么参数都可以满足,这就要求我们的傀儡函数new_gift支持任意类型的参数即def new_gift(*args, **kwargs)
然后把无论什么参数*args, **kwargs交由原函数gift(*args, **kwargs)处理即可。
修改后,我们便得到一个通用的装饰器,可以包装任何礼物。

def box(gift):
    print('='*5 + '礼物盒' + '='*5)

    def new_gift(*args, **kwargs):    # 接受任意数量的参数
        if args and len(args) > 0:   # 由于参数不确定了,我们假设万一有参数,第一个参数是color参数
            color = args[0]
            print(f'一盒{color}泡沫球')
        else:
            print(f'一盒泡沫球')

        print('好多巧克力')
        print('一支dior口红')
        result = gift(*args, **kwargs)  # 如果我们需要对原函数的结果做出处理,可以先获取到结果
        # print(f'原函数结果{result}')    由于原函数gift没有return,这是其实是None
        return result  # 返回原函数结果

    return new_gift

@box
def gift(color, pro=False):   # 新的礼物函数,两个参数,默认买12,万一女友要Pro,也可以
    if pro is True:
        print(f'iPhone12 Pro{color}版')
    else:
        print(f'iPhone12{color}版')

gift('海蓝色', pro=True)

这样,无论被装饰的函数有几个参数,box装饰器都可以正常处理。
运行后显示如下。

=====礼物盒=====
一盒海蓝色泡沫球
好多巧克力
一支dior口红
iPhone12 Pro海蓝色版

带参装饰器

信心满满的小李觉得,在盒子上还可以做些文章,要根据女友的喜好选择不同形状的箱子,因此我们需要根据参数来定制我们的装饰器box,在盒子外面再加一层定制函数。

def custom_box(shape):   # 根据参数定制装饰器
    def box(gift):   # 装饰器函数
        print('='*5 + f'{shape}礼物盒' + '='*5)   # 根据形状定制
        # ...
    return box   # 返回装饰器函数

此时我们得到一个可以根据参数进行定制的装饰器函数custom_box,这个装饰器接收到参数后,传递给真实装饰器box,并返回定制后box装饰器函数。
完整代码如下。

def custom_box(shape):   # 根据参数定制装饰器 =====================

    def box(gift):   # 实际的装饰器函数 ---------------------------
        print('='*5 + f'{shape}礼物盒' + '='*5)

        def new_gift(*args, **kwargs):   # 傀儡函数 ..............
            if args and len(args) > 0:
                color = args[0]
                print(f'一盒{color}泡沫球')
            else:
                print(f'一盒泡沫球')
            print('好多巧克力')
            print('一支dior口红')
            result = gift(*args, **kwargs)
            return result  # 返回原函数结果 ......................

        return new_gift # 返回傀儡函数 ---------------------------

    return box   # 返回定制的装饰器 ===============================

@custom_box('心形')   # 使用可定制的装饰器
def gift(color, pro=False):
    if pro is True:
        print(f'iPhone12 Pro{color}版')
    else:
        print(f'iPhone12{color}版')

gift('海蓝色', pro=True)

注意:装饰器在导入模块时立即计算的,即没调用gift('海蓝色', pro=True)之前就已经执行生成定制后的box。

运行后,结果如下。

=====心形礼物盒=====
一盒海蓝色泡沫球
好多巧克力
一支dior口红
iPhone12 Pro海蓝色版

生成器和迭代器

可迭代对象

实现了__iter__方法, __iter__方法返回一个迭代器

迭代器

按标准的迭代协议实现__iter__和__next__方法,StopIteration结束

class A:
    start = 0
    def __iter__(self):
        return self

    def __next__(self):
        if self.start > 10:
            raise StopIteration
        self.start += 1
        return self.start

生成器

内部实现了迭代器的一种函数,通过yield记录当前位置并返回一次迭代结果

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

列表推导式

推倒式:当我们对一批可迭代的数据(如列表或字典)进行提取或处理,最后要得到一个新的列表或字典时,推导式是一种非常简洁的表达方式。

比如,有一批数据

data = [
    {'name': '张三', 'gender': 'male',  'age': 12},
    {'name': '李四', 'gender': 'female',  'age': 10},
    {'name': '王五', 'gender': 'male',  'age': 20},
    {'name': '赵六', 'gender': 'male',  'age': 11},
    {'name': '周七', 'gender': 'female',  'age': 16},
    {'name': '孙八', 'gender': 'male',  'age': 13},
]

我们想要把数据中的name都提取出来形成一个新的列表,一般的操作是这样的。

names = []  # 定义一个空列表

for item in data:  # 遍历数据
    name = item['name']  # 提取每行中的name
    names.append(name)  # 追加到列表中

如果用推导式的话,形式如下。

names = [item['name'] for item in data]     # 遍历data,提取每项中的name生成一个新列表

数据处理

在提取数据时,我们还可以对每一项数据进行,处理,假设我们需要每个名称前加上'姓名: '这个字符串,可以这样。

names = ['姓名: '+item['name'] for item in data]

'姓名: '+item['name'] 就是每一项的数据

数据筛选

同样我们还可以对数据进行筛选,比如我们只要年龄大于12岁,后面可以使用if进行过滤

names = [item['name'] for item in data if item['age']>12]

多重循环

推导式还支持多重循环,比如

for x in range(1,5)
    if x > 2
        for y in range(1,4)
            if y < 3
                x*y

使用推导式表示如下

[x*y for x in range(1,5) if x > 2 for y in range(1,4) if y < 3]

批量执行操作

由于推导式就是一种循环操作,我们也可以使用推导式来批量执行一些相似操作,比如:

def step1(driver):
    print('步骤1)

def step2(driver):
    print('步骤2)

def step3(driver):
    print('步骤3)

我们可以将函数名放到一个列表里,然后使用推导式循环执行

steps = [step1, step2, step3]   # 函数名列表

[step(driver) for step in steps]  # 不需要变量接收,我们只需要它循环执行

字典推导式

当我们需要遍历一批数据最后得到一个字典时,同样可以使用字典推导式,如:

data = [
    {'name': '张三', 'gender': 'male',  'age': 12},
    {'name': '李四', 'gender': 'female',  'age': 10},
    {'name': '王五', 'gender': 'male',  'age': 20},
    {'name': '赵六', 'gender': 'male',  'age': 11},
    {'name': '周七', 'gender': 'female',  'age': 16},
    {'name': '孙八', 'gender': 'male',  'age': 13},
]

假设我们想得到一个{'张三': 12, '李四': 10, ....}这样的一个字典,使用字典推导式方式如下:

persons = {item['name']: item['age'] for item in data}

字典推导式同样支持if筛选等操作。

生成器

生成器实际上是一种包含初始数据和推导法则的对象,比如我们可以轻松的写出1w以内所有的奇数,原因是因为我只需要记住从1开始每次加2即可。
生成器便是这样。对应大量的数据或者CSV/Excel文件中的数据,生成器可以大量的节省内存,比如csv.Reader(f)就是一个生成器,只存了当前位置和读取下一行数据的方法。
当你需要遍历时,它再每次给你读取一行数据给你。
如列表推导式的例子,

data = [
    {'name': '张三', 'gender': 'male',  'age': 12},
    {'name': '李四', 'gender': 'female',  'age': 10},
    {'name': '王五', 'gender': 'male',  'age': 20},
    {'name': '赵六', 'gender': 'male',  'age': 11},
    {'name': '周七', 'gender': 'female',  'age': 16},
    {'name': '孙八', 'gender': 'male',  'age': 13},
]
names = [item['name'] for item in data]

我们把列表的中括号改为小括号就得到一个生成器

names2 = (item['name'] for item in data)

注意:生成器和推导式不同,其中的循环不是立即执行的,只用你遍历这个生成器时才会执行

for name in names:  # 遍历列表推导式生成的新列表
    print(name)

for name in names2:  # 遍历一个生成器
    print(name)

两个打印结果是一样的,生成器更节省内存,只有遍历时才运行。

魔术方法

魔术方法 描述
__new__ 创建类并返回这个类的实例
__init__ 可理解为“构造函数”,在对象初始化的时候调用,使用传入的参数初始化该实例
__del__ 可理解为“析构函数”,当一个对象进行垃圾回收时调用
__metaclass__ 定义当前类的元类
__class__ 查看对象所属的类
__base__ 获取当前类的父类
__bases__ 获取当前类的所有父类
__str__ 定义当前类的实例的文本显示内容
__getattribute__ 定义属性被访问时的行为
__getattr__ 定义试图访问一个不存在的属性时的行为
__setattr__ 定义对属性进行赋值和修改操作时的行为
__delattr__ 定义删除属性时的行为
__copy__ 定义对类的实例调用 copy.copy() 获得对象的一个浅拷贝时所产生的行为
__deepcopy__ 定义对类的实例调用 copy.deepcopy() 获得对象的一个深拷贝时所产生的行为
__eq__ 定义相等符号“==”的行为
__ne__ 定义不等符号“!=”的行为
__lt__ 定义小于符号“<”的行为
__gt__ 定义大于符号“>”的行为
__le__ 定义小于等于符号“<=”的行为
__ge__ 定义大于等于符号“>=”的行为
__add__ 实现操作符“+”表示的加法
__sub__ 实现操作符“-”表示的减法
__mul__ 实现操作符“*”表示的乘法
__div__ 实现操作符“/”表示的除法
__mod__ 实现操作符“%”表示的取模(求余数)
__pow__ 实现操作符“**”表示的指数操作
__and__ 实现按位与操作
__or__ 实现按位或操作
__xor__ 实现按位异或操作
__len__ 用于自定义容器类型,表示容器的长度
__getitem__ 用于自定义容器类型,定义当某一项被访问时,使用 self[key] 所产生的行为
__setitem__ 用于自定义容器类型,定义执行 self[key]=value 时产生的行为
__delitem__ 用于自定义容器类型,定义一个项目被删除时的行为
__iter__ 用于自定义容器类型,一个容器迭代器
__reversed__ 用于自定义容器类型,定义当 reversed( ) 被调用时的行为
__contains__ 用于自定义容器类型,定义调用 in 和 not in 来测试成员是否存在的时候所产生的行为
__missing__ 用于自定义容器类型,定义在容器中找不到 key 时触发的行为
posted @ 2022-04-08 21:25  韩志超  阅读(2032)  评论(0编辑  收藏  举报