python黑魔法——装饰器

目录

一、简单的例子

使用@装饰符(下面的@deco等同于myfunc = deco(myfunc))

def deco(func):
    print("step in deco")
    func()
    print("leave out deco")
    return func

@deco
def myfunc():
    print("myfunc() called")

myfunc()
myfunc()

输出:
>>> step in deco
    myfunc() called
    leave out deco
    myfunc() called
    myfunc() called

可以发现装饰器中的内容只执行了一次,而myfunc函数却多执行了一次(其实是在@deco这句执行的),这是为什么呢?回到之前说的@deco等同于myfunc = deco(myfunc),此时@deco执行了一次myfunc,并且返回myfunc, 也就是说其实是执行了myfunc = myfunc(myfunc被赋值为自己本身),所以并没有达到装饰的效果。
其实我们想要的效果是myfunc = deco 而非 myfunc = deco(myfunc)

改进的方法:装饰器加入内层函数

def deco(func):
    def wrapper():
        print("step in deco")
        func()
        print("leave out deco")
    return wrapper

@deco
def myfunc():
    print("myfunc() called")

myfunc()
myfunc()

输出:
>>> step in deco
    myfunc() called
    leave out deco
    step in deco
    myfunc() called
    leave out deco

这时候,@deco并没有执行myfunc,而是返回带有func参数闭包的wrapper函数的引用,这是我们调用myfunc的时候就相当于调用了wrapper,就起到了装饰的效果。


二、修饰带参数和存在返回值的函数

def deco(func):
    def wrapper(a, b):
        print("step in deco")
        ret = func(a, b)
        print("leave out deco")
        return ret
    return wrapper

@deco
def myfunc(a, b):
    print("myfunc called")
    return a + b

print(myfunc(3, 5))

输出:
>>> step in deco
    myfunc called
    leave out deco
    8

@装饰符的意义和上面的简单例子相同,只是在内层wrapper函数加入了参数和返回值


三、带参数的装饰器

有时候我们想让一个装饰器有多种装饰功能,在使用@装饰符的时候通过向装饰器传递参数以指明调用哪种装饰功能

import time

def deco(arg):
    def _deco(func):
        def __deco():
            if arg == 'year':
                print("the year is: {0}".format(time.localtime(time.time()).tm_year))
            elif arg == 'month':
                print("the month is: {0}".format(time.localtime(time.time()).tm_mon))
            elif arg == 'day':
                print("the day is: {0}".format(time.localtime(time.time()).tm_mday))
            else:
                print("the time is: {0}".format(time.ctime()))
            func()
        return __deco
    return _deco

@deco("year")
def myfunc():
    print("myfunc() called")

@deco("month")
def myfunc2():
    print("myfunc2() called")

@deco("")
def myfunc3():
    print("myfunc3() called")

myfunc()
myfunc2()
myfunc3()

输出:
>>> the year is: 2018
    myfunc() called
    the month is: 6
    myfunc2() called
    the time is: Wed Jun  6 01:26:33 2018
    myfunc3() called

同理,此处的@deco(“year”)等同于myfunc = deco(“year”)(myfunc),其实最终等同于myfunc = __deco,所以需要三层函数来实现


四、装饰器调用顺序

import time

def deco(arg):
    def _deco(func):
        def __deco():
            if arg == 'year':
                print("the year is: {0}".format(time.localtime(time.time()).tm_year))
            elif arg == 'month':
                print("the month is: {0}".format(time.localtime(time.time()).tm_mon))
            elif arg == 'day':
                print("the day is: {0}".format(time.localtime(time.time()).tm_mday))
            else:
                print("the time is: {0}".format(time.ctime()))
            func()
        return __deco
    return _deco

@deco("year")
@deco("month")
@deco("day")
def myfunc():
    print("myfunc called")

myfunc()

输出:
>>> the year is: 2018
    the month is: 6
    the day is: 6
    myfunc called

再来看另一个例子:

def deco1(func):
    print("step in deco1")
    def wrapper1():
        print("step in wrapper1")
        func()
        print("leave out wrapper1")
    print("leave out deco1")
    return wrapper1

def deco2(func):
    print("step in deco2")
    def wrapper2():
        print("step in wrapper2")
        func()
        print("leave out wrapper2")
    print("leave out deco2")
    return wrapper2

def deco3(func):
    print("step in deco3")
    def wrapper3():
        print("step in wrapper3")
        func()
        print("leave out wrapper3")
    print("leave out deco3")
    return wrapper3

@deco1
@deco2
@deco3
def myfunc():
    print("myfunc() called")

myfunc()

输出:
>>> step in deco3
    leave out deco3
    step in deco2
    leave out deco2
    step in deco1
    leave out deco1
    step in wrapper1
    step in wrapper2
    step in wrapper3
    myfunc() called
    leave out wrapper3
    leave out wrapper2
    leave out wrapper1

可以看到,调用装饰器的顺序和@装饰符的声明顺序相反的,但是调用wrapper的顺序却和装饰符的声明顺序相同。此处等同于myfunc = deco3(deco2(deco1(myfunc))),其实就是内层函数wrapper3装饰了myfunc,wrapper2装饰了wrapper3, wrapper1装饰了wrapper2.


五、python内置的装饰器

在Python中有三个内置的装饰器,都是跟class相关的:staticmethod、classmethod 和property。

  • staticmethod 是类静态方法,其跟成员方法的区别是没有 self 参数,并且可以在类不进行实例化的情况下调用
  • classmethod 与成员方法的区别在于所接收的第一个参数不是 self (类实例的指针),而是cls(当前类的具体类型)
  • property 是属性的意思,表示可以通过通过类实例直接访问的信息(可以用其实现C#的get和set方法)

三者的具体用法:

@property

使调用类中的方法像引用类中的字段属性一样。被修饰的特性方法,内部可以实现处理逻辑,但对外提供统一的调用方式。遵循了统一访问的原则。
property函数原型为property(fget=None,fset=None,fdel=None,doc=None)
示例:

# coding: utf-8
class TestClass:
    name = "test"

    def __init__(self, name):
        self.name = name

    @property
    def sayHello(self):
        print "hello", self.name

cls = TestClass("felix")
print "通过实例引用属性"
print cls.name
print "像引用属性一样调用@property修饰的方法"
cls.sayHello

实现类似java和C#get的功能

class Boy(object):
    def __init__(self, name):
        self.name = name

        @property
        def name(self):
            return self.name

        # 下面这个装饰器是由上面的@property衍生出来的装饰器
        @name.setter
        def name(self, new_name):
            self.name = new_name

        # 下面这个装饰器是由上面的@property衍生出来的装饰器
        @name.deleter
        def del_name(self):
            del self.name

# 实例化
boy = Boy('Tom')

# 更新name(set方法)
boy.name = 'Alice'
# 获得'Alice'(get方法)
print(boy.name) 
# 删除name(del方法)
del boy.name

@staticmethod

将类中的方法装饰为静态方法,即类不需要创建实例的情况下,可以通过类名直接引用。到达将函数功能与实例解绑的效果。

class TestClass:
    name = "test"

    def __init__(self, name):
        self.name = name

    @staticmethod
    def fun(self, x, y):
        return  x + y

cls = TestClass("felix")
print ("通过实例引用方法")
print (cls.fun(None, 2, 3)) # 参数个数必须与定义中的个数保持一致,否则报错
print ("类名直接引用静态方法")
print (TestClass.fun(None, 2, 3)) # 参数个数必须与定义中的个数保持一致,否则报错


输出:
>>> 通过实例引用方法
    5
    类名直接引用静态方法
    5

@classmethod

类方法的第一个参数是一个类,是将类本身作为操作的方法。类方法被哪个类调用,就传入哪个类作为第一个参数进行操作。

class Car(object):
    car = "audi"

    @classmethod
    def value(self, category): # 可定义多个参数,但第一个参数为类本身
        print ("{0} car of {1}".format(category, self.car))

class BMW(Car):
    car = "BMW"

class Benz(Car):
    car = "Benz"

print ("通过实例调用")
baoma = BMW()
baoma.value("Normal") # 由于第一个参数为类本身,调用时传入的参数对应的时category

print ("通过类名直接调用")
Benz.value("SUV")


输出:
>>> 通过实例调用
    Normal car of BMW
    通过类名直接调用
    SUV car of Benz

猜你喜欢

转载自blog.csdn.net/MSDN_tang/article/details/80602882