decorator-2017-6-6

<div id="table-of-contents">
<h2>Table of Contents</h2>
<div id="text-table-of-contents">
<ul>
<li><a href="#org1fbb994">1. 什么是闭包</a></li>
<li><a href="#org6e28685">2. 装饰器</a></li>
<li><a href="#orga345de2">3. 无参装饰器</a></li>
<li><a href="#org184871c">4. 有参装饰器</a></li>
<li><a href="#org8215eae">5. 位置参数和关键字参数都可以的装饰器</a></li>
<li><a href="#org7ddc069">6. return 之后的对象和传入对象的关系以及形式</a></li>
<li><a href="#org87145ad">7. 类装饰器</a></li>
</ul>
</div>
</div>

<a id="org1fbb994"></a>

什么是闭包

解释 1:在函数内部定义一个函数,这个函数使用到外部函数

闭包(closure)是一种引用了外部变量的函数对象,无论该变量所处的作用域是否还存在于内存中。

举例来说,函数 generatepowerfunc 返回了另一个函数:

def generate_power_func(n):
    print "id(n): %X" % id(n)
    def nth_power(x):
        return x**n
    print "id(nth_power): %X" % id(nth_power)
    return nth_power

函数 nthpower 就是一个闭包,它可以访问定义在 generatepowerfunc 函数中的变量 n,i
显而易见如果缺少变量 n,函数 nthpower 将是一个不能执行的没有闭合的函数,
这个不完整的函数 nthpower 需要变量 n 来让它变成一个完整的函数对象,
这种函数就是闭包,换句话说是变量 n 封闭了函数 nthpower

<a id="org6e28685"></a>

装饰器

https://wiki.python.org/moin/PythonDecorators

<a id="orga345de2"></a>

无参装饰器

# -*- coding: utf-8 -*-
 def func_cache(func):
   cache = {}
   def inner_deco(*args):
     if args in cache:
       print('func {} is already cached with arguments {}'.format(
         func.__name__, args))
       return cache[args]
     else:
       print('func {} is not cached with arguments {}'.format(
         func.__name__, args)) 
       res = func(*args)
       cache[args] = res
       return res
   return inner_deco

 @func_cache
 def add_two_number(a, b):
   return a + b

 if __name__ == "__main__":
   print('1. add_two_number(1, 2)')
   add_two_number(1, 2)
   print('2. add_two_number(2, 3)')
   add_two_number(2, 3)
   print('3. add_two_number(1, 2)')
   add_two_number(1, 2)

<a id="org184871c"></a>

有参装饰器

目前我们实现的函数缓存装饰器,会缓存所有遇到的函数返回值。
我们希望能够对缓存数量上限做一个限制,从而在内存消耗和运行效率上取得折中。
但是同时,对于不同的函数,我们希望做到缓存上限不同,例如对于运行一次比较耗时的函数,我们希望缓存上限大一些;
反之,则小一些。这时,需要用到带参数的 Decorator。

# -*- coding: utf-8 -*-
from functools import wraps
import random

def outer_deco(size=10):
  def func_cache(func):
    cache = {}
    @wraps(func)
    def inner_deco(*args, **kwargs):
      key = (args, frozenset(kwargs.items()))
      if key not in cache:
        print('func {} is not cached with arguments {} {}'.format(
          func.__name__, args, kwargs)) 
        res = func(*args, **kwargs)
        if len(cache) >= size:
          lucky_key = random.choice(list(cache.keys()))
          print('func {} cache pop {}'.format(
            func.__name__, lucky_key))
          cache.pop(lucky_key, None)
        cache[key] = res
      return cache[key]
    return inner_deco
  return func_cache

@outer_deco(size=3)
def add_two_number(a, b):
  return a + b

@outer_deco()
def product_two_number(a, b):
  return a * b

if __name__ == "__main__":
  print('add_two_number func name is {}'.format(add_two_number.__name__))
  print('1. add_two_number(1, 2)')
  add_two_number(1, 2)
  print('2. add_two_number(2, 3)')
  add_two_number(2, 3)
  print('3. add_two_number(1, b=2)')
  add_two_number(1, b=2)
  print('4. add_two_number(1, 2)')
  add_two_number(1, 2)
  print('5. product_two_number(1, 2)')
  product_two_number(1, 2)
  print('6. add_two_number(1, 3)')
  add_two_number(1, 3)

<a id="org8215eae"></a>

位置参数和关键字参数都可以的装饰器

http://blog.guoyb.com/2016/04/19/python-decorator/?hmsr=toutiao.io

<a id="org7ddc069"></a>

return 之后的对象和传入对象的关系以及形式

https://www.ibm.com/developerworks/cn/linux/l-cpdecor.html
高级抽象简介

根据我的经验,元类应用最多的场合就是在类实例化之后对类中的方法进行修改。decorator 目前并不允许您修改类实例化本身,但是它们可以修改依附于类的方法。这并不能让您在实例化过程中动态添加或删除方法或类属性,但是它让这些方法可以在运行时根据环境的条件来变更其行为。现在从技术上来说,decorator 是在运行 class 语句时应用的,对于顶级类来说,它更接近于 “编译时” 而非 “运行时”。但是安排 decorator 的运行时决策与创建类工厂一样简单。例如:
清单 8. 健壮但却深度嵌套的 decorator

def arg_sayer(what):
    def what_sayer(meth):
        def new(self, *args, **kws):
            print what
            return meth(self, *args, **kws)
        return new
    return what_sayer

def FooMaker(word):
    class Foo(object):
        @arg_sayer(word)
        def say(self): pass
    return Foo()

foo1 = FooMaker('this')
foo2 = FooMaker('that')
print type(foo1),; foo1.say()  # prints: <class '__main__.Foo'> this
print type(foo2),; foo2.say()  # prints: <class '__main__.Foo'> that

@argsayer() 绕了很多弯路,但只获得非常有限的结果,不过对于它所阐明的几方面来说,这是值得的:

Foo.say() 方法对于不同的实例有不同的行为。在这个例子中,不同之处只是一个数据值,可以轻松地通过其他方式改变这个值;不过原则上来说,decorator 可以根据运行时的决策来彻底重写这个方法。
本例中未修饰的 Foo.say() 方法是一个简单的占位符,其整个行为都是由 decorator 决定的。然而,在其他情况下,decorator 可能会将未修饰的方法与一些新功能相结合。
正如我们已经看到的一样,Foo.say() 的修改是通过 FooMaker() 类工厂在运行时严格确定的。可能更加典型的情况是在顶级定义类中使用 decorator,这些类只依赖于编译时可用的条件(这通常就足够了)。
decorator 都是参数化的。或者更确切地说,argsayer() 本身根本就不是一个真正的 decorator;argsayer()所返回的 函数 —— whatsayer() 就是一个使用了闭包来封装其数据的 decorator 函数。参数化的 decorator 较为常见,但是它们将所需的函数嵌套为三层。

迈进元类领域

正如上一节中介绍的一样,decorator 并不能完全取代元类挂钩,因为它们只修改了方法,而未添加或删除方法。
实际上,这样说并不完全正确。作为一个 Python 函数,decorator 完全可以实现其他 Python 代码所实现的任何功能。
通过修饰一个类的 ._new_() 方法(甚至是其占位符版本),您实际上可以更改附加到该类的方法。
尽管尚未在现实中看到这种模式,不过我认为它有着某种必然性,甚至可以作为 <span class="underline">metaclass</span> 指派的一项改进:
清单 9. 添加和删除方法的 decorator

def flaz(self): return 'flaz'     # Silly utility method
def flam(self): return 'flam'     # Another silly method

def change_methods(new):
    "Warning: Only decorate the __new__() method with this decorator"
    if new.__name__ != '__new__':
        return new  # Return an unchanged method
    def __new__(cls, *args, **kws):
        cls.flaz = flaz
        cls.flam = flam
        if hasattr(cls, 'say'): del cls.say
        return super(cls.__class__, cls).__new__(cls, *args, **kws)
    return __new__

class Foo(object):
    @change_methods
    def __new__(): pass
    def say(self): print "Hi me:", self

foo = Foo()
print foo.flaz()  # prints: flaz
foo.say()         # AttributeError: 'Foo' object has no attribute 'say'

在 changemethods() decorator 示例中,我们添加并删除了几个固定的方法,不过这是毫无意义的。
在更现实的情况中,应使用上一节中提到的几个模式。例如,参数化的 decorator 可以接受一个能表示要添加或删除的方法的数据结构;或者由数据库查询之类的某些环境特性做出这一决策。
这种对附加方法的操作也可以像之前一样打包到一个函数工厂中,这将使最终决策延迟到运行时。
这些新兴技术也许比 <span class="underline">metaclass</span> 指派更加万能。
例如,您可以调用一个增强了的 changemethods(),如下所示:
清单 10. 增强的 changemethods()

class Foo(object):
    @change_methods(add=(foo, bar, baz), remove=(fliz, flam))
    def __new__(): pass

回页首
修改调用模型

您将看到,有关 decorator 的最典型的例子可能是使一个函数或方法来实现 “其他功能”,同时完成其基本工作。
例如,在诸如 Python Cookbook Web 站点(请参见 参考资料 中的链接)之类的地方,您可以看到 decorator 添加了诸如跟踪、日志记录、存储/缓存、线程锁定以及输出重定向之类的功能。
与这些修改相关(但实质略有区别)的是修饰 “之前” 和 “之后”。对于修饰之前/之后来说,一种有趣的可能性就是检查传递给函数的参数和函数返回值的类型。
如果这些类型并非如我们预期的一样,那么这种 typecheck() decorator 就可能会触发一个异常,或者采取一些纠正操作。

与这种 decorator 前/后类似的情况,我想到了 R 编程语言和 NumPy 特有的函数的 “elementwise” 应用。
在这些语言中,数学函数通常应用于元素序列中的每个元素,但也会应用于单个数字。

当然,map() 函数、列表内涵(list-comprehension)和最近的生成器内涵(generator-comprehension 都可以让您实现 elementwise 应用。
但是这需要较小的工作区来获得类似于 R 语言的行为:map() 所返回的序列类型通常是一个列表;如果您传递的是单个元素而不是一个序列,那么调用将失败。例如:
清单 11. map() 调用失败

>>> from math import sqrt
>>> map(sqrt, (4, 16, 25))
[2.0, 4.0, 5.0]
>>> map(sqrt, 144)
TypeError: argument 2 to map() must support iteration

创建一个可以 “增强” 普通数值函数的 decorator 并不困难:
清单 12. 将函数转换成 elementwise 函数

def elementwise(fn):
    def newfn(arg):
        if hasattr(arg,'__getitem__'):  # is a Sequence
            return type(arg)(map(fn, arg))
        else:
            return fn(arg)
    return newfn

@elementwise
def compute(x):
    return x**3 - 1

print compute(5)        # prints: 124
print compute([1,2,3])  # prints: [0, 7, 26]
print compute((1,2,3))  # prints: (0, 7, 26)

当然,简单地编写一个具有不同返回类型的 compute() 函数并不困难;毕竟 decorator 只需占据几行。但是作为对面向方面编程的一种认可,
这个例子让我们可以分离 那些在不同层次上运作的关注事项。
我们可以编写各种数值计算函数,希望它们都可转换成 elementwise 调用模型,而不用考虑参数类型测试和返回值类型强制转换的细节。

对于那些对单个事物或事物序列(此时要保留序列类型)进行操作的函数来说,elementwise() decorator 均可同样出色地发挥作用。
作为一个练习,您可尝试去解决如何允许相同的修饰后调用来接受和返回迭代器
(提示:如果您只是想迭代一次完整的 elementwise 计算,那么当且仅当传入的是一个迭代对象时,才能这样简化一些。)

您将碰到的大多数优秀的 decorator 都在很大程度上采用了这种组合正交关注的范例。
传统的面向对象编程,尤其是在诸如 Python 之类允许多重继承的语言中,都会试图使用一个继承层次结构来模块化关注事项。
然而,这仅会从一个祖先那里获取一些方法,而从其他祖先那里获取其他方法,因此需要采用一种概念,使关注事项比在面向方面的思想中更加分散。
要充分利用生成器,就要考虑一些与混搭方法不同的问题:可以处于方法本身的 “核心” 之外的关注事项为依据,使各 方法以不同方式工作。

修饰 decorator

在结束本文之前,我想为您介绍一种确实非常出色的 Python 模块,名为 decorator,它是由与我合著过一些图书的 Michele Simionato 编写的。
该模块使 decorator 的开发变得更加美妙。decorator 模块的主要组件具有某种自反式的优雅,它是一个称为 decorator() 的 decorator。
与未修饰的函数相比,使用 @decorator 修饰过的函数可以通过一种更简单的方式编写。(相关资料请参看 参考资料)。

Michele 已经为自己的模块编写了很好的文档,因此这里不再赘述;不过我非常乐意介绍一下它所解决的基本问题。decorator 模块有两大主要优势。
一方面,它使您可以编写出嵌套层次更少的 decorator,如果没有这个模块,您就只能使用更多层次(“平面优于嵌套”);
但更加有趣的是这样一个事实:它使得修饰过的函数可以真正地与其在元数据中未修饰的版本相匹配,这是我的例子中没有做到的。
例如,回想一下我们上面使用过的简单 “跟踪” decorator addspam():
清单 13. 一个简单的 decorator 是如何造成元数据崩溃的

>>> def useful(a, b): return a**2 + b**2
>>> useful.__name__
'useful'
>>> from inspect import getargspec
>>> getargspec(useful)
(['a', 'b'], None, None, None)
>>> @addspam
... def useful(a, b): return a**2 + b**2
>>> useful.__name__
'new'
>>> getargspec(useful)
([], 'args', None, None)

尽管这个修饰过的函数的确完成 了自己增强过的工作,但若进一步了解,就会发现这并不是完全正确的,尤其是对于那些关心这种细节的代码分析工具或 IDE 来说更是如此。
使用 decorator,我们就可以改进这些问题:
清单 14. decorator 更聪明的用法

>>> from decorator import decorator
>>> @decorator
... def addspam(f, *args, **kws):
...     print "spam, spam, spam"
...     return f(*args, **kws)
>>> @addspam
... def useful(a, b): return a**2 + b**2
>>> useful.__name__
'useful'
>>> getargspec(useful)
(['a', 'b'], None, None, None)

这对于编写 decorator 更加有利,同时,其保留行为的元数据的也更出色了。
当然,阅读 Michele 开发这个模块所使用的全部资料会使您回到大脑混沌的世界,我们将这留给 Simionato 博士一样的宇宙学家好了。

<a id="org87145ad"></a>

类装饰器

https://www.python.org/dev/peps/pep-3129/

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

推荐阅读更多精彩内容

  • 要点: 函数式编程:注意不是“函数编程”,多了一个“式” 模块:如何使用模块 面向对象编程:面向对象的概念、属性、...
    victorsungo阅读 1,485评论 0 6
  • Python进阶框架 希望大家喜欢,点赞哦首先感谢廖雪峰老师对于该课程的讲解 一、函数式编程 1.1 函数式编程简...
    Gaolex阅读 5,495评论 6 53
  • http://python.jobbole.com/85231/ 关于专业技能写完项目接着写写一名3年工作经验的J...
    燕京博士阅读 7,562评论 1 118
  • 前言 人生苦多,快来 Kotlin ,快速学习Kotlin! 什么是Kotlin? Kotlin 是种静态类型编程...
    任半生嚣狂阅读 26,179评论 9 118
  • 一大清早,老公就吩咐我给他洗羽绒服,我说天要下雨,不容易干,但他还是坚决要洗,我问口袋里有钱物吗?他说有张情人的照...
    黯黯红尘一路相伴阅读 142评论 0 1