python装饰器

装饰器简述

要理解装饰器需要知道Python高阶函数python闭包,Python高阶函数可以接受函数作为参数,也可以返回函数,闭包的内部函数可以访问外部函数的局部变量。Python的装饰器正式基于高阶函数和闭包。

来看一个简单的装饰器:

def dec_func(fun):
    def wrapper():
        print('In Wapper!')
        fun()
        print('After fun')
    return wrapper
@dec_func
def foo():
    print('foo')
foo()
In Wapper!
foo
After fun

可以看到装饰器不影响foo()函数的正常功能,它接受一个函数作为参数,然后对这个函数进行装饰后返回给原来的函数名。

装饰器的原理类似下面的函数:

def bar():
    print('Bar')
bar = dec_func(bar)
bar()
In Wapper!
Bar
After fun

装饰器的运行首先是将函数带入装饰器,原本的函数名接受装饰器“装饰”后的返回函数,再调用这个返回函数。

装饰器就是对函数进行装饰,“装饰”以为这并不会对函数的正常运行造成影响,仅仅是对函数的一些功能进行额外的补充。

装饰器是一个函数,接受一个函数(或者类)作为参数,返回值也是也是一个函数(或者类)

无参装饰器

无参装饰器就是不接受参数的装饰器,无参装饰器嵌套了两层函数,一个是外部函数接受参数,一个是内部的返回函数。

import time
def timeit(func):
    def wrapper():  # wrapper是返回函数,被装饰的函数的函数名接受这个函数,相当于f = wrapper()
        start = time.clock()
        func()  # func就是被传入装饰器的函数,func()在wrapper()内,在调用wrapper时,会调用func()
        end = time.clock()
        print('Used {}'.format(end - start))
    return wrapper
@timeit
def f():
    print('In f()')
f()  # 此时f = wrapper
In f()
Used 0.00031399999999992545

1.首先,把foo函数当做参数传入timeit
2.foo接受返回的函数wrapper,此时的foo指向wrapper,执行foo其实执行wrapper
3.调用foo其实调用了wrapper,在调用wrapper时又重新调用了原本的foo函数,在调用wrapper其实调用foo

多装饰器

def deco1(func):
    def wrapper():
        print('In Deco1')
        func()
    return wrapper
def deco2(func):
    def wrapper():
        print('In Deco2')
        func()
    return wrapper
@deco2
@deco1
def foo():
    print('In foo()')
foo()
In Deco2
In Deco1
In foo()

等价与:

foo = deco2(deco1(foo))

在多装饰器中,紧挨着被装饰函数的装饰器属于最内层,最先调用,最外层的装饰器最后调用。

有参装饰器

def make_header(level):  #接受参数
    print('Create decorator')
    def decorator(func):  #接受函数
        print('Initialize...')
        def wrapper():
            print('Call')
            return '<h{0}>{1}</h{0}>'.format(level, func())
        return wrapper
    return decorator
@make_header(2)
def get_content():
    return 'hello world'
Create decorator
Initialize...
get_content()
Call





'<h2>hello world</h2>'

等价于:

get_content = make_header(2)
get_content = decorator(get_content)

def log(prefix):  # 接受装饰器的参数
    def log_decorator(f):  # 内部定义的wapper负责输出log的参数+被装饰函数名称, 接受被装饰函数
        def wrapper(*args, **kw):   # 接受被装饰函数的参数
                # 将log函数的参数引用
            print('[%s] in decorate wrapper s%s()...' % (prefix, f.__name__))
            f(*args, **kw)
        return wrapper  # 返回内部函数给装饰器
    return log_decorator  # 返回装饰器给log函数
@log('DEBUG')
def test():
    print('out decorate run test()')


print(test())
[DEBUG] in decorate wrapper stest()...
out decorate run test()
None

有参装饰器嵌套了三层函数,最外层的函数接受装饰器的参数,中间的函数接受被装饰函数,最内层的函数接受被装饰函数的参数。

import time
from functools import reduce

def performance(unit):
    def perf_decorator(f):
        def wrapper(*args, **kargs):
            start_time = time.time()
            run_func = f(*args, **kargs)
            end_time = time.time()
            run_time = (end_time - start_time) * 1000 if unit == 'ms' else (end_time - start_time)
            print('call %s() in %f %s' % (f.__name__, run_time, unit))
            return run_func
        return wrapper
    return perf_decorator
@performance('ms')
def factorial(n):
    return reduce(lambda x,y: x*y, range(1, n+1))
print(factorial(10))
call factorial() in 0.010252 ms
3628800

装饰器的影响

def log(f):
    def wrapper(*args, **kw):
        print('call...')
        return f(*args, **kw)
    return wrapper
@log
def f2(x):
    pass
print(f2.__name__)
wrapper

由于decorator返回的新函数函数名已经不是'f2',而是@log内部定义的'wrapper'。 这对于那些依赖函数名的代码就会失效。decorator还改变了函数的__doc__等其它属性。

改善:

import functools
def log(f):
    @functools.wraps(f)
    def wrapper(*args, **kw):
        print('call...')
        return f(*args, **kw)
    return wrapper

最后需要指出,由于我们把原函数签名改成了(*args, **kw),因此,无法获得原函数的原始参数信息。 即便我们采用固定参数来装饰只有一个参数的函数

def log(f):
    @functools.wraps(f)
    def wrapper(x):
        print('call...')
        return f(x)
    return wrapper

也可能改变原函数的参数名,因为新函数的参数名始终是 'x',原函数定义的参数名不一定叫 'x'

import time
import functools

def performance(unit):
    def perf_decorator(f):
        @functools.wraps(f)
        def wrapper(*args, **kw):
            t1 = time.time()
            r = f(*args, **kw)
            t2 = time.time()
            t = (t2 - t1) * 1000 if unit == 'ms' else (t2 - t1)
            print('call %s() in %f %s' % (f.__name__, t, unit))
            return r
        return wrapper
    return perf_decorator
@performance('ms')
def factorial(n):
    return reduce(lambda x, y: x * y, range(1, n + 1))
print(factorial(10))
call factorial() in 0.008821 ms
3628800
print(factorial.__name__)
factorial

基于类的装饰器

可以定义基于类的装饰器

class Bold(object):
    def __init__(self, func):
        self.func = func
 
    def __call__(self, *args, **kwargs):
        return '<b>' + self.func(*args, **kwargs) + '</b>'
@Bold
def hello(name):
    return 'hello %s' % name
hello('world')
'<b>hello world</b>'
  • __init__():它接收一个函数作为参数,也就是被装饰的函数
  • __call__():让类对象可调用,就像函数调用一样,在调用被装饰函数时被调用

阅读

Python Decorator 基础
Python: 会打扮的装饰器

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

推荐阅读更多精彩内容