喵吉欧尼酱

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 :: 管理 ::
Decorator(装饰器)
@符号修饰函数(有的语言称为:注释)
    python 2.4以后,增加了 @符号修饰函数 对函数进行修饰, python3.0/2.6又增加了对类的修饰。
    修饰符必须出现在函数定义前一行,不允许和函数定义在同一行。也就是说 @A def f(): 是非法的。

    简单的来说就是用一个新的对象来替换掉原有的对象,新的对象包含原有的对象,并且会处理它的执行结果和输入参数。
    python另外一个很有意思的属性:可以在函数中定义函数。
    其实总体说起来,装饰器其实也就是一个函数,一个用来包装函数的函数,返回一个修改之后的函数对象,将其重新赋值原来的标识符,并永久丧失对原始函数对象的访问。

    官方的说明:
    http://www.python.org/dev/peps/pep-0318/


代码上解释 Decorator(装饰器) 就是这样:
    @decomaker
    def func(arg1, arg2, ...):
        pass

    # 上面相当于这样写:
    result = decomaker(func)(arg1, arg2, ...)


范例(普通函数的用法,查看函数运行时间):
    import time
    def timeit(func):
        def wrapper(*args, **kwargs):
            start = time.time()
            func(*args, **kwargs)
            end =time.time()
            print( 'used:' + str(end - start) )
        return wrapper

    @timeit # 相当于: timeit(foo)()
    def foo():
        print( 'in foo()' )

    # 调用被修饰过的函数,跟普通函数没啥区别
    foo() # 打印: in foo()


    # 下面用一个不写“@”装饰的函数测试调用方法
    def foo2():
        print( 'in foo2()' )

    # 不被装饰的函数,真实的修饰这样写:
    timeit(foo2)() # 打印: in foo2()


范例(多个修饰函数,原函数带参数):
    def bold(fn):
        def wrapped(arg):
            return "<b>" + fn(arg) + "</b>"
        return wrapped

    def italic(fn):
        def wrapped(arg):
            return "<i>" + fn(arg) + "</i>"
        return wrapped

    @bold   # 相当于: bold(italic(hello))(arg)
    @italic # 相当于: italic(hello)(arg)
    def hello(s):
        return "hello " + s

    # 调用被修饰过的函数,跟普通函数没啥区别
    print( hello('holemar') ) # 打印: <b><i>hello holemar</i></b>


    # 下面用一个不写“@”装饰的函数测试调用方法
    def hello2(s):
        return "hello " + s

    # 不被装饰的函数,真实的修饰这样写:
    print( italic(hello2)('world') ) # 打印: <i>hello world</i>
    print( bold(hello2)('world') ) # 打印: <b>hello world</b>
    print( bold(italic(hello2))('world') ) # 打印: <b><i>hello world</i></b>


范例(装饰器带参数的用法,通用的处理输入和输出结果,这写法仅适用于py2.x):
    # 如果装饰器需要带参数,则里面需要嵌套两层函数(不带参数的只需嵌套一层),看下面的“相当于”就会明白
    def accepts(*types):
        def check_accepts(f):
            assert len(types) == f.func_code.co_argcount
            def new_f(*args, **kwds):
                for (a, t) in zip(args, types):
                    assert isinstance(a, t), "arg %r does not match %s" % (a,t)
                return f(*args, **kwds)
            new_f.func_name = f.func_name
            return new_f
        return check_accepts

    def returns(rtype):
        def check_returns(f):
            def new_f(*args, **kwds):
                result = f(*args, **kwds)
                assert isinstance(result, rtype), "return value %r does not match %s" % (result,rtype)
                return result
            new_f.func_name = f.func_name
            return new_f
        return check_returns

    # 注意这两个装饰器的顺序,如果倒过来会出错,因为 @accepts 先过滤了 func 函数,而 @returns 又过滤 @accepts 函数的结果
    @returns((int,float))  # 相当于: returns((int,float)) (accepts(int, (int,float))(func2)) (arg1, arg2)
    @accepts(int, (int,float)) # 相当于: accepts(int, (int,float)) (func2) (arg1, arg2)
    def func(arg1, arg2):
        return arg1 * arg2

    # 测试一下运行结果
    print( func(2, 3) )

    # 下面用一个不写“@”装饰的函数测试调用方法
    def func2(arg1, arg2):
        return arg1 * arg2

    # 下面的调用,要注意理解各个圆括号, 尤其最后一行嵌套的写法
    print( returns((int,float))(func2)(2, 5) )
    print( accepts(int, (int,float))(func2)(2, 5) )
    print( returns((int,float)) (accepts(int, (int,float))(func2)) (2, 5) )


内置的装饰器
    有三个,分别是 staticmethod, classmethod 和 property
    作用分别是把类中定义的实例方法变成静态方法、类方法和类属性。
    由于模块里可以定义函数,所以静态方法和类方法的用处并不是太多,除非你想要完全的面向对象编程。
    而属性也不是不可或缺的,Java没有属性也一样活得很滋润。使用频率较少,了解即可。

staticmethod 和 classmethod 的用法与区别:
    对于 classmethod 的参数, 需要隐式地传递类名, 而 staticmethod 参数中则不需要传递类名, 其实这就是二者最大的区别。
    对于 staticmethod 就是为了要在类中定义而设置的,一般来说很少这样使用,可以使用模块级(module-level)的函数来替代它。既然要把它定义在类中,想必有作者的考虑。
    对于 classmethod, 可以通过子类来进行重定义。


范例(staticmethod 和 classmethod 的用法与区别):
    class Person:
        def sayHi(self):  # self参数必须写,正常函数的写法
            print('Hello, how are you?')

        @staticmethod # 申明此方法是一个静态方法,外部可以直接调用
        def tt(a): # 静态方法,第一个参数不需要用 self
            print(a) # 第一个参数就是传过来的参数

        def ff(self):
            self.sayHi() # 正常方法的调用
            self.tt('dd') # 静态方法的调用

        @classmethod  # 申明此方法是一个类方法
        def class_method(class_name, arg1):
            c = class_name()
            c.sayHi() # 正常类方法的调用,用之前需要new这个类
            class_name.tt('cc') # 静态方法的调用
            print(arg1) # 第一个参数是类名,第二个参数开始才是传过来的参数

    p = Person()
    p.ff() # 正常方法的调用: self参数不需赋值, 必须先 new 出一个类才可以用

    Person.tt('tt') # 可以直接调用
    p.tt('tt') # 使用实例调用也行
    Person.class_method('cm') # 也可以直接调用


范例(利用 classmethod 实现单例模式):
    class IOLoop(object):
        def __init__(self):
            print( 'IOLoop.__init__' )

        @classmethod
        def instance(cls):
            if not hasattr(cls, "_instance"):
                cls._instance = cls()
            return cls._instance

        @classmethod
        def initialized(cls):
            """Returns true if the singleton instance has been created."""
            return hasattr(cls, "_instance")

        def service(self):
          print 'Hello,World'

    # 下面调用一下,看看效果
    print( IOLoop.initialized() ) # 打印: False  表示还没有初始化这个类
    ioloop = IOLoop.instance()  # 打印: IOLoop.__init__  表示执行了这个类的 __init__ 构造函数
    ioloop.service() # 打印: Hello,World

    print( IOLoop.initialized() ) # 打印: True   表示已经初始化这个类了
    ioloop = IOLoop.instance() # 没有打印,因为 __init__ 不需要再次执行
    ioloop.service() # 打印: Hello,World


property 属性用法(原理可参考文档“1.6.3.属性”)
  1.现在介绍第一种使用属性的方法(不使用 @property 的写法):
    在该类中定义三个函数,分别用作赋值、取值和删除变量(此处表达也许不很清晰,请看示例)

    # 假设定义了一个类:C, 该类必须继承自object类, 有一私有变量__x
    class C(object):
        def __init__(self):
            self.__x=None

        # 取值函数
        def getx(self):
            return self.__x

        # 赋值函数
        def setx(self,value):
            self.__x=value

        # 删除函数
        def delx(self):
            #del self.__x
            self.__x=None

        # 定变量名
        # property 函数原型为 property(fget=None, fset=None, fdel=None, doc=None), 所以根据自己需要定义相应的函数即可。
        x = property(getx, setx, delx, '属性x的doc')


    # 现在这个类中的x属性便已经定义好了,我们可以对它进行赋值、取值, 以及删除操作
    c=C() # new 一个实例
    c.x=100 # 赋值
    y=c.x # 取值
    print(y) # 打印: 100
    print(c.x) # 打印: 100

    del c.x # 删除变量
    print(c.x) # 打印: None
    print(C.x.__doc__) # 打印这属性的doc


  2.下面看第二种方法(在py2.6中新增, 使用 @property 修饰符的写法)
    注意同一属性的三个函数名要相同

    # 同样定义一个类:C, 该类也有一私有变量__x
    class C(object):
        def __init__(self):
            self.__x=None

        @property # 申明这是一个属性,同时也定义了取值函数、doc
        def x(self):
            '''属性x的doc
            '''
            return self.__x

        @x.setter # 赋值函数
        def x(self,value):
            self.__x=value

        @x.deleter # 删除函数
        def x(self):
            #del self.__x
            self.__x=None

    # 对属性进行赋值、取值, 以及删除的操作同上例, 不再写



functools 模块提供了两个装饰器。
    这个模块是Python 2.5后新增的,一般来说大家用的应该都高于这个版本。

    wraps(wrapped[, assigned][, updated]):
        函数是有几个特殊属性比如函数名,在被装饰后,上例中的函数名foo会变成包装函数的名字wrapper,这个装饰器可以解决这个问题,它能将装饰过的函数的特殊属性保留。

    total_ordering(cls):
        这个装饰器在特定的场合有一定用处,但是它是在Python 2.7后新增的。
        它的作用是为实现了至少 __lt__, __le__, __gt__, __ge__ 其中一个的类加上其他的比较方法,这是一个类装饰器。
        如果觉得不好理解,不妨仔细看看这个装饰器的源代码。

范例(functools.wraps 装饰器的用法与用途):
    import time
    import functools
    def timeit(func):
        @functools.wraps(func)
        def wrapper():
            start = time.clock()
            func()
            end =time.clock()
            print 'used:', end - start
        return wrapper

    @timeit
    def foo():
        print 'in foo()'

    foo()
    print(foo.__name__) # 打印: foo,  没有 @functools.wraps 装饰过的话,打印 wrapper

 

posted on 2017-08-19 10:16  喵吉欧尼酱  阅读(115)  评论(0)    收藏  举报