#!/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. windows下利用批处理命令生成maven项目(java、javaWeb)

    觉得用IDE生成maven项目会卡住,很慢.就用cmd写了个cmd批处理命令来生成maven项目.效果如下 生成项目导入idea后结构如下 批处理命令代码如下 @echo off ::echo 请选择 ...

  2. Vue.js系列之项目搭建(1)

    项目搭建具体步骤如下: 1.安装node 到官网下载安装,我这里是win7系统. (中)https://nodejs.org/zh-cn/ (英)https://nodejs.org/en/ 2.安装 ...

  3. JPA使用指南 javax.persistence的注解配置

    @SuppressWarnings("serial") @Entity @Table(name="T_X") public class X implements ...

  4. bzoj1054: [HAOI2008]移动玩具 状压+爆搜即可

    题意:在一个4*4的方框内摆放了若干个相同的玩具,某人想将这些玩具重新摆放成为他心中理想的状态,规定移动时只能将玩具向上下左右四个方向移动,并且移动的位置不能有玩具,请你用最少的移动次数将初的玩具状态 ...

  5. 谈谈oracle里的join、left join、right join

    create table l as select 'left_1' as str,'1' as v from dual union allselect 'left_2' ,'2' as v from ...

  6. MVC4 绑定下拉框方法,解决编辑时不绑定值

    方法一  Controller 部分代码: public ActionResult Modify(int id) { //3.1.1 检查id //3.1.2根据id查询数据 Models.Stude ...

  7. 【LeetCode 8_字符串_实现】String to Integer (atoi)

    , INVALID}; int g_status; long long SubStrToInt(const char* str, bool minus) { ; : ; while (*str != ...

  8. OMAP4之DSP核(Tesla)软件开发学习(二)Linux内核驱动支持OMAP4 DSP核

    注:必须是Linux/arm 3.0以上内核才支持RPMSG,在此使用的是.config - Linux/arm 3.0.31 Kernel Configuration.(soure code fro ...

  9. [置顶] Android Studio apk打包以及获取apk签名信息

    首先说下Android Studio 主要分为以下几步 填写你的签名的一些信息 (例如签名的文件名.省份.密码 别名 就是你比如叫xxx 但是别名是xx张三 认证年限就是apk过期默认是25年 其他就 ...

  10. self-taught learning setting && semi-supervised learning

    参考文献: 摘于上文献: The more general and powerful setting is the self-taught learning setting, which does n ...