装饰器和可迭代对

欢迎参加大型膜拜活动

img

img

img

img

img

膜拜完感觉浑身都充满了力量啊!!!

好了新的一天新的动力,今天的憨憨们,有没有给自己打气?

img

猫:“卧槽老子怎么变小了!”

今日洗脑金句: python教万岁,java洗洗睡

闭包函数

在开始前,我跟你们讲一个我遇到的疑惑。

我跟你们讲过一个局部作用域是不能去打扰另一个局部作用域的对吧,于是后来我发现了一个问题

def f1():
    x=1
    def f2():
        print(x)

1

照我们原本的理解来看,是不是没道理,为什么f2可以去调用f1的x,我当时好不容易接受了python不能这样搞得事实,然后突然给我来了一手这个,那不就和java一样了吗

img

这就是 闭包函数

一、什么是闭包

闭包:闭是封闭(函数内部函数),包是包含(该内部函数对外部作用域而非全局作用域的变量的引用)。闭包指的是:函数内部函数对外部作用域而非全局作用域的引用。

放过自己,别看这定义,你只用知道上面那种栗子是闭包,然后被包起来的可以调用包住的变量就是了。。。。。

def outter():
    x = 1

    def inner():
        print(x)
    return inner


f = outter()


def f2():
    x = 2
    f()


f2()

1

传参数就不用讲了,看一下代码,给outter的参数x,因为inner他自己没有,print又要用,没办法,只好去他的上家(outter)里拿。

def outter(x):
    x = 1

    def inner():
        print(x)
    return inner


f = outter(1)
f()
f()
f()
# 查看闭包的元素
print(F"f.__closure__[0].cell_contents: {f.__closure__[0].cell_contents}")

1
1
1
f. __closure____ [0].cell_contents: 1

二、闭包函数的应用

import requests


def get(url):
    response = requests.get(url)
    print(f"done: {url}")
    
get('https://www.baidu.com')
get('https://www.baidu.com')
get('https://www.baidu.com')


get('https://www.cnblogs.com/linhaifeng')
get('https://www.cnblogs.com/linhaifeng')
get('https://www.cnblogs.com/linhaifeng')

done: https://www.baidu.com
done: https://www.baidu.com
done: https://www.baidu.com
done: https://www.cnblogs.com/linhaifeng
done: https://www.cnblogs.com/linhaifeng
done: https://www.cnblogs.com/linhaifeng

这个栗子的意思就是说你每次都要重新传一个参数进去,会很麻烦,然后按照惯例,接下来就会bb用闭包会有多方便,巴拉巴拉巴拉的


def outter(url):
    def get():
        response = requests.get(url)
        print(f"done: {url}")
    return get

baidu=outter('https://www.baidu.com')
python = outter('https://www.python.org')

baidu()
baidu()

python()
python()

done: https://www.baidu.com
done: https://www.baidu.com
done: https://www.python.org
done: https://www.python.org

装饰器

注意,前方重灾区。目前最难点,非战斗人员带好氧气瓶,以及啊字续命照片若干张。

一、无参装饰器

1.1什么是装饰器

装饰器就是函数,用来给别的函数加功能用的。

这里有两个注意的点

  • 装饰器本身其实是可以任意可调用的对象
  • 被装饰的对象也可以是任意可调用的对象

就是那句话,万物接对象

img

1.2为什么要用装饰器

如果我们已经上线了一个项目,我们需要修改某一个方法,但是我们不想修改方法的使用方法,这个时候可以使用装饰器。因为软件的维护应该遵循开放封闭原则,即软件一旦上线运行后,软件的维护对修改源代码是封闭的,对扩展功能指的是开放的。

装饰器的实现必须遵循两大原则:

  1. 不修改被装饰对象的源代码
  2. 不修改被装饰对象的调用方式

装饰器其实就是在遵循以上两个原则的前提下为被装饰对象添加新功能。就是这么简单的东西,是吧,来接着来,一会儿别哭啊

img

1.3怎么用装饰器

第一种方式,改变源代码,这违背了原则,所以不讲,略过

编写重复代码

import time


def index():
    print('welcome to index')
    time.sleep(1)


def f2():
    print('welcome to index')
    time.sleep(1)


start = time.time()
index()
end = time.time()
print(F"index run time is {start-end}")

start = time.time()
f2()
end = time.time()
print(F"f2 run time is {start-end}")

welcome to index
index run time is -1.0046868324279785
welcome to index
f2 run time is -1.000690221786499

上面的内容是不是看不懂什么意义,他的意思就是,如果你要测试index的运行时间,就要写一次代码,如果要测f2函数的运行时间,又要写一次,会很繁琐。这时候就要用装饰器了。

第一种传参方式:改变调用方式

import time


def index():
    print('welcome to index')
    time.sleep(1)


def time_count(func):
    start = time.time()
    func()
    end = time.time()
    print(f"{func} time is {start-end}")


time_count(index)

welcome to index
<function index at 0x102977378> time is -1.000748872756958

你要测试什么函数,就在time_count()里面传函数的对象就好了。当然这么简单还不是装饰器,为什么呢,因为他违背了原则之一的不改变调用方式,我们的悲壮是对象是index,我们想给他加一个测时间的功能,最后却调用了别的函数,这明显违背了原则。所以,接下来就是真正的装饰器了

第二种传参方式:包给函数-外包

import time


def index():
    print('welcome to index')
    time.sleep(1)


def time_count(func):
    # func = 最原始的index
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print(f"{func} time is {start-end}")
    return wrapper

# f = time_count(index)
# f()


index = time_count(index)  # index为被装饰函数的内存地址,即index = wrapper
index()  # wrapper()

welcome to index
<function index at 0x102977730> time is -1.0038220882415771

先什么都别管,有没有改变被装饰函数的源代码,没有!有没有改变调用方式,没有!还是index()调用。那么他就是装饰器思想了。index = time_count(index) 这里把最原始的index对象当做参数传进了time_count方法,在里面因为闭包原则,所以嵌套的函数wrapper里的func会读取外层函数的func,也就是传进去的最原始的index,那么func()就是调用了最原始的index函数。然后,index通过被赋值的形式,变成了等同于wrapper函数名,你现在调用index()就相当于调用了wrapper了

巧巧巧妙极了

img

**1.4完善装饰器

上述的装饰器,最后调用index()的时候,其实是在调用wrapper(),因此如果原始的index()有返回值的时候,wrapper()函数的返回值应该和index()的返回值相同,也就是说,我们需要同步原始的index()和wrapper()方法的返回值。

import time


def index():
    print('welcome to index')
    time.sleep(1)

    return 123


def time_count(func):
    # func = 最原始的index
    def wrapper():
        start = time.time()
        res = func()
        end = time.time()
        print(f"{func} time is {start-end}")

        return res
    return wrapper


index = time_count(index)
res = index()
print(f"res: {res}")

welcome to index
<function index at 0x102977620> time is -1.0050289630889893
res: 123

如果原始的index()方法需要传参,那么我们之前的装饰器是无法实现该功能的,由于有wrapper()=index(),所以给wrapper()方法传参即可。

import time


def index():
    print('welcome to index')
    time.sleep(1)

    return 123


def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)

    return name


def time_count(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print(f"{func} time is {start-end}")

        return res
    return wrapper


home = time_count(home)

res = home('egon')
print(f"res: {res}")

welcome egon to home page
<function home at 0x102977378> time is -1.0039079189300537
res: egon

懵逼了吧哈哈哈哈哈哈哈啊哈哈哈啊哈哈啊哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈嗝

img

其他还是一样,先看res = home('egon'),这里传了一个参数进去。那么里面就要接收,这列用*和**来接收,因为不知道要装饰的对象究竟回传几个参数进来,所以balabalabla。然后就一样了,没什么区别了。

1.5装饰器语法糖

‘’不行了,真的看不懂,看的想自杀。‘’

福音来了,如果你实在看不懂,那么会用就行了

‘’我干啊,这么长,又不懂,怎么用啊,我星星你星星个大星星!‘’

这不是有语法糖嘛

在被装饰函数正上方,并且是单独一行写上@装饰器名

import time


def time_count(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print(f"{func} time is {start-end}")

        return res
    return wrapper


@time_count  # home = time_count(home)
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)

    return name


@time_count  # index = time_count(index)
def index():
    print('welcome to index')
    time.sleep(1)

    return 123


res = home('egon')
print(f"res: {res}")

welcome egon to home page
<function home at 0x102977620> time is -1.0005171298980713
res: egon

只用在被装饰对象上面加一句@time_count,他就会把这个被装饰得对象塞进去,省去了index = time_count(index) 。

‘’就尼玛省了一句有毛用,你说你星星个大星星呢?‘’

这位同学在这么暴躁的话,我就要把你的头塞进屁股里了哦?

img

滚啊,是你自己的屁股里

1.6装饰器模板

def deco(func):
    def wrapper(*args,**kwargs):
        res = func(*args,**kwargs)
        return res
    return wrapper

就这个了啊,只需要和上面的语法糖配合起来,把你想要添加的功能写在wrapper函数里面就好了。

二、 有参装饰器

无参装饰器只套了两层,本节将讲一个套三层的装饰器——有参装饰器,但现在我们先实现一个用户登录注册的装饰器。

import time

current_user = {'username': None}


def login(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):
        if current_user['username']:
            res = func(*args, **kwargs)

            return res

        user = input('username: ').strip()
        pwd = input('password: ').strip()

        if user == 'nick' and pwd == '123':
            print('login successful')
            current_uesr['usre'] = user
            res = func(*args, **kwargs)

            return res
        else:
            print('user or password error')

    return wrapper


@login
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)

    return name


@login
def index():
    print('welcome to index')
    time.sleep(1)

    return 123


res = index()

username: nick
password: 123
login successful
welcome to index

懵逼。这里就是个上面一样的,只要你每一步都能看懂,到这里应该没什么问题,我说的。

不过你不可能每一部都看得懂,我写的我看得懂你们就不一定了,所以我比较推荐的是,如果你们看博客学习有困难,最好去听听老师讲的,或者搞个视频看。

对于上面的登录注册,我们把用户登录成功的信息写入内存当中。但是在工业上,用户信息可以存在文本中、mysql中、mongodb当中,但是我们只让用户信息来自于file的用户可以认证。因此我们可以改写上述的装饰器。

import time

current_user = {'username': None}


def login(func):
    # func = 最原始的index
    def wrapper(*args, **kwargs):

        if current_user['username']:
            res = func(*args, **kwargs)

            return res

        user = input('username: ').strip()
        pwd = input('password: ').strip()
        
        engine = 'file'

        if engine == 'file':
            print('base of file')
            if user == 'nick' and pwd == '123':
                print('login successful')
                current_uesr['usre'] = user
                res = func(*args, **kwargs)

                return res
            else:
                print('user or password error')
        elif engine == 'mysql':
            print('base of mysql')
        elif engine == 'mongodb':
            print('base of mongodb')
        else:
            print('default')

    return wrapper


@login
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)


@login
def index():
    print('welcome to index')
    time.sleep(1)


res = index()

username: nick
password: 123
base of file
login successful
welcome to index

所以,这是我们写死了的file,但是肯定有别的情况的,这个时候怎么办,当然是当做参数传进去,那么参数写在哪里呢,从哪里传进去呢?res = index()从这里?不行,所以只能在写一个闭包,就变成了三层闭包!

img

2.1三层闭包

def f1(y):

    def f2():
        x = 1

        def f3():
            print(f"x: {x}")
            print(f"y: {y}")
        return f3
    return f2


f2 = f1(2)
f3 = f2()
f3()

x: 1
y: 2

现在需求改了,我们需要判断用户动态的获取用户密码的方式,如果是file类型的,我们则让用户进行认证。因此我们可以使用有参装饰器。

import time

current_uesr = {'username': None}


def auth(engine='file'):

    def login(func):
        # func = 最原始的index
        def wrapper(*args, **kwargs):

            if current_user['username']:
                res = func(*args, **kwargs)

                return res

            user = input('username: ').strip()
            pwd = input('password: ').strip()

            if engine == 'file':
                print('base of file')
                if user == 'nick' and pwd == '123':
                    print('login successful')
                    current_uesr['usre'] = user
                    res = func(*args, **kwargs)

                    return res
                else:
                    print('user or password error')
            elif engine == 'mysql':
                print('base of mysql, please base of file')
            elif engine == 'mongodb':
                print('base of mongodb, please base of file')
            else:
                print('please base of file')

        return wrapper

    return login


@auth(engine='mysql')
def home(name):
    print(f"welcome {name} to home page")
    time.sleep(1)


@auth(engine='file')
def index():
    print('welcome to index')
    time.sleep(1)


res = index()

username: nick
password: 123
base of file
login successful
welcome to index

由于两层的装饰器,参数必须得固定位func,但是三层的装饰器解除了这个限制。我们不仅仅可以使用上述单个参数的三层装饰器,多个参数的只需要在三层装饰器中多加入几个参数即可。也就是说装饰器三层即可,多加一层反倒无用。

打住,这节就过了,放过自己,放过我

img

迭代器

什么是迭代,就是你爷爷生你老子,你老子生你,这就是迭代,是在循环,但不是毫无关系,没有你爷爷就没有你粑粑,没有你粑粑就没有你

while True:
    print('*'*10)

很明显,这就不是迭代,这是卡带。不停做毫无关系的循环,卡巴斯基了。

一、可迭代对象

python中一切皆是,如

x = 1
name = 'nick'
lis = [1, 2]
tup = (1, 2)
dic = {'name': 'nick'}
s1 = {'a', 'b'}


def func():
    pass


f = open('49w.txt', 'w', encoding='utf-8)

没错,函数也是对象

对于这一切的对象中,但凡有__iter__方法的对象,都是可迭代对象。

# x = 1.__iter__  # SyntaxError: invalid syntax

# 以下都是可迭代的对象

name = 'nick'.__iter__
lis = [1, 2].__iter__
tup = (1, 2).__iter__
dic = {'name': 'nick'}.__iter__
s1 = {'a', 'b'}.__iter__
f = open('49w.txt', 'w', encoding='utf-8')
f.__iter__
f.close()

1.1 总结

可迭代的对象:Python内置str、list、tuple、dict、set、file都是可迭代对象。

特点:

  1. 内置有__iter__方法的都叫可迭代的对象。

二、迭代器对象

这个是迭代器对象,不是可迭代对象,不一样的,怎么判断呢,就是看他有没有__next__方法。

为什么要搞这些迭代的东西,因为字符串和列表是按照索引取值的,但是其他的可迭代对象都时没有索引的,我们需要想出一个方法,让他们不依赖索引取值。

# 不依赖索引的数据类型迭代取值
dic = {'a': 1, 'b': 2, 'c': 3}
iter_dic = dic.__iter__()
print(iter_dic.__next__())
print(iter_dic.__next__())
print(iter_dic.__next__())
# print(iter_dic.__next__())  # StopIteration:

a
b
c

# 依赖索引的数据类型迭代取值
lis = [1, 2, 3]
iter_lis = lis.__iter__()
print(iter_lis.__next__())
print(iter_lis.__next__())
print(iter_lis.__next__())
# print(iter_lis.__next__())  # StopIteration:

1
2
3

2.1 总结

特点:

  1. 内置__next__方法,执行该方法会拿到迭代器对象中的一个值
  2. 内置有__iter__方法,执行该方法会拿到迭代器本身
  3. 文件本身就是迭代器对象。

缺点:

  1. 取值麻烦,只能一个一个取,并且只能往后取,值取了就没了
  2. 无法使用len()方法获取长度

三、for循环原理

for循环称为迭代器循环,in后必须是可迭代的对象。

lis = [1, 2, 3]
for i in lis:
    print(i)

1
2
3

然后又有钢筋要问了,诶,那文件是迭代器对象,怎么可以循环呢

因为文件对象也有__iter__方法,他会直接调用这个方法,然后变成可迭代对象。

因为迭代器使用__iter__后还是迭代器本身,因此for循环不用考虑in后的对象是可迭代对象还是迭代器对象。

由于对可迭代对象使用__iter__方法后变成一个迭代器对象,这个迭代器对象只是占用了一小块内存空间,他只有使用__next__后才会吐出一个一个值。如lis = [1,2,3,4,5,...]相当于一个一个鸡蛋,而lis = [1,2,3,4,5,...].__iter__相当于一只老母鸡,如果你需要蛋,只需要__next__即可。

img

让我们再度喊起口号 python教万岁,java洗洗睡,结束。

posted @ 2019-08-12 18:41  chanyuli  阅读(129)  评论(0编辑  收藏  举报