【转】【python】装饰器的原理

写在前面:

在开发OpenStack过程中,经常可以看到代码中的各种注解,自己也去查阅了资料,了解了这是python中的装饰器,因为弱类型的语言可以将函数当成返回值返回,这就是装饰器的原理。

虽然说知道装饰器的使用方法以及原理,但是一直不明白为什么要通过在内部函数返回一个函数名这样的写法,在微信上看到下面这篇文章,豁然开朗。因为觉得写的非常好,所以我也没必要再来写一遍了,直接转载,供以后的开发中参考。

-----------------------------------------------分割线--------------------------------------------------------------

文章链接: https://segmentfault.com/a/1190000003719779

引言

本文主要梳理了Python decorator的实现思路,解释了为什么Python decorator是现在这个样子。

关于代理模式、装饰模式

设计模式中经常提到的代理模式、装饰模式,这两种叫法实际上是说的同一件事,只是侧重点有所不同而已。

这两者都是通过在原有对象的基础上封装一层对象,通过调用封装后的对象而不是原来的对象来实现代理/装饰的目的。

例如:(以Java为例)

 1 public class CountProxy implements Count {
 2     private CountImpl countImpl;
 3
 4     public CountProxy(CountImpl countImpl) {
 5         this.countImpl = countImpl;
 6     }
 7
 8     @Override
 9     public void queryCount() {
10         System.out.println("事务处理之前");
11         // 调用委托类的方法;
12         countImpl.queryCount();
13         System.out.println("事务处理之后");
14     }
15
16     @Override
17     public void updateCount() {
18         System.out.println("事务处理之前");
19         // 调用委托类的方法;
20         countImpl.updateCount();
21         System.out.println("事务处理之后");
22
23     }
24
25 }

在这个例子中CountProxy是对CountImpl的封装。
使用者通过CountProxy.queryCount方法来调用CountImpl.queryCount方法,这被称为代理,即CountProxy是代理类,CountImpl是被代理类。
CountProxy.queryCount方法中,可以在CountImpl.queryCount方法调用之前和之后添加一些额外的操作,被称为装饰,即CountProxy是装饰类,CountImpl是被装饰类。

如果强调通过CountProxy 对CountImpl进行代理的作用,则称为代理模式;
如果强调通过CountProxy 对CountImpl增加额外的操作,则称为装饰模式;

不论是哪种称呼,其本质都在于对原有对象的封装。
其封装的目的在于增强所封装对象的功能或管理所封装的对象。

从上面的例子也可以发现,代理/封装所围绕的核心是可调用对象(比如函数)。

Python中的代理/装饰

Python中的可调用对象包括函数、方法、实现了__call__方法的类。
Python中的函数也是对象,可以作为高阶函数的参数传入或返回值返回。
因此,当代理/装饰的对象是函数时,可以使用高阶函数来对某个函数进行封装。
例如:

 1 def query_count_proxy(fun, name, age):
 2     print(‘do something before‘)
 3     rv = fun(name, age)
 4     print(‘do something after‘)
 5     return rv
 6
 7
 8 def query_count(name, age):
 9     print(‘name is %s, age is %d‘ % (name, age))
10
11
12 query_count_proxy(query_count, ‘Lee‘, 20)

但是,这个例子中,query_count函数作为参数传入query_count_proxy函数中,并在query_count_proxy函数中被调用,其结果作为返回值返回。这就完成了代理的功能,同时,在调用query_count函数的前后,我们还增加了装饰代码。
但是,query_count_proxy的函数参数与query_count不一样了,理想的代理应该保持接口一致才对。

为了保持一致,我们可以利用高阶函数可以返回函数的特点来完成:

 1 def query_count_proxy(fun):
 2
 3     def wrapper(name, age):
 4         print(‘do something before‘)
 5         rv = fun(name, age)
 6         print(‘do something after‘)
 7         return rv
 8
 9     return wrapper
10
11
12 def query_count(name, age):
13     print(‘name is %s, age is %d‘ % (name, age))
14
15
16 query_count_proxy(query_count)(‘Lee‘, 20)

修改后的例子,query_count_proxy仅负责接受被代理的函数query_count作为参数,同时,返回一个函数对象wrapper作为返回值,真正的封装动作在wrapper这个函数中完成。

此时,如果调用query_count_proxy(query_count)就得到了wrapper函数对象,则,执行query_count_proxy(query_count)(‘Lee‘, 20)就相当于执行了wrapper(‘Lee‘, 20)

但是可以看到,query_count_proxy(query_count)(‘Lee‘, 20)这种使用方法,仍然不能保证一致。

为了保持一致,我们需要利用Python中对象与其名称可以动态绑定的特点。
不使用query_count_proxy(quer_count)(‘Lee‘, 20)来调用代理函数,而是使用下面两句:

1 query_count = query_count_proxy(query_count)
2 query_count(‘Lee‘, 20)

执行query_count_proxy(query_count)生成wrapper函数对象,将这个对象通过query_count = query_count_proxy(query_count)绑定到query_count这个名字上来,这样执行query_count(‘Lee‘, 20)时,其实执行的是wrapper(‘Lee‘, 20)

这么做的结果就是:使用代理时调用query_count(‘Lee‘, 20)与不使用代理时调用query_count(‘Lee‘, 20)对使用者而言保持不变,不用改变代码,但是在真正执行时,使用的是代理/装饰后的函数。

这里,基本利用Python的高阶函数及名称绑定完成了代理/装饰的功能。
还有什么不理想的地方呢?
对,就是query_count = query_count_proxy(query_count),因为这句既不简洁,又属于重复工作。
Python为我们提供了语法糖来完成这类的tedious work。
方法就是:

1 @query_count_proxy
2 def query_count(name, age):
3     return ‘name is %s, age is %d‘ % (name, age)

query_count = query_count_proxy(query_count)就等同于在定义query_count函数的时候,在其前面加上@query_count_proxy

Python看到这样的语法,就会自动的执行query_count = query_count_proxy(query_count)进行name rebinding

补充

以上就是Python实现可调用对象装饰的核心。
可调用对象包括函数、方法、实现了__call__方法的类,上述内容只是针对函数来解释,对于方法、实现了__call__方法的类,其基本原理相同,具体实现略有差别。

时间: 2024-10-27 19:27:51

【转】【python】装饰器的原理的相关文章

Python 装饰器工作原理解析

#!/usr/bin/env python #coding:utf-8 """ 装饰器实例拆解 """ def login00(func):     print('00请通过验证用户!')     return func def tv00(name):     print('00你的用户是:%s' %name) # 装饰器的精简工作原理解释: tv = login00(tv00) # 返回tv函数的对象,赋值给tv tv('yh00') # 调用

python装饰器原理及相关操作

python装饰器,简单的说就是用于操作底层代码的代码,在不改变底层代码函数的情况下对底层代码进行验证操作等 首先,必须知,道调用func和func的区别,分别为返回函数所在的内存地址和调用该函数,输出执行结果,例如: def func(): print("欢迎光临!!!") print("返回函数所在的内存地址:",func) func() 列举一个简单的web页面调用例子 1 #做登录验证 2 def login(func): 3 print("登录成

python——装饰器

装饰器是什么呢? 我们先来打一个比方,我写了一个python的插件,提供给用户使用,但是在使用的过程中我添加了一些功能,可是又不希望用户改变调用的方式,那么该怎么办呢? 这个时候就用到了装饰器.装饰器的原理是什么?我们接下来就一步一步看过来! 假如我们有一个home函数如下: 1 def home(): 2 print 'this is the home page!' 而我们希望用户在访问home函数之前先验证一下权限,那么在不改变用户调用方法的情况下,就需要在home中调用一个login函数,

Python装饰器由浅入深

装饰器的功能在很多语言中都有,名字也不尽相同,其实它体现的是一种设计模式,强调的是开放封闭原则,更多的用于后期功能升级而不是编写新的代码.装饰器不光能装饰函数,也能装饰其他的对象,比如类,但通常,我们以装饰函数为例子介绍其用法.要理解在Python中装饰器的原理,需要一步一步来.本文尽量描述得浅显易懂,从最基础的内容讲起. (注:以下使用Python3.5.1环境) 一.Python的函数相关基础 第一,必须强调的是python是从上往下顺序执行的,而且碰到函数的定义代码块是不会立即执行它的,只

对Python装饰器的个人理解方法

0.说明 在自己好好总结并对Python装饰器的执行过程进行分解之前,对于装饰器虽然理解它的基本工作方式,但对于存在复杂参数的装饰器(装饰器和函数本身都有参数),总是会感到很模糊,即使这会弄懂了,下一次也很快忘记,其实本质上还是没有多花时间去搞懂其中的细节问题. 虽然网络上已经有很多这样的文章,但显然都是别人的思想,因此自己总是记不牢,所以花点时间自己好好整理一下. 最近在对<Python核心编程>做总结,收获了不少,下面分享一下我自己对于Python装饰器的理解,后面还提供了一个较为复杂的P

关于python装饰器

关于python装饰器,不是系统的介绍,只是说一下某些问题 1 首先了解变量作用于非常重要 2 其次要了解闭包 1 def logger(func): 2 def inner(*args, **kwargs): 3 print "Arguments were: %s, %s" % (args, kwargs) 4 return func(*args, **kwargs) 5 return inner 在这里面,func是被装饰的函数,*args, **kwargs是 func要接收的参

[Python] 对 Python 装饰器的理解的一些心得分享出来给大家参考

最近写一个py脚本来整理电脑中的文档,其中需要检校输入的字符,为了不使代码冗长,想到使用装饰器. 上网搜索有关python的装饰器学习文档,主要看的是AstralWind的一篇博文,以及Limodou的一篇文章.作为初学者,这两篇文章对新手有很大的帮助,但仍然有些不易理解的地方.因此在此以一个初学者的认知记录一下python的装饰器的学习心得. 1. 什么是装饰器? 顾名思义,装饰器就是在方法上方标一个带有@符号的方法名,以此来对被装饰的方法进行点缀改造. 当你明白什么是装饰器之后,自然会觉得这

python装饰器(decorator)

最近在自学Python,在装饰器这里迷惑了我很久,有几个问题一直困惑着我.1.装饰器的语法原理:2.为什么要用装饰器: 首先来看一下装饰器的原理.总而言之,装饰器就是函数或者类作为函数的返回值.将函数或者类作为装饰器的参数传递之后,装饰器对该函数或者类进行相应操作后再将其返回.这就是装饰器的基本原理.举个例子来讲,我们定义装饰器decorator,定义函数f.其过程便如下:f=decorator(f).这里显得很抽象,我们用具体的例子来描述一下其意思.这里我们定义了一个函数: 1 def rec

利用世界杯,读懂 Python 装饰器

Python 装饰器是在面试过程高频被问到的问题,装饰器也是一个非常好用的特性, 熟练掌握装饰器会让你的编程思路更加宽广,程序也更加 pythonic. 今天就结合最近的世界杯带大家理解下装饰器. 德国战车 6 月 17 日德国战墨西哥,小痴虽然是一个伪球迷,但每年的世界杯还是会了解下.而德国是上届的冠军,又是这届夺冠热门.德意志战车在 32 年间小组赛就没有输过!卧槽!虽然小痴很少赌球,但这次德国如此强大,肯定会赢吧.搏一搏单车变摩托!随后小痴买了德国队赢.心里想着这次肯定稳了!赢了会所嫩模!