51Testing软件测试论坛

标题: 省时省力!能够简化自动化代码的神器使用指南 [打印本页]

作者: lsekfe    时间: 2020-11-26 10:36
标题: 省时省力!能够简化自动化代码的神器使用指南
我们在进行自动化代码编写的时候,经常会使用一些必不可少的功能,比如统计函数运行时长、检测性能或者打印日志;这个时候,使用Python装饰器就能极大地简化代码,避免编写大量的重复代码。
  Python装饰器就是对原有函数的一种装饰,在不改变原函数代码的前提下,给函数增加新的功能;decorator本质上就是一个高阶函数,它接收一个函数作为参数,然后,返回一个新函数。
  如下,我们现在有一个函数test(a,b),它的功能时求a,b的差值:
  1. def test(a, b):

  2.     time.sleep(1)

  3.     return a-b
复制代码
现在有一个新的需求,就是计算一下该函数的执行时间;一般来说,我们可以直接在函数的基础上进行修改,代码如下:
  1. def test(a, b):

  2.     start_time = datetime.datetime.now()

  3.     time.sleep(1)

  4.     end_time = datetime.datetime.now()

  5.     interval = (end_time-start_time).seconds

  6.     return a-b, interval

  7. test(1, 2)
复制代码
对于一个函数来说,这样的写法是没有问题的;但是,我们试想一下,假设有很多函数,每个函数都要求运行时间,就要一个个的单独修改,耗时耗力;此时,我们也可以独立封装一个计算时间差的函数time_calculation(func,*args,**kw),将test函数作为参数传递,代码如下:
  1. def test(a, b):

  2.     time.sleep(1)

  3.     return a-b

  4. def time_calculation(func, a, b):

  5.     start_time = datetime.datetime.now()

  6.     result = func(a, b)

  7.     end_time = datetime.datetime.now()

  8.     interval = (end_time - start_time).seconds

  9.     return result, interval

  10. time_calculation(test, 1, 2)
复制代码
当然,此时函数调用就由test()变成了time_calculation();如果多处调用了test函数,我们就要一个个的修改成time_calculation(),一样是很麻烦。如果不想修改代码,我们就要保证调用test()和调用time_calculation()效果是一样的;我们可以将传入的test函数包装一下,返回一个新的函数,再把这个函数返回赋值给test,代码如下:
  1. def test(a, b):

  2.     time.sleep(1)

  3.     return a-b

  4. def time_calculation(func):

  5.     def new_func(a, b):

  6.         start_time = datetime.datetime.now()

  7.         f = func(a, b)

  8.         end_time = datetime.datetime.now()

  9.         interval = (end_time - start_time).seconds

  10.         print('运行时间为 %f ms' % interval)

  11.         return f

  12.     return new_func

  13. test = time_calculation(test)

  14. test(1, 2)
复制代码
编写无参数decorator  以上的例子就是一个装饰器的概念,我们使用Python提供的?@?语法,对上面的代码进行精简,这样可以避免手动编写?f=decorate(f)?这样的代码:
  以上装饰器只能传入两个参数a和b,而我们编写装饰器是为了让其自适应任意函数,方便调用,可以利用Python的*args?和**kw,保证任意个数的参数总是能正常调用:
  1. def time_calculation(func):

  2.     def new_func(a, b):

  3.         start_time = datetime.datetime.now()

  4.         f = func(a, b)

  5.         end_time = datetime.datetime.now()

  6.         interval = (end_time - start_time).seconds

  7.         print('运行时间为 %f ms' % interval)

  8.         return f

  9.     return new_func

  10. @time_calculation

  11. def test(a, b):

  12.     time.sleep(1)

  13.     return a-b

  14. test(1,2)
复制代码

编写带参数decorator  以上就是一个无参数的decorator,可以适用于任意函数的装饰;但是我们发现,对于被装饰的函数,我们想打印额外的信息是无法做到的;比如说,对于某些函数,希望打印出'[INFO]xxx()...',有的函数,希望打印出'[ERROR]xxx()...',这时,被修饰函数本身就需要传入’INFO’或’ERROR’这样的参数;此时,就需要编写一个带参数的decorator,带参数的time_calculation函数首先返回一个decorator函数,再让这个decorator函数接收新的函数并返回。
  对于上边的函数,根据传入参数确定输出的是毫秒还是秒,代码如下:
  1. def time_calculation(unit):

  2.     def time_decorator(func):

  3.         def new_func(*args, **kw):

  4.             start_time = datetime.datetime.now()

  5.             f = func(*args, **kw)

  6.             end_time = datetime.datetime.now()

  7.             interval = (end_time - start_time).seconds

  8.             if unit == 'ms':

  9.                 interval = (end_time - start_time).seconds*1000

  10.             print('运行时间为 %f %s' % (interval, unit))

  11.             return f

  12.         return new_func

  13.     return time_decorator

  14. @time_calculation('ms')

  15. def test(a, b):

  16.     time.sleep(1)

  17.     return a-b

  18. test(1, 2)
复制代码
完善的decorator  由于装饰器返回的新函数函数名已经不是'func',而是@time_calculation内部定义的'time_decorator'。这对于那些依赖函数名的代码就会失效。装饰器还改变了函数的__doc__等其它属性。如果要让调用者看不出一个函数经过了@decorator的“改造”,就需要在返回把原函数的一些属性复制到新函数中:
  1. new_func.__name__ = func.__name__

  2. new_func.__doc__ = func.__doc__
复制代码
这样写decorator很不方便,因为我们也很难把原函数的所有必要属性都一个一个复制到新函数上,所以Python内置的functools可以用来自动化完成这个“复制”的任务:
  1. def time_calculation(unit):

  2.     def time_decorator(func):

  3. @functools.wraps(func) # functool用于保持原函数的部分属性值不变

  4.         def new_func(*args, **kw):

  5.             start_time = datetime.datetime.now()

  6.             f = func(*args, **kw)

  7.             end_time = datetime.datetime.now()

  8.             interval = (end_time - start_time).seconds

  9.             if unit == 'ms':

  10.                 interval = (end_time - start_time).seconds*1000

  11.             print('运行时间为 %f %s' % (interval, unit))

  12.             return f

  13.         return new_func

  14.     return time_decorator

  15. @time_calculation('ms')

  16. def test(a, b):

  17.     time.sleep(1)

  18.     return a-b

  19. test(1, 2)
复制代码
最后需要指出,由于我们把原函数签名改成了(*args,**kw),因此,无法获得原函数的原始参数信息。即便我们采用固定参数来装饰只有一个参数的函数:也可能改变原函数的参数名,因为新函数的参数名始终是'x',原函数定义的参数名不一定叫'x'。  多个装饰器
  一个函数可以支持多个装饰器,从最后一个装饰器开始,执行到第一个装饰器,再执行函数本身。
  1. def decorator1(func):

  2.     print('执行decorator1')

  3.     def wrapper1():

  4.         print('hello world 前')

  5.         func()

  6.     return wrapper1

  7. def decorator2(func):

  8.     print('执行decorator2')

  9.     def wrapper2():

  10.         func()

  11.         print('hello world 后')

  12.     return wrapper2

  13. @decorator1

  14. @decorator2

  15. def test():

  16.     print('hello world')



  17. test()
复制代码
以上相当于执行了test=dect1(dect2(test)),此时先执行dect2(test),结果是输出“执行decorator2”,返回wrapper2,然后执行dect1(wrapper2),结果是输出“执行decorator1”、将func指向函数wrapper2、并返回函数wrapper1,然后进行赋值。我们可以对以上代码进行DEBUG调试,就明白了他的执行轨迹。  Python内置装饰器
  Python内置的装饰器有三个:staticmethod,classmethod和property。
  @staticmethod
  将类中的方法装饰为静态方法,可以通过类直接调用,也可以通过实例化调用,不需要传入self。
  1. class TestClass:

  2.     def __init__(self, name):

  3.         self.name = name

  4.     @staticmethod

  5.     def test(a, b):

  6.         return a+b

  7. TestClass.test(1, 2)
复制代码
@classmethod  定义为类方法,使用classmethod装饰的方法可以使用类或者类的实例对象来调用,第一个参数需要是表示自身类的cls参数,可以来调用类的属性,类的方法,实例化对象等。
  1. class Test(object):

  2.     @classmethod

  3.     def value(cls, cate):  # 可定义多个参数,但第一个参数为类本身

  4.         print("%s of %s" % (cate, cls.car))

  5. class Test1:

  6.     car = "BMW"


  7. Test1.value("SUV")
复制代码

@property  把方法变成属性,使调用类中的方法像引用类中的字段属性一样。被修饰的特性方法,内部可以实现处理逻辑,但对外提供统一的调用方式。遵循了统一访问的原则。@property可以用来实现类似与Java中set和get方法。
  1. class TestClass:

  2.     def __init__(self, name):

  3.         self.name = name

  4.     @property

  5.     def test(self):

  6.         return self.name

  7. cls = TestClass("login")

  8. print("通过实例引用属性:" + cls.name)

  9. print("引用test方法" + cls.test)
复制代码























欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/) Powered by Discuz! X3.2