目录 

1.基础知识点

2.闭包

3.装饰器

 

正文

1.基础知识点------函数可以作为参数传给另一个函数

#将函数作为参数传给另一个函数
def test1(name):
    print("函数test1")
    return name()

def test2():
    print("函数test2")

test1(test2)
"""
函数test1
函数test2
"""

调用函数test1(test2),传入的参数是函数test2

注意test2作为参数,不能加(),才可以被到处传递。如果加了括号,就是调用test2函数了。

例如,下面的test2加括号,就是先调用test2()函数,再执行test1()函数了。

def test1(name):
    print("函数test1")

def test2():
    print("函数test2")

test1(test2())
"""
函数test2
函数test1
"""

 

2.闭包  Closure

①什么是闭包?

 ----嵌套函数:即外层函数里面又定义了一个内层函数

 ----内层函数调用了外层函数的非全局变量

 ----外层函数的返回值:是内层函数名

以上3个条件同时满足,就构成了一个闭包。

这么说有点抽象,举个栗子:

def outer(a):
    print("这是外层函数")
    def inner():
        b=a+1
        print("这是内层函数")
        print(b)
    return inner

outer(3)()

"""

这是外层函数
这是内层函数
4

"""

调用outer(3)得到的结果是打印【这是外层函数】并返回inner ;

outer(3)()就是在outer(3)的基础上再次调用内层函数,得到最后的结果。

 

上面的知识都基本理解了,下面讲解重点内容:装饰器

 

3.装饰器

①什么是装饰器?

装饰器本身也是一个函数或者类,可以新增一些被装饰函数/类的功能,简化代码。

②装饰器的分类:上面的定义可知,有函数装饰器 、类装饰器---------(被装饰的对象可以是函数,也可以是类)

注意:装饰器来装饰函数或者类,被装饰的函数或类的原功能是不变的,会增加一些新的功能;

           其次,被装饰的函数或者类的调用方式是不会变的-----切记

 

装饰器的语法糖

    被装饰的函数/类名 = 装饰器名(被装饰的函数/类名)

怎么理解这句话,来看个栗子:

#装饰器原理
def fun1(func): def fun2(): print("执行func()函数之前") func() print("执行func()函数之后") return fun2
def fun3(): print("这是功能函数") fun3() print("-------分割线---------") fun3=fun1(fun3) #被装饰的函数 = 装饰器名(被装饰的函数名) fun3() """ 这是功能函数 -------分割线--------- 执行func()函数之前 这是功能函数 执行func()函数之后 """

 

函数装饰器实例1-----被装饰的函数,无参数

def login(a):
    print("这是登录函数")
    def recharge():
        a()
        print("这是充值函数")
    return recharge

@login
def withdraw():
    print("这是取现函数")


withdraw()
"""
登录函数
取现函数
充值函数
"""
怎么理解上面的调用步骤:

根据语法糖公式:只要withdraw函数被装饰,那么@login 就等价于 withdraw = login(withdraw) ====》输出 "登录函数” 和 返回recharge变量

withdraw() = login(withdraw)()==recharge()=====>recharge()函数中的a()就是withdraw(),最后的结果如上。

 

函数装饰器实例2-----被装饰的函数,有参数

还是上面的装饰函数login()

def login(a):
    print("这是登录函数")
    def recharge():
        a()
        print("这是充值函数")
    return recharge

要对下面的函数withdraw(p,q)进行装饰,应该怎么修改上面的装饰函数?

def withdraw(p,q):
    print("这是取现函数")

修改步骤:

首先要装饰withdraw(p,q),先在函数上面添加@login;

那么,根据装饰器的语法糖公式:withdraw = login(withdraw)=recharge ;

要调用withdraw() ,那么withdraw(p,q) = login(withdraw)(p,q) =recharge(p,q)

所以,只要修改内层函数为recharge(j,k)即可完成装饰函数的修改。

#2.被装饰函数有参数
def login(a):
    print("这是登录函数")
    def recharge(j,k):
        print("这是充值函数")
        return a(j,k)

    return recharge

@login
def withdraw(p,q):
    print("这是取现函数")
    return p+q

print(withdraw(3,4))
"""
这是登录函数
这是充值函数
这是取现函数
7
"""

在实际项目应该中,参数可能有0个或者多个,那么就用到了可变长参数(动态参数):

元祖形式的可变长参数*args,   和字典形式的**kwargs,如下

添加计时器功能(自动化中用计时器的功能非常多,在这里提及一下)

#2.被装饰函数有参数
import time
def login(a):
    print("这是登录函数")
    def recharge(*args,**kwargs):
        time_before =time.time()
        res= a(*args,**kwargs)
        time_after = time.time()
        ti = time_after-time_before
        print(ti) #计时器功能
        print("这是充值函数")
        return res

    return recharge

@login
def withdraw(p,q):
    print("这是取现函数")
    return p+q


print(withdraw(3,4))
"""
这是登录函数
这是充值函数
0.0
这是取现函数
7
"""

 

函数装饰器实例3-----被装饰的类,无参数

def login(a):
    print("这是登录函数")
    def recharge():
        a()
        print("这是充值函数")
       
    return recharge

还是上面login这个函数装饰器,要对一个类进行装饰,应该怎么修改装饰器?

@login
class
MyClass: def withdraw(self): print("这是取现函数")

首先,在类的上面添加@login

类里面的实例方法的调用,首先要初始化一个对象MyClass(),对象再去调用里面的方法,即MyClass().withdraw()

同理,语法糖公式:MyClass = login(MyClass)=recharge 

那么 ,要初始化对象MyClass(),则MyClass()=login(MyClass)()=recharge (),即最后是调用recharge()函数,就是要修改内层函数。

那么怎么修改?最后要实现MyClass().withdraw(), 所以recharge()函数的返回值要是一个MyClass()对象,也就是return a() -----a是login的形参,装饰后传入的就是MyClass

#3被装饰的是一个类
def login(a):
    print("这是登录函数")
    def recharge():
        print("这是充值函数")
        return a()

    return recharge

@login   #MyClass() = login(MyClass)() =recharge()
class MyClass:
    def withdraw(self):
        print("这是取现函数")

MyClass().withdraw()
"""
这是登录函数
这是充值函数
这是取现函数
"""

 

⑤类装饰器实例1---装饰函数

魔术方法 :__call__(),只有在类的对象加上括号的时候,才会触发

举个栗子:

class MyClass:
    def __call__(self,*args,**kwargs):
        print("这个是魔术方法")


MyClass()()  #类的对象加上括号,触发call方法
#这个是魔术方法

那么,由上面的类,给下面的函数装饰,应该怎么修改类装饰器呢?

⑤类装饰器

@MyClass
def fun():  
    print("功能函数")

函数的调用就是fun()

公式: fun=MyClass(fun) -->则类里面要传个参数啊----》参数哪里传?__init__()传参数啊。即类里面添加一个__init__()进行初始化

又 fun()=MyClass(fun)(),即MyClass(fun)()对象又加一个括号,会触发call方法,所以要在call方法中去调用被装饰的函数,就可以实现不影响原功能函数的功能,又可以新增功能啦

class MyClass:

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

    def __call__(self,*args,**kwargs):
        self.a()
        print("这个是魔术方法")


@MyClass
def fun():  #fun()=MyClass(fun)()
    print("功能函数")

fun()
"""
功能函数
这个是魔术方法
"""

 

⑤类装饰器实例2---装饰类

class MyClass:

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

    def __call__(self,*args,**kwargs):
        print("这个是魔术方法")
        return self.a()


@MyClass  #B()=MyClass(B)()
class B:
    def fun(self):
        print("B类的实例方法")

类B中的方调用是B().fun()

B()=MyClass(B)(),触发call()方法,不影响原来的类的调用,在call中,直接将a()返回即可。

 

⑥对装饰器进行传参----定义三层函数,最外层函数的返回值是第二层函数名

在接口自动化中,数据驱动ddt,就是装饰器传参,对参数做了一系列的处理,将每条数据传入内层函数中。

下面来探究装饰器传参的原理。

def var(name):
    def test1(a):
        print("test1函数")
        def test2(b):
            for i in name:
                a(i)
            print("test2函数")
        return test2
    return test1

@var([1,2,3])  #var([1,2,3])=test1 ---> fun()=test1(fun)()=test2(b)
def fun(b):
    print("正在执行第{}条测试用例".format(b))

fun(2)
"""
test1函数
正在执行第1条测试用例
正在执行第2条测试用例
正在执行第3条测试用例
test2函数
"""

装饰器传参数,在最外层函数传参。

@var([1,2,3])传入参数是列表[1,2,3],  var([1,2,3])=test1,按照装饰器的语法糖,在内层函数中调用了a(),就是调用fun().

在这个基础上,增加了for循环。-----仿照ddt数据驱动

 

⑦多个装饰器的执行顺序-----------原理从下往上,执行从上往下

def test1(fun1):
    def wrapper1():
        fun1()
        print("装饰器1")
    return wrapper1

def test2(fun2):
    def wrapper2():
        fun2()
        print("装饰器2")
    return wrapper2

@test1     #wrapper2=test1(wrapper2)
@test2     #fun=test2(fun)=wrapper2
def fun():
    print("功能函数")

fun()

"""

功能函数
装饰器2
装饰器1

"""

 

原理讲解:fun()函数被test1()、test2()装饰,按照原理从下往上,fun函数先被test2()装饰,再被test1装饰时,test1装饰的其实就是wrapper2

装饰原理从下往上,写出公式。

 

@test1     #wrapper2=test1(wrapper2)
@test2     #fun=test2(fun)=wrapper2


执行按照从上往下:wrapper2()=test1(wrapper2)()
首先wrapper2()的执行结果是:
功能函数
装饰器2
再看,test1装饰wrapper2,即test1中转入的参数是wrapper2,最后输出的结果如上代码的输出结果

⑧装饰器的应用

1).计时          装饰器中实现计时功能,在自动化测试中,可以用来计时(装饰器中实现计时、调用原函数、计时,计算时差,实现计时功能)

2).前置、后置条件。装饰器中实现前置条件(比如打开浏览器),再调用原函数,实现后置条件(关闭浏览器)-----装饰器中调用原函数的前后各实现钱之后只条件即可

3).鉴权 比如登录 充值

   关联:只有先登录-->才能进行充值

 装饰器函数或者类,实现登录功能,获取token/session; 被装饰的充值函数,会自动获取token/session

 

 举个栗子(写个大致的过程):

#导入session类
from requests import Session

#初始化一个session对象
s=Session()

#装饰器-实现登录功能
def login_success(fun):
    URL = "www.douban.com"
    json={"username":"kkk","password":"123456"}
    method = "post"
    def wrapper():
        s.post(url=URL,json=json,method=method)
        fun()
    return wrapper
@login_success
def recharge(): res=s.post()

上面的例子,s这个对象属于全局变量,闭包使用的是非全局变量,那么可以用装饰器传参数的方式,将Session()对象作为参数,这样的话,闭包内部函数引用的就是外层函数的非全局变量。

如下:登录和充值是同一个session会话。-----登录装饰器给充值用例装饰

#导入session类
from requests import Session
#装饰器-实现登录功能
def var(s):
    def login_success(fun):
        URL = "www.douban.com"
        json={"username":"kkk","password":"123456"}
        method = "post"
        def wrapper():
            s.post(url=URL,json=json,method=method)
            fun()
        return wrapper
    return login_success

@var(Session())   #=@login_success
def recharge():
    res=s.post()