python 装饰器常见用法

跟踪调用

class tracer: # State via instance attributes
    def __init__(self, func): # On @ decorator
        self.calls = 0 # Save func for later call
        self.func = func
    def __call__(self, *args, **kwargs): # On call to original function
        self.calls += 1
        print('call %s to %s' % (self.calls, self.func.__name__))
        return self.func(*args, **kwargs)

用例:

@tracer
def spam(a, b, c): # Same as: spam = tracer(spam)
    print(a + b + c) # Triggers tracer.__init__

@tracer
def eggs(x, y): # Same as: eggs = tracer(eggs)
    print(x ** y) # Wraps eggs in a tracer object

spam(1, 2, 3) # Really calls tracer instance: runs tracer.__call__
spam(a=4, b=5, c=6) # spam is an instance attribute
eggs(2, 16) # Really calls tracer instance, self.func is eggs
eggs(4, y=4) # self.calls is per-function here (need 3.0 nonlocal)

output:
    call 1 to spam
    6
    call 2 to spam
    15
    call 1 to eggs
    65536
    call 2 to eggs
256

计时调用

import time

def timer(label='', trace=True): # On decorator args: retain args
    class Timer:
        def __init__(self, func): # On @: retain decorated func
            self.func = func
            self.alltime = 0
        def __call__(self, *args, **kargs): # On calls: call original
            start = time.clock()
            result = self.func(*args, **kargs)
            elapsed = time.clock() - start
            self.alltime += elapsed
            if trace:
                format = '%s %s: %.5f, %.5f'
                values = (label, self.func.__name__, elapsed, self.alltime)
                print(format % values)
                return result
    return Timer

用例:

@timer(label='[CCC]==>')
def listcomp(N): # Like listcomp = timer(...)(listcomp)
    return [x * 2 for x in range(N)] # listcomp(...) triggers Timer.__call__

@timer(trace=True, label='[MMM]==>')
def mapcall(N):
    return map((lambda x: x * 2), range(N))
for func in (listcomp, mapcall):
    print('')
    result = func(5) # Time for this call, all calls, return value
    func(50000)
    func(500000)
    func(1000000)
    print(result)
    print('allTime = %s' % func.alltime) # Total time for all calls
print('map/comp = %s' % round(mapcall.alltime / listcomp.alltime, 3))

output:
    [CCC]==> listcomp: 0.00003, 0.00003
    [CCC]==> listcomp: 0.00640, 0.00643
    [CCC]==> listcomp: 0.08687, 0.09330
    [CCC]==> listcomp: 0.17911, 0.27241
    allTime = 0.272407666337
    [MMM]==> mapcall: 0.00004, 0.00004
    [MMM]==> mapcall: 0.01340, 0.01343
    [MMM]==> mapcall: 0.13907, 0.15250
    [MMM]==> mapcall: 0.27907, 0.43157
    [0, 2, 4, 6, 8]
    allTime = 0.431572169089
    map/comp = 1.584
[0, 2, 4, 6, 8]

单例

class singleton:
    def __init__(self, aClass): # On @ decoration
        self.aClass = aClass
        self.instance = None
    def __call__(self, *args): # On instance creation
        if self.instance == None:
            self.instance = self.aClass(*args) # One instance per class
    return self.instance

用例:

@singleton
class Test:
    def __init__(self,**args):
        self.a = args["a"]
        self.b = args["b"]
def main():
    t1 = Test(**{"a":1,"b":2})
    print("a:",t1.a,"b:",t1.b)
    t2 = Test()
    print("a:",t2.a,"b:",t2.b)
   
output:
    ('a:', 1, 'b:', 2)
    ('a:', 1, 'b:', 2)

跟踪对象接口

def Tracer(aClass): # On @ decorator
    class Wrapper:
        def __init__(self, *args, **kargs): # On instance creation
            self.fetches = 0
            self.wrapped = aClass(*args, **kargs) # Use enclosing scope name
        def __getattr__(self, attrname):
            print('Trace: ' + attrname) # Catches all but own attrs
            self.fetches += 1
            return getattr(self.wrapped, attrname) # Delegate to wrapped obj
    return Wrapper

用例:

@Tracer
class Spam: # Spam = Tracer(Spam)
    def display(self): # Spam is rebound to Wrapper
    print('Spam!' * 8)

@Tracer
class Person: # Person = Tracer(Person)
    def __init__(self, name, hours, rate): # Wrapper remembers Person
        self.name = name
        self.hours = hours
        self.rate = rate
    def pay(self): # Accesses outside class traced
        return self.hours * self.rate # In-method accesses not traced

food = Spam() # Triggers Wrapper()
food.display() # Triggers __getattr__
print([food.fetches])
bob = Person('Bob', 40, 50) # bob is really a Wrapper
print(bob.name) # Wrapper embeds a Person
print(bob.pay())
print('')
sue = Person('Sue', rate=100, hours=60) # sue is a different Wrapper
print(sue.name) # with a different Person
print(sue.pay())
print(bob.name) # bob has different state
print(bob.pay())
print([bob.fetches, sue.fetches]) # Wrapper attrs not traced

output:
    Trace: display
    Spam!Spam!Spam!Spam!Spam!Spam!Spam!Spam!
    [1]
    Trace: name
    Bob
    Trace: pay
    2000
    Trace: name
    Sue
    Trace: pay
    6000
    Trace: name
    Bob
    Trace: pay
    2000
    [4, 2]

管理函数和类

def register(label="",registerd = False):
    def onRegister(func):
        key = label if label else func.__name__
        if registerd:
            registerMap[key] = func
        return func
    return onRegister

用例:

@register(registerd=True)
def test1():
    print("test1")

@register(label="modify")
def test2():
    print("test2")

@register(label="test1",registerd=True)
def test3():
    print("test3")

print getInstance("test1")
print getInstance("test2")
print getInstance("modify")

output:
    <function test3 at 0x00000000024C1DD8>
    ERROR:root:no attribute registered!
    None
    ERROR:root:no attribute registered!
None

验证函数参数

def rangetest(**argchecks): 
        def onDecorator(func): 
                code = func.__code__
                allargs = code.co_varnames[:code.co_argcount]
                funcname = func.__name__
                def onCall(*pargs, **kargs):
                    positionals = list(allargs)
                    positionals = positionals[:len(pargs)]
                    for (argname, (low, high)) in argchecks.items():
                        # For all args to be checked
                        if argname in kargs:
                            # Was passed by name
                            if kargs[argname] < low or kargs[argname] > high:
                                errmsg = '{0} argument "{1}" not in {2}..{3}'
                                errmsg = errmsg.format(funcname, argname, low, high)
                                raise TypeError(errmsg)
                            elif argname in positionals:
                                # Was passed by position
                                position = positionals.index(argname)
                                if pargs[position] < low or pargs[position] > high:
                                    errmsg = '{0} argument "{1}" not in {2}..{3}'
                                    errmsg = errmsg.format(funcname, argname, low, high)
                                    raise TypeError(errmsg)
                            else:
                                # Assume not passed: default
                                if True:
                                    print('Argument "{0}" defaulted'.format(argname))
                            return func(*pargs, **kargs) # OK: run original call
                return onCall
        return onDecorator

用例:

class Person:
    def __init__(self, name, job, pay):
        self.job = job
        self.pay = pay
    # giveRaise = rangetest(...)(giveRaise)
    @rangetest(percent=(0.0, 1.0)) # percent passed by name or position
    def giveRaise(self, percent):
        self.pay = int(self.pay * (1 + percent))

bob = Person('Bob Smith', 'dev', 100000)
sue = Person('Sue Jones', 'dev', 100000)
bob.giveRaise(.10)
sue.giveRaise(percent=.20)
print(bob.pay, sue.pay)
bob.giveRaise(1.10)
bob.giveRaise(percent=1.20)
# Test omitted defaults: skipped

output:
    Argument "percent" defaulted
    (100000, 120000)
    Traceback (most recent call last):
      File "d:/huyaowen/workspace/demo/rangetest.py", line 54, in <module>
        main()
      File "d:/huyaowen/workspace/demo/rangetest.py", line 50, in main
        bob.giveRaise(percent=1.20)
      File "d:/huyaowen/workspace/demo/rangetest.py", line 18, in onCall
        raise TypeError(errmsg)
    TypeError: giveRaise argument "percent" not in 0.0..1.0

参数类型检测

def typetest(**argchecks):
    def onDecorator(func):
            code = func.__code__ # __code__ 返回已编译的函数对象
            allargs = code.co_varnames[:code.co_argcount]
            funcname = func.__name__
            def onCall(*pargs, **kargs):
                positionals = list(allargs)[:len(pargs)]
                for (argname, expectedtype) in argchecks.items():
                    # 检测参数是否在关键字参数中
                    if argname in kargs:
                        if not isinstance(kargs[argname], expectedtype):
                            errmsg = '{0} argument "{1}" type is {2} ,not the expected type {3}'
                            errmsg = errmsg.format(funcname, argname, type(kargs[argname]), expectedtype)
                            raise TypeError(errmsg)
                    # 检测参数是否在位置参数中
                    elif argname in positionals:
                        position = positionals.index(argname)
                        if not isinstance(pargs[position], expectedtype):
                            errmsg = '{0} argument "{1}" type is {2} ,not the expected type {3}'
                            errmsg = errmsg.format(funcname, argname, type(pargs[position]), expectedtype)
                            raise TypeError(errmsg)
                    else:
                        pass
                return func(*pargs, **kargs)
            return onCall
    return onDecorator

用例:

@typetest(a=int, c=float)
def func(a, b, c, d):
    print(a,b,c,d)

func(1, 2, 3.0, 4) # Okay
func('spam', 2, 99, 4)

output:
    (1, 2, 3.0, 4)
    Traceback (most recent call last):
      File "d:/huyaowen/workspace/demo/typetest.py", line 38, in <module>
        main()
      File "d:/huyaowen/workspace/demo/typetest.py", line 35, in main
        func('spam', 2, 99, 4)
      File "d:/huyaowen/workspace/demo/typetest.py", line 22, in onCall
        raise TypeError(errmsg)
    TypeError: func argument "a" type is <type 'str'> ,not the expected type <type 'int'>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容