闭包和装饰器的关系

因为最近想总结一下闭包和装饰器,有点细节总是理不顺,于是找了一下B站上播放量大的,其中一个下面评论很多都说讲的很好,但是我听了一下,关于闭包的地方讲解的就有明显错误。与fluent python和effective python上有矛盾,其实python cookbook上也没说一定是函数作为参数,只是说可以。但是B站有些视频讲解时,竟然说闭包一定是传入的参数是函数,其实这个就差远了。所以大家看一些东西时,最好还是看经典教材,毕竟网上一些讲解的视频,没有经过审核,再者讲解者自身水平参差不齐。

总结后,发现装饰器真是个好东西,灵活方便。装饰器是通过闭包来实现的,可以这么说两者之间的关系。

fluent python 2nd中关于闭包的说法。

A closure is a function-let’s call it f – with an extend scope that encompasses variables referenced in the body of f that are not global variables nor local variables of f. Such variables must come from the local scope of an outer function which encompasses f. It does not matter whether the function is anonymous or not; what matters is that it can access nonglobal variables that are defined outside of its body.

闭包是一个函数 f +一个/些变量,这些变量在闭包内引用,但是不是global变量也不是f的局部变量。这些变量必须来自包含函数f的外部函数的局部区域。函数是不是匿名函数无所谓,关键是f可以访问那些定义在 f 外部的非全局变量。书中给了一个图例,很清晰,到底什么是闭包。

闭包和装饰器的关系

从这个图的定义来看,闭包是函数并且这个函数可以访问非global的自由变量。当然一般闭包首先涉及到嵌套函数(函数内有函数),也涉及到高阶函数(传入的参数是函数或者返回值是函数)。但是并不像有些人讲的那样,闭包一定是传入函数。

如下是闭包的一个典型用法,这里闭包外的函数没有参数的。

python;gutter:true; def make_averager(): count = 0 total = 0 def averager(new_value): nonlocal count,total count += 1 total += new_value return total/count return averager # 返回的是函数,带括号返回的函数运行结果</p> <p>avg = make_averager() # an object of function make_averager print(avg.<strong>name</strong>) # the name is averager avg(10) avg(11) res = avg(13) res = avg(14) print(res)</p> <pre><code> 闭包外的函数传入的参数也可以是函数。这种形式是很多装饰器的基本形式。 ;gutter:true;
def f(n):
return n**2

def make_averager(func):
count = 0
total = 0
def averager(new_value):
nonlocal count,total
count += 1
res = func(new_value)
print(res)
total += res
return total/count
return averager # 返回的是函数,带括号返回的函数运行结果

avg = make_averager(f) # an object of function make_averager
print(avg.__name__) # the name is averager
avg(1)
avg(2)
res = avg(3)
res = avg(4)
print(res)

装饰器什么时候会用到呢?比如你要做计时统计,要记录日志,这些都是共有的一些功能,可以摘出来单独用,然后作为包装在别的函数上,这样别的函数主要功能既清晰明了,又能够实现对函数的一些记录和统计。

前面讲到闭包,其实从概念上闭包没有说一定是传入函数和返回函数,但是可能通常情况下这么用比较多。但是像上面的图中,并不需要传入函数。所以不能说闭包传入的参数是函数,就是装饰器也不一定的。

那么闭包和装饰器是什么关系呢?可以这么说,装饰器通过闭包的形式来实现的。通过下面几个例子,体会到了闭包和装饰器的关系,装饰器真是好用。从这个层面上就可以看到,为什么很多博客说装饰器就是给函数增加了一项功能,其实从功能上确实是这样的。

上面的函数可以用装饰器的形式。

python;gutter:true; ''' 虽然这么用不太好,但是也算是可以的。所以装饰器和闭包之间是什么关系呢? 有没有一目了然呢? '''</p> <p>def make_averager(func): count = 0 total = 0 def averager(new_value): nonlocal count,total count += 1 res = func(new_value) total += res return total/count return averager # 返回的是函数,带括号返回的函数运行结果</p> <p>@make_averager def f(n): return n**2</p> <p>''' the decorator can be detailed below: def f(n): return n**2 avg = make_averager(f) # an object of function make_averager</p> <p>''' print(f.<strong>name</strong>) f(10) f(20)</p> <pre><code> 那么下面看看装饰器常常用的情形,就是计时统计和日志记录。 ;gutter:true;
”’
closure
the free vairable is func
here the inner function clocked() can call func and
”’

import time

def clock(func):
def clocked(*args):
t0 = time.perf_counter()
result = func(*args)
elapsed = time.perf_counter() – t0
name = func.__name__
arg_lst = []
if args:
arg_lst.append(‘, ‘.join(repr(arg) for arg in args))
arg_str = ‘, ‘.join(arg_lst)
print(arg_str)
print(f'[{elapsed:0.8f}s] {name}({arg_str}) -> {result!r}’)
return result
return clocked

”’
decorator
”’
@clock
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
”’
def factorial(n):
return 1 if n < 2 else n*factorial(n-1)
factorial = clock(factorial)
”’

if __name__ == ‘__main__’:
print(‘*’ * 20, ‘Calling factorial(6)’)
print(‘6! =’, factorial(6))

你想实现一个功能,然后想计时,那么就用类似于上述的装饰器,不想计时时,只需要把装饰器注释掉就可以了,比如下面的例子,把装饰器注释掉了,就是没有计时了,别的不影响。

python;gutter:true; import time</p> <p>def clock(func): def clocked(<em>args): t0 = time.perf_counter() result = func(</em>args) elapsed = time.perf_counter() - t0 name = func.<strong>name</strong> arg_lst = [] if args: arg_lst.append(', '.join(repr(arg) for arg in args)) arg_str = ', '.join(arg_lst) print(arg_str) print(f'[{elapsed:0.8f}s] {name}({arg_str}) -> {result!r}') return result return clocked</p> <p>''' decorator '''</p> <h1>@clock</h1> <p>def factorial(n): return 1 if n < 2 else n<em>factorial(n-1) ''' def factorial(n): return 1 if n < 2 else n</em>factorial(n-1) factorial = clock(factorial) '''</p> <p>if <strong>name</strong> == '<strong>main</strong>': # print('*' * 20, 'Calling factorial(6)') print('6! =', factorial(6)) # 6! = 720</p> <pre><code> 日志记录例子也差不多,想记录日志就加上装饰器,不想用就注释掉。 ;gutter:true;
from functools import wraps
import logging

def logged(level, name=None, message=None):
"""
Add logging to a function. level is the logging
level, name is the logger name, and message is the
log message. If name and message aren’t specified,
they default to the function’s module and name.

"""
def decorate(func):
logname = name if name else func.__module__
log = logging.getLogger(logname)
logmsg = message if message else func.__name__

@wraps(func)
def wrapper(*args, **kwargs):
log.log(level, logmsg)
return func(*args, **kwargs)
return wrapper
return decorate

Example use
@logged(logging.DEBUG)
def add(x, y):
return x + y

@logged(logging.CRITICAL)
def plus(x, y):
return x * y

@logged(logging.CRITICAL, ‘example’)
def spam():
print(‘Spam log test!’)

spam()

add(3,4)

plus(3,4)

参考:fluent python 2nd, effective python, python cookbook

Original: https://www.cnblogs.com/jianyingzhou/p/15949968.html
Author: caoeryingzi
Title: 闭包和装饰器的关系

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/814823/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球