Python装饰器与闭包
<p>闭包是Python装饰器的基础。要理解闭包,先要了解Python中的变量作用域规则。</p><h3>变量作用域规则</h3>
<p>首先,在函数中是能访问全局变量的:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">>></span><span class="pl-k">></span> a <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">'</span>global var<span class="pl-pds">'</span></span>
<span class="pl-k">>></span><span class="pl-k">></span> <span class="pl-k">def</span> <span class="pl-en">foo</span>():
<span class="pl-c1">print</span>(a)
<span class="pl-k">>></span><span class="pl-k">></span> foo()
<span class="pl-k">global</span> var</pre>
</div>
<p>然后,在一个嵌套函数中,内层函数能够访问在外层函数中定义的局部变量:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">>></span><span class="pl-k">></span> <span class="pl-k">def</span> <span class="pl-en">foo</span>():
a <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">'</span>free var<span class="pl-pds">'</span></span>
<span class="pl-k">def</span> <span class="pl-en">bar</span>():
<span class="pl-c1">print</span>(a)
<span class="pl-k">return</span> bar
<span class="pl-k">>></span><span class="pl-k">></span> foo()()
free var</pre>
</div>
<h3>闭包</h3>
<p>上面的嵌套函数就是闭包。<strong>闭包</strong>是指延伸了作用域的函数,在其中能够访问未在函数定义体中定义的非全局变量。未在函数定义体中定义的非全局变量一般都是在嵌套函数中出现的。</p>
<p>上述示例中的变量a就是一个并未在函数bar中定义的非全局变量。对于bar来说,它有个专业名字,叫做<strong>自由变量</strong>。</p>
<p>自由变量的名称可以在字节码对象中查看:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">>></span><span class="pl-k">></span> bar <span class="pl-k">=</span> foo()
<span class="pl-k">>></span><span class="pl-k">></span> bar.<span class="pl-c1">__code__</span>.co_freevars
(<span class="pl-s"><span class="pl-pds">'</span>a<span class="pl-pds">'</span></span>,)</pre>
</div>
<p>自由变量的值绑定在函数的__closure__属性中:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">>></span><span class="pl-k">></span> bar.__closure__
(<span class="pl-k"><</span>cell at <span class="pl-c1"><span class="pl-k">0x</span>000001CB2912DF48</span>: <span class="pl-c1">str</span> <span class="pl-c1">object</span> at <span class="pl-c1"><span class="pl-k">0x</span>000001CB291D3D70</span><span class="pl-k">></span>,)</pre>
</div>
<p>其中保存了对应自由变量的cell对象的序列,cell对象的cell_contents属性保存了变量的值:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">>></span><span class="pl-k">></span> bar.__closure__[<span class="pl-c1">0</span>].cell_contents
<span class="pl-s"><span class="pl-pds">'</span>free var<span class="pl-pds">'</span></span></pre>
</div>
<p>这与JavaScript中闭包的行为是类似的,JavaScript中嵌套函数会将外层函数的活动对象添加到它的作用域链中。但与JavaScript不同的是,当Python函数中的全局变量或者自由变量是不可变对象(数字、字符串、元组等)时,是只能读取,无法更新的:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">>></span><span class="pl-k">></span> a <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">>></span><span class="pl-k">></span> <span class="pl-k">def</span> <span class="pl-en">foo</span>():
<span class="pl-c1">print</span>(a)
a <span class="pl-k">+=</span> <span class="pl-c1">1</span>
<span class="pl-k">>></span><span class="pl-k">></span> foo()
<span class="pl-c1">UnboundLocalError</span>: local variable <span class="pl-s"><span class="pl-pds">'</span>a<span class="pl-pds">'</span></span> referenced before assignment
<span class="pl-k">>></span><span class="pl-k">></span> <span class="pl-k">def</span> <span class="pl-en">foo</span>():
a <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">def</span> <span class="pl-en">bar</span>():
<span class="pl-c1">print</span>(a)
a <span class="pl-k">+=</span> <span class="pl-c1">1</span>
<span class="pl-k">return</span> bar
<span class="pl-k">>></span><span class="pl-k">></span> foo()()
<span class="pl-c1">UnboundLocalError</span>: local variable <span class="pl-s"><span class="pl-pds">'</span>a<span class="pl-pds">'</span></span> referenced before assignment</pre>
</div>
<p>两种情况下,都会报错。这并不是缺陷,而是Python的设计选择。Python不要求声明变量,但是会假定在函数定义体中赋值的变量是局部变量,以避免在不知情的情况下修改全局变量。</p>
<p><code>a += 1</code>与<code>a = a + 1</code>相同,编译函数的定义体时,会将a当做局部变量,不会当做自由变量保存。然后尝试获取a的值时,发现a并没有绑定值,于是报错。</p>
<p>解决这个问题的办法,一是将变量置于一些可变对象,如列表、字典中:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">def</span> <span class="pl-en">foo</span>():
ns <span class="pl-k">=</span> {}
ns[<span class="pl-s"><span class="pl-pds">'</span>a<span class="pl-pds">'</span></span>] <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">def</span> <span class="pl-en">bar</span>():
ns[<span class="pl-s"><span class="pl-pds">'</span>a<span class="pl-pds">'</span></span>] <span class="pl-k">+=</span> <span class="pl-c1">1</span>
<span class="pl-c1">print</span> (ns[<span class="pl-s"><span class="pl-pds">'</span>a<span class="pl-pds">'</span></span>])
<span class="pl-k">return</span> bar</pre>
</div>
<p>另外的方法就是使用<strong>global</strong>或者<strong>nonlocal</strong>将变量声明为全局变量或者自由变量:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">>></span><span class="pl-k">></span> <span class="pl-k">def</span> <span class="pl-en">foo</span>():
a <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-k">def</span> <span class="pl-en">bar</span>():
<span class="pl-k">nonlocal</span> a
a <span class="pl-k">+=</span> <span class="pl-c1">1</span>
<span class="pl-c1">print</span>(a)
<span class="pl-k">return</span> bar
<span class="pl-k">>></span><span class="pl-k">></span> foo()()
<span class="pl-c1">2</span></pre>
</div>
<p>当自由变量本身是可变对象时,是可以直接进行操作的:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">def</span> <span class="pl-en">make_avg</span>():
ls <span class="pl-k">=</span> []
<span class="pl-k">def</span> <span class="pl-en">avg</span>(<span class="pl-smi">x</span>):
ls.append(x)
<span class="pl-c1">print</span>(<span class="pl-c1">sum</span>(ls)<span class="pl-k">/</span><span class="pl-c1">len</span>(ls))
<span class="pl-k">return</span> avg</pre>
</div>
<h3>装饰器</h3>
<p>装饰器是可调用对象,参数一般是另一个函数。装饰器可以以某种方式增强被装饰函数的行为,然后返回被装饰的函数或者将其替换成一个新的函数。</p>
<p>一个最简单的不做任何额外行为的装饰器:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">def</span> <span class="pl-en">decorate</span>(<span class="pl-smi">func</span>):
<span class="pl-k">return</span> func</pre>
</div>
<p><code>decorate</code>函数就是一个最简单的装饰器,使用方法:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">def</span> <span class="pl-en">target</span>():
<span class="pl-k">pass</span>
target <span class="pl-k">=</span> decorate(target)</pre>
</div>
<p>Python为装饰器的使用提供了语法糖,可以简便的写为:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-en">@decorate</span>
<span class="pl-k">def</span> <span class="pl-en">target</span>():
<span class="pl-k">pass</span></pre>
</div>
<h4>导入时运行</h4>
<p>装饰器一个很重要的特性是它是导入时(加载模块时)运行的:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">def</span> <span class="pl-en">decorate</span>(<span class="pl-smi">func</span>):
<span class="pl-c1">print</span>(<span class="pl-s"><span class="pl-pds">'</span>running decorator when import<span class="pl-pds">'</span></span>)
<span class="pl-k">return</span> func
<span class="pl-en">@decorate</span>
<span class="pl-k">def</span> <span class="pl-en">foo</span>():
<span class="pl-c1">print</span>(<span class="pl-s"><span class="pl-pds">'</span>running foo<span class="pl-pds">'</span></span>)
<span class="pl-k">pass</span>
<span class="pl-k">if</span> <span class="pl-c1">__name__</span> <span class="pl-k">==</span> <span class="pl-s"><span class="pl-pds">'</span>__main__<span class="pl-pds">'</span></span>:
<span class="pl-c1">print</span>(<span class="pl-s"><span class="pl-pds">'</span>start foo<span class="pl-pds">'</span></span>)
foo()</pre>
</div>
<p>结果:</p>
<div class="highlight highlight-source-python">
<pre>running decorator when <span class="pl-k">import</span>
start foo
running foo</pre>
</div>
<p>可以看到,装饰器是导入时运行的,而被装饰的函数是明确调用时运行的。</p>
<p>装饰器可以返回被装饰的函数本身,和运行时导入的特性结合起来,可以实现简单的注册器功能:</p>
<div class="highlight highlight-source-python">
<pre>view_registry <span class="pl-k">=</span> []
<span class="pl-k">def</span> <span class="pl-en">register</span>(<span class="pl-smi">func</span>):
view_registry.append(func)
<span class="pl-k">return</span> func
<span class="pl-en">@register</span>
<span class="pl-k">def</span> <span class="pl-en">view1</span>():
<span class="pl-k">pass</span>
<span class="pl-en">@register</span>
<span class="pl-k">def</span> <span class="pl-en">view2</span>():
<span class="pl-k">pass</span>
<span class="pl-k">def</span> <span class="pl-en">main</span>():
<span class="pl-c1">print</span>(view_registry)
<span class="pl-k">if</span> <span class="pl-c1">__name__</span> <span class="pl-k">==</span> <span class="pl-s"><span class="pl-pds">'</span>__main__<span class="pl-pds">'</span></span>:
main()</pre>
</div>
<h4>返回新函数</h4>
<p>上述装饰器的例子都返回了被装饰的原函数,但装饰器的典型行为还是返回一个新函数:把被装饰的函数替换成新函数,新函数接受与原函数相同的参数,并且返回原函数本该返回的值。写法类似于:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">def</span> <span class="pl-en">deco</span>(<span class="pl-smi">func</span>):
<span class="pl-k">def</span> <span class="pl-en">new_func</span>(<span class="pl-k">*</span><span class="pl-smi">args</span>, <span class="pl-k">**</span><span class="pl-smi">kwargs</span>):
<span class="pl-k">return</span> func(<span class="pl-k">*</span>args, <span class="pl-k">**</span>kwargs)
<span class="pl-k">return</span> new_func</pre>
</div>
<p>这种情况下装饰器就使用到了闭包。JavaScript中的防抖与节流函数就是这种典型的装饰器行为。新函数一般会使用外部装饰器函数中的变量当做自由变量,对函数作出某种增强行为。</p>
<p>举个例子,我们知道,当Python函数的参数是个可变对象时,会产生意料之外的行为:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">def</span> <span class="pl-en">foo</span>(<span class="pl-smi">x</span>, <span class="pl-smi">y</span><span class="pl-k">=</span>[]):
y.append(x)
<span class="pl-c1">print</span>(y)
foo(<span class="pl-c1">1</span>)
foo(<span class="pl-c1">2</span>)
foo(<span class="pl-c1">3</span>)</pre>
</div>
<p>输出:</p>
<div class="highlight highlight-source-python">
<pre>[<span class="pl-c1">1</span>]
[<span class="pl-c1">1</span>, <span class="pl-c1">2</span>]
[<span class="pl-c1">1</span>, <span class="pl-c1">2</span>, <span class="pl-c1">3</span>]</pre>
</div>
<p>这是因为,函数的参数默认值保存在__defaults__属性中,指向了同一个列表:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">>></span><span class="pl-k">></span> foo.<span class="pl-c1">__defaults__</span>
([<span class="pl-c1">1</span>, <span class="pl-c1">2</span>, <span class="pl-c1">3</span>],)</pre>
</div>
<p>我们就可以用一个装饰器在函数执行前取出默认值做深复制,然后覆盖函数原先的参数默认值:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">import</span> copy
<span class="pl-k">def</span> <span class="pl-en">fresh_defaults</span>(<span class="pl-smi">func</span>):
defaults <span class="pl-k">=</span> func.<span class="pl-c1">__defaults__</span>
<span class="pl-k">def</span> <span class="pl-en">deco</span>(<span class="pl-k">*</span><span class="pl-smi">args</span>, <span class="pl-k">**</span><span class="pl-smi">kwargs</span>):
func.<span class="pl-c1">__defaults__</span> <span class="pl-k">=</span> copy.deepcopy(defaults)
<span class="pl-k">return</span> func(<span class="pl-k">*</span>args, <span class="pl-k">**</span>kwargs)
<span class="pl-k">return</span> deco
<span class="pl-en">@fresh_defaults</span>
<span class="pl-k">def</span> <span class="pl-en">foo</span>(<span class="pl-smi">x</span>, <span class="pl-smi">y</span><span class="pl-k">=</span>[]):
y.append(x)
<span class="pl-c1">print</span>(y)
foo(<span class="pl-c1">1</span>)
foo(<span class="pl-c1">2</span>)
foo(<span class="pl-c1">3</span>)</pre>
</div>
<p>输出:</p>
<pre><code>
</code></pre>
<h4>接收参数的装饰器</h4>
<p>装饰器除了可以接受函数作为参数外,还可以接受其他参数。使用方法是:创建一个装饰器工厂,接受参数,返回一个装饰器,再把它应用到被装饰的函数上,语法如下:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">def</span> <span class="pl-en">deco_factory</span>(<span class="pl-k">*</span><span class="pl-smi">args</span>, <span class="pl-k">**</span><span class="pl-smi">kwargs</span>):
<span class="pl-k">def</span> <span class="pl-en">deco</span>(<span class="pl-smi">func</span>):
<span class="pl-c1">print</span>(args)
<span class="pl-k">return</span> func
<span class="pl-k">return</span> deco
<span class="pl-en">@deco_factory</span>(<span class="pl-s"><span class="pl-pds">'</span>factory<span class="pl-pds">'</span></span>)
<span class="pl-k">def</span> <span class="pl-en">foo</span>():
<span class="pl-k">pass</span></pre>
</div>
<p>在Web框架中,通常要将URL模式映射到生成响应的view函数,并将view函数注册到某些中央注册处。之前我们曾经实现过一个简单的注册装饰器,只是注册了view函数,却没有URL映射,是远远不够的。</p>
<p>在Flask中,注册view函数需要一个装饰器:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-en">@app.route</span>(<span class="pl-s"><span class="pl-pds">'</span>/hello<span class="pl-pds">'</span></span>)
<span class="pl-k">def</span> <span class="pl-en">hello</span>():
<span class="pl-k">return</span> <span class="pl-s"><span class="pl-pds">'</span>Hello, World<span class="pl-pds">'</span></span></pre>
</div>
<p>原理就是使用了装饰器工厂,可以简单的模拟一下实现:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">class</span> <span class="pl-en">App</span>:
<span class="pl-k">def</span> <span class="pl-c1">__init__</span>(<span class="pl-smi"><span class="pl-smi">self</span></span>):
<span class="pl-c1">self</span>.view_functions <span class="pl-k">=</span> {}
<span class="pl-k">def</span> <span class="pl-en">route</span>(<span class="pl-smi"><span class="pl-smi">self</span></span>, <span class="pl-smi">rule</span>):
<span class="pl-k">def</span> <span class="pl-en">deco</span>(<span class="pl-smi">view_func</span>):
<span class="pl-c1">self</span>.view_functions <span class="pl-k">=</span> view_func
<span class="pl-k">return</span> view_func
<span class="pl-k">return</span> deco
app <span class="pl-k">=</span> App()
<span class="pl-en">@app.route</span>(<span class="pl-s"><span class="pl-pds">'</span>/<span class="pl-pds">'</span></span>)
<span class="pl-k">def</span> <span class="pl-en">index</span>():
<span class="pl-k">pass</span>
<span class="pl-en">@app.route</span>(<span class="pl-s"><span class="pl-pds">'</span>/hello<span class="pl-pds">'</span></span>)
<span class="pl-k">def</span> <span class="pl-en">hello</span>():
<span class="pl-k">pass</span>
<span class="pl-k">for</span> rule, view <span class="pl-k">in</span> app.view_functions.items():
<span class="pl-c1">print</span>(rule, <span class="pl-s"><span class="pl-pds">'</span>:<span class="pl-pds">'</span></span>, view.<span class="pl-c1">__name__</span>)</pre>
</div>
<p>输出:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">/</span> : index
<span class="pl-k">/</span>hello : hello</pre>
</div>
<p>还可以使用装饰器工厂来确定view函数可以允许哪些HTTP请求方法:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">def</span> <span class="pl-en">action</span>(<span class="pl-smi">methods</span>):
<span class="pl-k">def</span> <span class="pl-en">deco</span>(<span class="pl-smi">view</span>):
view.allow_methods <span class="pl-k">=</span>
<span class="pl-k">return</span> view
<span class="pl-k">return</span> deco
<span class="pl-en">@action</span>([<span class="pl-s"><span class="pl-pds">'</span>GET<span class="pl-pds">'</span></span>, <span class="pl-s"><span class="pl-pds">'</span>POST<span class="pl-pds">'</span></span>])
<span class="pl-k">def</span> <span class="pl-en">view</span>(<span class="pl-smi">request</span>):
<span class="pl-k">if</span> request.method.lower() <span class="pl-k">in</span> view.allow_methods:
<span class="pl-c1">...</span></pre>
</div>
<h4>重叠的装饰器</h4>
<p>装饰器也是可以重叠使用的:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-en">@d1</span>
<span class="pl-en">@d2</span>
<span class="pl-k">def</span> <span class="pl-en">foo</span>():
<span class="pl-k">pass</span></pre>
</div>
<p>等同于:</p>
<div class="highlight highlight-source-python">
<pre>foo <span class="pl-k">=</span> d1(d2(foo))</pre>
</div>
<h4>类装饰器</h4>
<p>装饰器的参数也可以是一个类,也就是说,装饰器可以装饰类:</p>
<div class="highlight highlight-source-python">
<pre><span class="pl-k">import</span> types
<span class="pl-k">def</span> <span class="pl-en">deco</span>(<span class="pl-smi"><span class="pl-smi">cls</span></span>):
<span class="pl-k">for</span> key, method <span class="pl-k">in</span> <span class="pl-c1">cls</span>.<span class="pl-c1">__dict__</span>.items():
<span class="pl-k">if</span> <span class="pl-c1">isinstance</span>(method, types.FunctionType):
<span class="pl-c1">print</span>(key, <span class="pl-s"><span class="pl-pds">'</span>:<span class="pl-pds">'</span></span>, method.<span class="pl-c1">__name__</span>)
<span class="pl-k">return</span> <span class="pl-c1">cls</span>
<span class="pl-en">@deco</span>
<span class="pl-k">class</span> <span class="pl-en">Test</span>:
<span class="pl-k">def</span> <span class="pl-c1">__init__</span>(<span class="pl-smi"><span class="pl-smi">self</span></span>):
<span class="pl-k">pass</span>
<span class="pl-k">def</span> <span class="pl-en">foo</span>(<span class="pl-smi"><span class="pl-smi">self</span></span>):
<span class="pl-k">pass</span></pre>
</div><br><br>
来源:https://www.cnblogs.com/linxiyue/p/11224322.html
頁:
[1]