博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
python-装饰器
阅读量:4543 次
发布时间:2019-06-08

本文共 4666 字,大约阅读时间需要 15 分钟。

1.函数作用域LEGB

L:local函数内部作用域

E:enclosing函数内部与内嵌函数之间

G:global全局作用域

B:build-in内置作用域

2.闭包

Closure:内部函数中对enclosing作用域的变量进行引用

3.python装饰器

装饰器用来装饰函数

返回一个函数对象

被装饰函数标识符指向返回的函数对象

@dec

编写无参数decorator:

Python的 decorator 本质上就是一个高阶函数,它接收一个函数作为参数,然后,返回一个新函数。

使用 decorator 用Python提供的 @ 语法,这样可以避免手动编写 f = decorate(f) 这样的代码。

考察一个@log的定义:

def log(f):    def fn(x):        print 'call ' + f.__name__ + '()...'        return f(x)    return fn

对于阶乘函数,@log工作得很好:

@logdef factorial(n):    return reduce(lambda x,y: x*y, range(1, n+1))print factorial(10)

结果:

call factorial()...3628800

但是,对于参数不是一个的函数,调用将报错:

@logdef add(x, y):    return x + yprint add(1, 2)

结果:

Traceback (most recent call last):  File "test.py", line 15, in 
print add(1,2)TypeError: fn() takes exactly 1 argument (2 given)

因为 add() 函数需要传入两个参数,但是 @log 写死了只含一个参数的返回函数。

要让 @log 自适应任何参数定义的函数,可以利用Python的 *args 和 **kw,保证任意个数的参数总是能正常调用:

def log(f):    def fn(*args, **kw):        print 'call ' + f.__name__ + '()...'        return f(*args, **kw)    return fn

现在,对于任意函数,@log 都能正常工作。

import time

def performance(f):

def fn(*args,**kw):
t1 = time.time()
r = f(*args,**kw)
t2 = time.time()
print 'call %s() in %fs' % (f.__name__,(t2-t1))
return r
return fn

@performance

def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))

print factorial(10)

编写带参数decorator:

如果有的函数非常重要,希望打印出'[INFO] call xxx()...',有的函数不太重要,希望打印出'[DEBUG] call xxx()...',这时,log函数本身就需要传入'INFO'或'DEBUG'这样的参数,类似这样:

@log('DEBUG')def my_func():    pass

把上面的定义翻译成高阶函数的调用,就是:

my_func = log('DEBUG')(my_func)

上面的语句看上去还是比较绕,再展开一下:

log_decorator = log('DEBUG')my_func = log_decorator(my_func)

上面的语句又相当于:

log_decorator = log('DEBUG')@log_decoratordef my_func():    pass

所以,带参数的log函数首先返回一个decorator函数,再让这个decorator函数接收my_func并返回新函数:

def log(prefix):    def log_decorator(f):        def wrapper(*args, **kw):            print '[%s] %s()...' % (prefix, f.__name__)            return f(*args, **kw)        return wrapper    return log_decorator@log('DEBUG')def test():    passprint test()

执行结果:

[DEBUG] test()...None

对于这种3层嵌套的decorator定义,你可以先把它拆开:

# 标准decorator:def log_decorator(f):    def wrapper(*args, **kw):        print '[%s] %s()...' % (prefix, f.__name__)        return f(*args, **kw)    return wrapperreturn log_decorator# 返回decorator:def log(prefix):    return log_decorator(f)

拆开以后会发现,调用会失败,因为在3层嵌套的decorator定义中,最内层的wrapper引用了最外层的参数prefix,所以,把一个闭包拆成普通的函数调用会比较困难。不支持闭包的编程语言要实现同样的功能就需要更多的代码。

import time

def performance(unit):

def perf_decorator(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)

完善decorator

@decorator可以动态实现函数功能的增加,但是,经过@decorator“改造”后的函数,和原函数相比,除了功能多一点外,有没有其它不同的地方?

在没有decorator的情况下,打印函数名:

def f1(x):    passprint f1.__name__

输出: f1

有decorator的情况下,再打印函数名:

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

输出: wrapper

可见,由于decorator返回的新函数函数名已经不是'f2',而是@log内部定义的'wrapper'。这对于那些依赖函数名的代码就会失效。decorator还改变了函数的__doc__等其它属性。如果要让调用者看不出一个函数经过了@decorator的“改造”,就需要把原函数的一些属性复制到新函数中:

def log(f):    def wrapper(*args, **kw):        print 'call...'        return f(*args, **kw)    wrapper.__name__ = f.__name__    wrapper.__doc__ = f.__doc__    return wrapper

这样写decorator很不方便,因为我们也很难把原函数的所有必要属性都一个一个复制到新函数上,所以Python内置的functools可以用来自动化完成这个“复制”的任务:

import functoolsdef 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'

偏函数:

当一个函数有很多参数时,调用者就需要提供多个参数。如果减少参数个数,就可以简化调用者的负担。

比如,int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:

>>> int('12345')12345

但int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做 N 进制的转换:

>>> int('12345', base=8)5349>>> int('12345', 16)74565

假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:

def int2(x, base=2):    return int(x, base)

这样,我们转换二进制就非常方便了:

>>> int2('1000000')64>>> int2('1010101')85

functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:

>>> import functools>>> int2 = functools.partial(int, base=2)>>> int2('1000000')64>>> int2('1010101')85

所以,functools.partial可以把一个参数多的函数变成一个参数少的新函数,少的参数需要在创建时指定默认值,这样,新函数调用的难度就降低了。

 

转载于:https://www.cnblogs.com/Nyan-Workflow-FC/p/5672975.html

你可能感兴趣的文章
C++ 智能指针
查看>>
IOS7 position:fixed 定位问题
查看>>
12.伪类选择器与伪元素的应用
查看>>
Oracle存储过程基本语法
查看>>
JS高程第八章 BOM
查看>>
python-vi
查看>>
Unix进程控制
查看>>
DNS解析过程详解
查看>>
51Nod 1181 质数中的质数(质数筛法)
查看>>
并发编程学习总结
查看>>
Xamarin.Android 上中下布局
查看>>
VS Code使用记录
查看>>
locust参数化(数据库取值)
查看>>
Google Protocol Buffers浅析(三)
查看>>
.net core 中使用Google的protoc
查看>>
Spring Cloud和Spring Boot的区别
查看>>
jquery实现图片上传前本地预览
查看>>
C# — 题库答案汇总
查看>>
docker居然需要3.10以上的内核
查看>>
Win10下安装zookeeper
查看>>