#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time    : 2018/5/28 14:06
# @File    : lianxi.py

#
# import time
# def decorator(func):
#     def wrapper(*args, **kwargs):
#         start_time = time.time()
#         func()
#         end_time = time.time()
#         print(end_time - start_time)
#
#     return wrapper
#
# @decorator
# def func():
#     time.sleep(0.8)
#
# func() # 函数调用

# 示例一

# #既不需要侵入,也不需要函数重复执行
# import time
#
# def deco(func):
#     def wrapper():
#         startTime = time.time()
#         func()
#         endTime = time.time()
#         msecs = (endTime - startTime)*1000
#         print("time is %d ms" %msecs)
#     return wrapper
#
#
# @deco
# def func():
#     print("hello")
#     time.sleep(1)
#     print("world")
#
# if __name__ == '__main__':
#     f = func #这里f被赋值为func,执行f()就是执行func()
#     f()

# #带有参数的装饰器
# import time
#
# def deco(func):
#     def wrapper(a,b):
#         startTime = time.time()
#         func(a,b)
#         endTime = time.time()
#         msecs = (endTime - startTime)*1000
#         print("time is %d ms" %msecs)
#     return wrapper
#
#
# @deco
# def func(a,b):
#     print("hello,here is a func for add :")
#     time.sleep(1)
#     print("result is %d" %(a+b))
#
# if __name__ == '__main__':
#     f = func
#     f(3,4)
#     #func()

# #带有不定参数的装饰器
# import time
#
# def deco(func):
#     def wrapper(*args, **kwargs):
#         startTime = time.time()
#         func(*args, **kwargs)
#         endTime = time.time()
#         msecs = (endTime - startTime)*1000
#         print("time is %d ms" %msecs)
#     return wrapper
#
#
# @deco
# def func(a,b):
#     print("hello,here is a func for add :")
#     time.sleep(1)
#     print("result is %d" %(a+b))
#
# @deco
# def func2(a,b,c):
#     print("hello,here is a2 func for add :")
#     time.sleep(1)
#     print("result2 is %d" %(a+b+c))
#
#
# if __name__ == '__main__':
#     f = func
#     func2(3,4,5)
#     f(3,4)
#     #func()
# 运行结果
# hello,here is a2
# func
# for add:
#     result2 is 12
# time is 1000
# ms
# hello,here is a
# func
# for add:
#     result is 7
# time is 1000
# ms

#
# #多个装饰器
#
# import time
#
# def deco01(func):
#     def wrapper(*args, **kwargs):
#         print("this is deco01")
#         startTime = time.time()
#         func(*args, **kwargs)
#         endTime = time.time()
#         msecs = (endTime - startTime)*1000
#         print("time is %d ms" %msecs)
#         print("deco01 end here")
#     return wrapper
#
# def deco02(func):
#     def wrapper(*args, **kwargs):
#         print("this is deco02")
#         func(*args, **kwargs)
#
#         print("deco02 end here")
#     return wrapper
#
# @deco01
# @deco02
# def func(a,b):
#     print("hello,here is a func for add :")
#     time.sleep(1)
#     print("result is %d" %(a+b))
#
#
#
# if __name__ == '__main__':
#     f = func
#     f(3,4)
#     #func()
#
# # 注意执行顺序
# '''
# this is deco01
# this is deco02
# hello,here is a func for add :
# result is 7
# deco02 end here
# time is 1000 ms
# deco01 end here
# '''

# 示例二
# 版本一1.1
# def debug(func):
#     def wrapper():
#         print( "[DEBUG]: enter {}()".format(func.__name__))
#         return func()
#     return wrapper
#
# def say_hello():
#     print( "hello!")
#
# say_hello = debug(say_hello)  # 添加功能并保持原函数名不变
# say_hello()
# 1.2 版本
# def debug(func):
#     def wrapper():
#         print("[DEBUG]: enter {}()".format(func.__name__))
#         return func()
#     return wrapper
#
# @debug
# def say_hello():
#     print("hello!")
# say_hello()

# 2.0 版本
# def debug(func):
#     def wrapper(something):  # 指定一毛一样的参数
#         print( "[DEBUG]: enter {}()".format(func.__name__))
#         return func(something)
#     return wrapper  # 返回包装过函数
#
# @debug
# def say(something):
#     print( "hello {}!".format(something))
#
# say('decorator.......')

# # 版本 3
# def debug(func):
#     def wrapper(*args, **kwargs):  # 指定宇宙无敌参数
#         print( "[DEBUG]: enter {}()".format(func.__name__))
#         print( 'Prepare and say...',)
#         return func(*args, **kwargs)
#     return wrapper  # 返回
#
# @debug
# def say(something):
#     print( "hello {}!".format(something))
# @debug
# def say2(something, aaa):
#     print( "hello {},------{}!".format(something, aaa))
#
# say2('aaa', 'vvvv')
# say('aaa')

# # 版本 4
# def logging(level):
#     def wrapper(func):
#         def inner_wrapper(*args, **kwargs):
#             print( "[{level}]: enter function {func}()".format(level=level,func=func.__name__))
#             return func(*args, **kwargs)
#         return inner_wrapper
#     return wrapper
#
# @logging(level='INFO')
# def say(something):
#     print( "say {}!".format(something))
#
# # 如果没有使用@语法,等同于
# # say = logging(level='INFO')(say)
#
# @logging(level='DEBUG')
# def do(something):
#     print ("do {}...".format(something))
#
# if __name__ == '__main__':
#     say('hello')
#     do("my work")

# # 版本 5
# class logging(object):
#     def __init__(self, func):
#         self.func = func
#
#     def __call__(self, *args, **kwargs):
#         print( "[DEBUG]: enter function {func}()".format(func=self.func.__name__))
#         return self.func(*args, **kwargs)
# @logging
# def say(something):
#     print( "say {}!".format(something))
#
# say('hello')

# 版本  6
class logging(object):
    def __init__(self, level='INFO'):
        self.level = level

def __call__(self, func):  # 接受函数
        def wrapper(*args, **kwargs):
            print("[{level}]: enter function {func}()".format(level=self.level,func=func.__name__))

func(*args, **kwargs)

return wrapper  # 返回函数

@logging(level='INFO')
def say(something):
    print("say {}!".format(something))

say('什么鬼')

装饰器里的那些坑

装饰器可以让你代码更加优雅,减少重复,但也不全是优点,也会带来一些问题。
位置错误的代码

让我们直接看示例代码。

def html_tags(tag_name):
    print 'begin outer function.'
    def wrapper_(func):
        print "begin of inner wrapper function."
        def wrapper(*args, **kwargs):
            content = func(*args, **kwargs)
            print "<{tag}>{content}</{tag}>".format(tag=tag_name, content=content)
        print 'end of inner wrapper function.'
        return wrapper
    print 'end of outer function'
    return wrapper_

@html_tags('b')
def hello(name='Toby'):
    return 'Hello {}!'.format(name)

hello()
hello()

在装饰器中我在各个可能的位置都加上了print语句,用于记录被调用的情况。你知道他们最后打印出来的顺序吗?如果你心里没底,那么最好不要在装饰器函数之外添加逻辑功能,否则这个装饰器就不受你控制了。以下是输出结果:

begin outer function.
end of outer function
begin of inner wrapper function.
end of inner wrapper function.
<b>Hello Toby!</b>
<b>Hello Toby!</b>

错误的函数签名和文档

装饰器装饰过的函数看上去名字没变,其实已经变了。

def logging(func):
    def wrapper(*args, **kwargs):
        """print log before a function."""
        print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
        return func(*args, **kwargs)
    return wrapper

@logging
def say(something):
    """say something"""
    print "say {}!".format(something)

print say.__name__  # wrapper

为什么会这样呢?只要你想想装饰器的语法糖@代替的东西就明白了。@等同于这样的写法。

say = logging(say)

logging其实返回的函数名字刚好是wrapper,那么上面的这个语句刚好就是把这个结果赋值给say,say的__name__自然也就是wrapper了,不仅仅是name,其他属性也都是来自wrapper,比如doc,source等等。

使用标准库里的functools.wraps,可以基本解决这个问题。

from functools import wraps

def logging(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """print log before a function."""
        print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
        return func(*args, **kwargs)
    return wrapper

@logging
def say(something):
    """say something"""
    print "say {}!".format(something)

print say.__name__  # say
print say.__doc__ # say something

看上去不错!主要问题解决了,但其实还不太完美。因为函数的签名和源码还是拿不到的。

import inspect
print inspect.getargspec(say)  # failed
print inspect.getsource(say)  # failed

如果要彻底解决这个问题可以借用第三方包,比如wrapt。后文有介绍。
不能装饰@staticmethod 或者 @classmethod

当你想把装饰器用在一个静态方法或者类方法时,不好意思,报错了。

class Car(object):
    def __init__(self, model):
        self.model = model

@logging  # 装饰实例方法,OK
    def run(self):
        print "{} is running!".format(self.model)

@logging  # 装饰静态方法,Failed
    @staticmethod
    def check_model_for(obj):
        if isinstance(obj, Car):
            print "The model of your car is {}".format(obj.model)
        else:
            print "{} is not a car!".format(obj)

"""
Traceback (most recent call last):
...
  File "example_4.py", line 10, in logging
    @wraps(func)
  File "C:\Python27\lib\functools.py", line 33, in update_wrapper
    setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'staticmethod' object has no attribute '__module__'
"""

前面已经解释了@staticmethod这个装饰器,其实它返回的并不是一个callable对象,而是一个staticmethod对象,那么它是不符合装饰器要求的(比如传入一个callable对象),你自然不能在它之上再加别的装饰器。要解决这个问题很简单,只要把你的装饰器放在@staticmethod之前就好了,因为你的装饰器返回的还是一个正常的函数,然后再加上一个@staticmethod是不会出问题的。

class Car(object):
    def __init__(self, model):
        self.model = model

@staticmethod
    @logging  # 在@staticmethod之前装饰,OK
    def check_model_for(obj):
        pass

如何优化你的装饰器

嵌套的装饰函数不太直观,我们可以使用第三方包类改进这样的情况,让装饰器函数可读性更好。
decorator.py

decorator.py 是一个非常简单的装饰器加强包。你可以很直观的先定义包装函数wrapper(),再使用decorate(func, wrapper)方法就可以完成一个装饰器。

from decorator import decorate

def wrapper(func, *args, **kwargs):
    """print log before a function."""
    print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
    return func(*args, **kwargs)

def logging(func):
    return decorate(func, wrapper)  # 用wrapper装饰func

你也可以使用它自带的@decorator装饰器来完成你的装饰器。

from decorator import decorator

@decorator
def logging(func, *args, **kwargs):
    print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
    return func(*args, **kwargs)

decorator.py实现的装饰器能完整保留原函数的name,doc和args,唯一有问题的就是inspect.getsource(func)返回的还是装饰器的源代码,你需要改成inspect.getsource(func.__wrapped__)。
wrapt

wrapt是一个功能非常完善的包,用于实现各种你想到或者你没想到的装饰器。使用wrapt实现的装饰器你不需要担心之前inspect中遇到的所有问题,因为它都帮你处理了,甚至inspect.getsource(func)也准确无误。

import wrapt

# without argument in decorator
@wrapt.decorator
def logging(wrapped, instance, args, kwargs):  # instance is must
    print "[DEBUG]: enter {}()".format(wrapped.__name__)
    return wrapped(*args, **kwargs)

@logging
def say(something): pass

使用wrapt你只需要定义一个装饰器函数,但是函数签名是固定的,必须是(wrapped, instance, args, kwargs),注意第二个参数instance是必须的,就算你不用它。当装饰器装饰在不同位置时它将得到不同的值,比如装饰在类实例方法时你可以拿到这个类实例。根据instance的值你能够更加灵活的调整你的装饰器。另外,args和kwargs也是固定的,注意前面没有星号。在装饰器内部调用原函数时才带星号。

如果你需要使用wrapt写一个带参数的装饰器,可以这样写。

def logging(level):
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        print "[{}]: enter {}()".format(level, wrapped.__name__)
        return wrapped(*args, **kwargs)
    return wrapper

@logging(level="INFO")
def do(work): pass

关于wrapt的使用,建议查阅官方文档,在此不在赘述。

http://wrapt.readthedocs.io/en/latest/quick-start.html

小结

Python的装饰器和Java的注解(Annotation)并不是同一回事,和C#中的特性(Attribute)也不一样,完全是两个概念。

装饰器的理念是对原函数、对象的加强,相当于重新封装,所以一般装饰器函数都被命名为wrapper(),意义在于包装。函数只有在被调用时才会发挥其作用。比如@logging装饰器可以在函数执行时额外输出日志,@cache装饰过的函数可以缓存计算结果等等。

而注解和特性则是对目标函数或对象添加一些属性,相当于将其分类。这些属性可以通过反射拿到,在程序运行时对不同的特性函数或对象加以干预。比如带有Setup的函数就当成准备步骤执行,或者找到所有带有TestMethod的函数依次执行等等。

至此我所了解的装饰器已经讲完,但是还有一些内容没有提到,比如装饰类的装饰器。有机会再补充。谢谢观看。

各种装饰器demo及优化的更多相关文章

  1. python 装饰器demo

    本质就是一个函数,这个函数符合闭包语法结构,可以在函数不需要改变任何代码情况下增加额外功装饰器的返回值是一个函数的引用 功能:1.引入日志:2.函数执行时间统计:3.执行函数前预备处理:4.执行函数后 ...

  2. python基础整理4——面向对象装饰器惰性器及高级模块

    面向对象编程 面向过程:根据业务逻辑从上到下写代码 面向对象:将数据与函数绑定到一起,进行封装,这样能够更快速的开发程序,减少了重复代码的重写过程 面向对象编程(Object Oriented Pro ...

  3. python高级-装饰器(19)

    一.什么是闭包 先看一个例子: #定义一个函数 def test(number): #在函数内部在定义一个函数,并且这个函数用到外围函数的变量 #那么将这个函数及用到的一些变量称之为闭包 def te ...

  4. Python装饰器使用技巧

    装饰器 装饰器是程序开发中经常会用到的一个功能,用好了装饰器,开发效率如虎添翼,所以这也是Python面试中必问的问题,但对于好多初次接触这个知识的人来讲,这个功能有点绕,自学时直接绕过去了,然后面试 ...

  5. python3-三个demo带你入门装饰器

    装饰器入门 在不修改程序源代码和程序调用方式的情况下,扩展程序功能时不得不用到装饰器. python中的装饰器可谓功能强大,强大到刚接触它就被它弄得措手不及. 但是,静下心来好好研究,那可是回味无穷. ...

  6. 重学 Java 设计模式:实战装饰器模式(SSO单点登录功能扩展,增加拦截用户访问方法范围场景)

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 对于代码你有编程感觉吗 很多人写代码往往是没有编程感觉的,也就是除了可以把功能按照固 ...

  7. python 函数之装饰器,迭代器,生成器

    装饰器 了解一点:写代码要遵循开发封闭原则,虽然这个原则是面向对象开发,但也适用于函数式编程,简单的来说,就是已经实现的功能代码不允许被修改但 可以被扩展即: 封闭:已实现功能的代码块 开发:对扩张开 ...

  8. python学习笔记之装饰器、递归、算法(第四天)

    参考老师的博客: 金角:http://www.cnblogs.com/alex3714/articles/5161349.html 银角:http://www.cnblogs.com/wupeiqi/ ...

  9. 【转】详解Python的装饰器

    原文链接:http://python.jobbole.com/86717/ Python中的装饰器是你进入Python大门的一道坎,不管你跨不跨过去它都在那里. 为什么需要装饰器 我们假设你的程序实现 ...

随机推荐

  1. 带宽检测工具iftop

    1.安装 # yum install iftop –y 2.使用 # iftop -i eth0 -n # iftop -i eth0 -P 说明: 中间的<= =>这两个左右箭头,表示的 ...

  2. Java回顾之一些基础概念

    类的初始化顺序 在Java中,类里面可能包含:静态变量,静态初始化块,成员变量,初始化块,构造函数.在类之间可能存在着继承关系,那么当我们实例化一个对象时,上述各部分的加载顺序是怎样的? 首先来看代码 ...

  3. PHP自定义XML类实现数组到XML文件的转换

    这两天在公司写和各应用商店应用内搜索的接口,大致就像百度应用内搜索这样的东西,具体可以点下面的链接查看. 百度应用内搜索 有的应用商店需要JSON格式的数据,所以我只需要用下面的语句就可以返回对方服务 ...

  4. poj3680

    题解: 相邻的建边 每一段建边 然后见一个原点,汇点 代码: #include<cstdio> #include<cmath> #include<cstring> ...

  5. ISE创建Microblaze软核(二)

    ISE创建Microblaze软核(二) (2012-07-13 15:09:08) 转载▼ 标签: 杂谈 分类: FPGA开发 第四步 进入Platform Studio操作界面 通过向导创建软核后 ...

  6. JDK配置 java跨平台性

    jdk 虚拟机jre 依赖包javac 编译java 运行JAVA_HOME 一个存储jdk路径的自定义的变量,方便其他地方配置以后更改方便其他地方调用JAVA_HOME使用%JAVA_HOME%配置 ...

  7. Openlayers4中地图的导出

    概述: 本文讲述Openlayers4中地图的导出,包括调用天地图切片跨域.Geoserver11 WMS跨域等. 效果: 导出图片 页面展示 实现代码: document.getElementByI ...

  8. dir listing 目录文件列表索引

    一般而言,网站应用都有一个入口,比如说:index.php,index.html,app.js等.通过这个路口,以及相应的路由功能,去到网站各个功能版块. 而网站的目录结构,目录里面的文件列表,一般都 ...

  9. js实现trim()方法

    在面向对象编程里面去除字符串左右空格是很容易的事,可以使用trim().ltrim() 或 rtrim(),在jquery里面使用$.trim()也可以轻松的实现.但是在js中却没有这个方法.下面的实 ...

  10. Codeforces 165 E. Compatible Numbers【子集前缀和】

    LINK 题目大意 给你一个数组,问你数组中的每个数是否可以在数组里面找到一个数和他and起来是0,如果可以就输出这个数,否则就输出-1 思路 首先很显然的是可以考虑找到每个数每一位都取反的数的子集 ...