首页 > Python > Python的装饰器用法
2016
09-10

Python的装饰器用法

装饰器(Decorator)相对简单,咱们先介绍它:“装饰器的功能是将被装饰的函数当作参数传递给与装饰器对应的函数(名称相同的函数),并返回包装后的被装饰的函数”,听起来有点绕,没关系,直接看示意图,其中 a 为与装饰器 @a 对应的函数, b 为装饰器修饰的函数,装饰器@a的作用是: 

简而言之:@a 就是将 b 传递给 a(),并返回新的 b = a(b)

在python中常看到在定义函数是使用@func. 这就是装饰器, 装饰器是把一个函数作为参数的函数,常常用于扩展已有函数,即不改变当前函数状态下增加功能.

def run():
  print "I'm run."

 我有这么一个函数, 我想知道这个函数什么时候开始什么时候结束. 我应该这么写:

def run():
  print time.ctime()
  print "I'm run."
  print time.ctime()

但是如果不允许修改函数的话就需要装饰器了

def count(func):
  def wrapper():
    print time.ctime()
    ret = func()
    print time.ctime()
    return ret
  return wrapper
 
@count
def run():
  print "I'm run."

 再看一个例子:

def now():
  print '2016-9-10'
f = now
f()

函数有一个__name__ 对象 可通过 dir(func) func为定义的函数名

now.__name__    # print 'now'
f.__name__     # print 'now'
 
print f       # print '<function now at 0x000000000213A908>'
print now      # print '<function now at 0x000000000213A908>'

我们通过装饰器打印log日志

def log(func):
  def wrapper(*args, **kwargs):
    print "call %s()" % func.__name__
    return func(*args, **kwargs)
  return wrapper
 
@log
def now():
  print '2015-4-10'
 
now()        # print 'call now()'

其实装饰器修饰函数相当于, now = log(now) 也就是装饰器函数把被修饰的函数当参数后赋给同名的变量

思考的一个例子:用python 装饰器打log(不明白往后看)

#! /usr/bin/env python
# coding=utf-8
  
from time import time
def logged(when):
    def log(f,*args,**kargs):
        print("called: function:%s,args:%r,kargs:%r"%(f,args,kargs))
    def pre_logged(f):
        def wrapper(*args,**kargs):
            log(f,*args,**kargs)
            return f(*args,**kargs)
    def post_logged(f):
        def wrapped(*args,**kargs):
            now=time()
            try:
                return f(*args,**kargs)
            finally:
                log(f,*args,**kargs)
                print("time delta:%s"%(time()-now))
        return wrapped
    try:
        #从这里开始调用
        return{"pre":pre_logged,"post":post_logged}[when]
    except Exception as e:
        print(e)
  
@logged("post")
def hello(name):
    print("hello",name)
@logged("post")
def test(a,b=1):
    print(a+b)
  
hello("world")
test(1,2)

functools.wraps 函数

当我们使用了装饰器后now的__name__值发生了改变

# 没有使用前
now.__name__    # print 'now'
# 使用后
now.__name__    # print 'wrapper'

当我们使用装饰器前,now.__name__使用的是当前now函数,但使用后 now这个函数其实是 log(now) 也就是log函数的返回值也就是被包裹的wrapper. 解决方法是functools.wraps函数.

装饰闭包, 使用前得调用 import functools

def log(func):
  @functools.wraps(func)
  def wrapper(*args, **kwargs):
    ...

带参数的装饰器

如果decorator需要传入参数, 那就需要在写一个返回decorator的高阶函数. 写出来更复杂.

def login(level):
  def _deco(func):
    def wrapper(*args, **kwargs):
      if level >= 5:
        print '用户 VIP 等级 %d' % int(level-5)
      else:
        print '用户 屌丝 等级 %d' % abs(level-5)
      return func(*args, **kwargs)
    return wrapper
  return _deco
 
@login(5)
def user(username):
  print 'welcome, %s' % username
 
# 用户vip 等级0
# welcome, mink
user('mink')

带参数的decorator等于func = 装饰器函数(装饰器参数)(func)

------------------------------------------------------------------

# -*- coding:gbk -*- 
'''''示例3: 使用语法糖@来装饰函数,相当于“myfunc = deco(myfunc)”
但发现新函数只在第一次被调用,且原函数多调用了一次''' 
 
def deco(func): 
    print("before myfunc() called.") 
    func() 
    print("  after myfunc() called.") 
    return func 
 
@deco 
def myfunc(): 
    print(" myfunc() called.") 
 
myfunc()

------------------------------------------------------------------------------

# -*- coding:gbk -*- 
'''''示例4: 使用内嵌包装函数来确保每次新函数都被调用,
内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象''' 
 
def deco(func): 
    def _deco(): 
        print("before myfunc() called.") 
        func() 
        print("  after myfunc() called.") 
        # 不需要返回func,实际上应返回原函数的返回值 
    return _deco 
 
@deco 
def myfunc(): 
    print(" myfunc() called.") 
    return 'ok' 
 
myfunc()   

------------------------------------------------------------------

# -*- coding:gbk -*- 
'''''示例5: 对带参数的函数进行装饰,
内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象''' 
 
def deco(func): 
    def _deco(a, b): 
        print("before myfunc() called.") 
        ret = func(a, b) 
        print("  after myfunc() called. result: %s" % ret) 
        return ret 
    return _deco 
 
@deco 
def myfunc(a, b): 
    print(" myfunc(%s,%s) called." % (a, b)) 
    return a + b 
 
myfunc(1, 2) 

--------------------------------------------------------------------------

def deco(func): 
    def _deco(*args, **kwargs): 
        print("before %s called." % func.__name__) 
        ret = func(*args, **kwargs) 
        print("  after %s called. result: %s" % (func.__name__, ret)) 
        return ret 
    return _deco 
 
@deco 
def myfunc(a, b): 
    print(" myfunc(%s,%s) called." % (a, b)) 
    return a+b 
 
@deco 
def myfunc2(a, b, c): 
    print(" myfunc2(%s,%s,%s) called." % (a, b, c)) 
    return a+b+c 
 
myfunc(1, 2) 
myfunc(3, 4) 
myfunc2(1, 2, 3) 

-----------------------------------------------------------------

# -*- coding:gbk -*- 
'''''示例7: 在示例4的基础上,让装饰器带参数,
和上一示例相比在外层多了一层包装。
装饰函数名实际上应更有意义些''' 
 
def deco(arg): 
    def _deco(func): 
        def __deco(): 
            print("before %s called [%s]." % (func.__name__, arg)) 
            func() 
            print("  after %s called [%s]." % (func.__name__, arg)) 
        return __deco 
    return _deco 
 
@deco("mymodule") 
def myfunc(): 
    print(" myfunc() called.") 
 
@deco("module2") 
def myfunc2(): 
    print(" myfunc2() called.") 
 
myfunc() 
myfunc2() 

------------------------------------------------------------

# -*- coding:gbk -*- 
'''''示例7: 在示例4的基础上,让装饰器带参数,
和上一示例相比在外层多了一层包装。
装饰函数名实际上应更有意义些''' 
 
def deco(arg): 
    def _deco(func): 
        def __deco(): 
            print("before %s called [%s]." % (func.__name__, arg)) 
            func() 
            print("  after %s called [%s]." % (func.__name__, arg)) 
        return __deco 
    return _deco 
 
@deco("mymodule") 
def myfunc(): 
    print(" myfunc() called.") 
 
@deco("module2") 
def myfunc2(): 
    print(" myfunc2() called.") 
 
myfunc() 
myfunc2() 

------------------------------------------------------

装饰器类

通过类的__call__可以想使用函数一样使用类

class A(object):
  def __init__(self, func):
    self.func = func
 
  def __call__(self):
    return self.func() ** 2
 
@A
def foo():
  return 10
 
print foo()   # print 100

作者:admin
admin
TTF的家园-www.ttfde.top 个人博客以便写写东西,欢迎喜欢互联网的朋友一起交流!

本文》有 0 条评论

留下一个回复