装饰器

装饰是为函数和类指定管理代码的一种方式.装饰器本身的形式是处理其他的可调用对象的可调用的对象。

  • 函数装饰器在函数定义的时候进行名称重绑定,提供一个逻辑层来管理函数和方法或随后对他们的调用
  • 类装饰器在类定义的时候进行名称重绑定,提供一个逻辑层来管理类,或管理随后调用他们所创建的示例
基础知识
@decorator
def F(arg):pass
F(99)   #调用函数

def F(arg):pass
F = decorator(F)
F(99)    #调用函数
这一自动名称重绑定在def语句上有效,不管它针对一个简单的函数或是类中的一个方法。当随后调用F函数的时候,它自动调用装饰器所返回的对象,该对象可能是实现了所需的包装逻辑的另一个对象,或者是最初的函数本身。

装饰器在装饰的时候调用,并且当随后调用最初的函数名的时候,它所返回的调用对象将被调用。装饰器本身接收被装饰的函数,返回的调用对象会接收随后传递给给装饰函数的名称的任何参数。

def decorator(F):
    def wrapper(*args):
        print(args)
    return wrapper
@decorator
def func(x,y):
    pass
if __name__ == '__main__':
    func(6,7)    #调用方法实际上调用的是wrapper(6,7)
    #func = decorator(func)  #func现在是decorator返回的包装器函数wrapper
    #func(6,7)
class decorator:
    def \_\_init__(self,func):
        self.func = func
    def \_\_call__(self,*args):
        print(args)
        self.func(*args)
@decorator
def func(x,y):
    print(x+y)
if __name__ == '__main__':
    func(6,7)
###
现在,随后在调用func的时候,它确实会调用装饰器所创建的实例\__call__运算符重载方法;然后\__call__方法可能运行最初的func,因为它在一个实例属性中仍然可用。当按照这种方式编写代码的时候,每个装饰的函数都会产生一个新的实例来保持状态

--------------------------------------------------------------------------
def decorator(F):
    def wrapper(*args):
        F(*args)
    return wrapper
class C:
    @decorator
    def method(self,x,y):
        print(x*y)
if __name__ == '__main__':
    c = C()
    c.method(6,7)
       42

类装饰器

基础知识

@decorator
class C:
    ....
x = C(99)
相当于
class C:......
C = decorator(C)
x = C(99)
也是返回一个可调用对象的一个可调用对象,因此大多数函数和类的组合已经足够了

一个例子

#-*-coding:UTF-8-*-

def decorator(cls):
    class Wrapper:
        def __init__(self, *arg):
            self.wrappered = cls(*arg)
        def __getattr__(self,name):
            return getattr(self.wrappered,name)
    return Wrapper
@decorator
class C:
    def __init__(self,x,y):
        self.attr = 'spam'
if __name__ == '__main__':
    x = C(6,7)
    print(x.attr)
"""
这个例子中,装饰器把C类的名称重新绑定到另一个类(Wrapper),这个类(Wrapper)在一个封闭的作用域中保持了最初的类(C)
,并且当调用它的时候,创建并嵌入了最初的类的一个实例。
当随后从该实例获取一个属性的时候,包装器的__getattr__拦截了它,并且将其委托给最初的类的嵌入的实例
"""

装饰器嵌套
语法

@A
@B
@C
def f(...):
  ...
相当于:
def f(...):....
f = A(B(C(f)))

例如:

def d1(F):return lambda:'X'+F()
def d2(F):return lambda:'Y'+F()
def d3(F):return lambda:'Z'+F()
@d1
@d2
@d3
def func():
    return 'spam'
if __name__ == '__main__':
    print(func())
>>>XYZspam

装饰器参数
函数装饰器和类装饰器似乎都能接受参数,尽管实际上这些参数传递给了真正返回装饰器的一个可回调对象,而装饰器反过来又返回一个可调用对象
语法

@decorator(A,B)
def F(arg):...
F(99)
自动映射到其对等的形式,其中装饰器是一个可调用对象,它返回实际的装饰器。返回的装饰器反过来返回可调用的对象
def F(arg):
  ...
F = decorator(A,B)(F)
F(99)

跟踪调用的例子

"""
这个类装饰的每个函数将如何创建一个新的实例,带有自己保存的函数对象和
调用计数器。还要注意观察,*args参数语法如何来打包和解包任意的多个
传入参数。这个类装饰器可以用来包装带有任意多个参数的任何函数
"""
class tracer:
    def __init__(self,func):
        self.calls = 0
        self.func = func  #把被包装的函数存储为自己的实例属性
    def __call__(self,*args):
        self.calls += 1
        print('call %s to %s'%(self.calls,self.func.\__name__))
        self.func(*args)
@tracer
def spam(a,b,c):
    print(a+b+c)
if __name__ == '__main__':
    spam(1,2,3)
>>>
call 1 to spam
6
###
类装饰器tracer不适合装饰其他类中的方法,例如
class Person:
    """docstring for Person"""
    def __init__(self,name,pay):
        self.name = name
        self.pay= pay
    @tracer
    def giveRaise(self,percent):
        self.pay*=(1.0+percent)

tracer类的call方法的self是一个tracer实例,没有在参数列表中传递Person主体

使用嵌套函数来装饰方法
如果想让函数小黄时期在简单函数和类方法上都能工作,最直接的解决方法在于把自己的函数装饰器编写成嵌套的def,以便对于包装器类和主类实例都不依赖单个的self实例参数

#-*-coding:UTF-8-*-
def tracer(func):
    calls = 0
    def onCall(*args,**kwargs):
        nonlocal calls
        calls+=1
        print('call %s to %s'%(calls,func.__name__))
        return func(*args,**kwargs)
    return onCall
@tracer
def spam(a,b,c):
    print(a+b+c)

class Person:
    def __init__(self,name,pay):
        self.name = name
        self.pay = pay
    # @tracer
    def giveRaise(self,percent):
        self.pay *= (1.0+percent)
    @tracer
    def lastName(self):
        return self.name.split()[-1]
if __name__ == '__main__':
    spam(1,2,3)
    bob = Person('Bob Smith',50000)
    bob.giveRaise = tracer(bob.giveRaise) #装饰器的原型
    bob.giveRaise(.10)
    print(int(bob.pay))

**添加装饰器参数

#-*-coding:UTF-8-*-
import time
def timer(label='',trace=True):
    class Timer:
        def __init__(self,func):
            self.func = func
            self.alltime = 0
        def __call__(self,*args,**kargs):
            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):
    return [x * 2 for x in range(N)]
if __name__ == '__main__':
    print(listcomp(50))

编写类装饰器

def Tracer(aClass):
    class Wrapper:
        def __init__(self,*args,**kargs):
            self.fetches = 0
            self.wrapped = aClass(*args,**kargs) #实例化aClass类的一个实例
        def __getattr__(self,attrname):
            print('Trace: '+attrname)
            self.fetches += 1
            return getattr(self.wrapped,attrname)  #调用self.wrapped的attrname方法(属性)
    return Wrapper

"""
类装饰器 编写为一个类的情况
class Tracer:
    def __init__(self,aClass):          #运行到@的时候会触发\__init__
        self.aClass = aClass
    def __call__(self,*args):           #下面实例化主类的时候,Spam()会调用\__call__。需要返回一个主类的对象
        self.wrapped = self.aClass(*args)
        return self
    def __getattr__(self,attrname):
        print('Trace: '+attrname)
        return getattr(self.wrapped,attrname)
        
"""
@Tracer
class Spam:
    def display(self,a):      #添加一个参数
        print('Spam '*8+a)
@Tracer
class Person:
    def __init__(self,name,hours,rate):
        self.name = name
        self.hours = hours
        self.rate = rate
    def pay(self):
        return self.hours * self.rate

if __name__ == '__main__':
    food = Spam()
    food.display('a')       #必须提供一个参数
    print(food.fetches)
    bob = Person('Bob',40,50)
    print(bob.name)

实现私有属性

#-*-coding:UTF-8-*-

traceMe = False
def trace(*args):
    if traceMe:print('['+' '.join(map(str,args))+']')

def Private(*privates):
    print(privates)
    def onDecorator(aClass):
        class onInstance:
            def __init__(self,*args,**kargs):
                self.wrapped = aClass(*args,**kargs) #自己初始化是也会调用__setattr__
                print(aClass.__name__)
            def __getattr__(self,attr):
                trace('get:',attr)
                if attr in privates:
                    raise TypeError('private attr')
                else:
                    return getattr(self.wrapped,attr)
            def __setattr__(self,attr,value):
                trace('set: ',attr,value)
                print('--->',attr)
                if attr == 'wrapped':
                    self.__dict__[attr] = value
                elif attr in privates:
                    raise TypeError('private attr change')
                else:
                    setattr(self.wrapped,attr,value)
        return onInstance
    return onDecorator

if __name__ == '__main__':
    traceMe = True
    @Private('data','size')
    class Doubler:
        def __init__(self,label,start):
            self.label = label
            self.data = start
        def size(self):
            return len(self.data)
        def double(self):
            for i in range(self.size()):
                self.data[i] = self.data[i] * 2
        def display(self):
            print('%s=>%s'%(self.label,self.data))
    """
    Doubler = Private('data','size')(Doubler)
    Private('data','size') 返回的是onDecorator函数,而此函数接收一个类
    并在该函数返回一个包装类onInstance,onInstance在封闭的onDecorator函数中
    aClass是传递给onDecorator函数的参数,可以在onInstance内部使用aClass
    """
    X = Doubler('X is',[1,2,3])
    print(X.label)
    X.display()
    # X.data = [1,2,3,4]
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容

  • 每个人都有的内裤主要功能是用来遮羞,但是到了冬天它没法为我们防风御寒,咋办?我们想到的一个办法就是把内裤改造一下,...
    chen_000阅读 1,360评论 0 3
  • **装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是...
    牛崽儿酷阅读 316评论 0 0
  • 元类与装饰器的关系 在class语句的末尾,类装饰器把类名重新绑定到一个函数的结果。 元类通过一条class语句的...
    低吟浅唱1990阅读 385评论 0 0
  • Python装饰器的高级用法(翻译) 原文地址https://www.codementor.io/python/t...
    城南道阅读 4,805评论 1 22
  • 当阳光穿越万水千山,俯首窗前,亲昵枯萎的玫瑰花时,早上的困乏随着这抹浪漫烟消云散,在喝完杯中最后一滴牛奶后,我决定...
    文艺到老阅读 475评论 0 2