@函数装饰器

1. 函数装饰器的工作原理 

   装饰器(fuctional decorators)用于拓展原来函数功能的一种函数,目的是在不改变原函数名(或类名)的情况下,给函数增加新的功能。

   这个函数的特殊之处在于它的返回值也是一个函数,这个函数是内嵌“原”函数的函数。

   本质上,decorator 就是一个返回函数的高阶函数。装饰器函数在被装饰函数定义好后立即执行。

   1)最原始的装饰器

      以下面这个例子来讲解装饰器是怎么运行的:

         被装饰器装饰的函数 f 即使没有被调用,但因为被 @deco 修饰,所以装饰器的代码已经运行了,即函数 deco 已经运行了,deco内部嵌套的 wrapper 显然还没有

         运行,只是被定义了下,然后返回 wrapper 函数名变量执行流程如下:

         a. 先定义原函数 f。

         b.  然后运行装饰器函数,使得被装饰的原函数 f 重新引用了装饰器内部的那个函数名 wrapper

import time

def deco(f):
    def wrapper():
        start_time = time.time()
        f()
        end_time = time.time()
        execution_time = (end_time - start_time) * 1000
        print("time is %d ms" % execution_time )
    return wrapper

@deco         # 此行代码等同于,f = deco(f) = wrapper
def f():
    time.sleep(1)

f()   # f() = wrapper()

"""
output:
time is 1015 ms
"""

   2)带有固定参数的装饰器

      由1)中的解释,我们可以知道调用f时,实际调用的是wrapper,参数先通过wrapper的形参传入,然后wrapper内部再调用函数f,参数再间接传给f。

import time

def deco(f):
    def wrapper(a,b):
        start_time = time.time()
        f(a,b)
        end_time = time.time()
        execution_time = (end_time - start_time) * 1000
        print("time is %d ms" % execution_time)
    return wrapper

@deco
def f(a,b):
    time.sleep(1)

f(3,4)   # f = wrapper -> f(3,4) = wrapper(3,4)

   3)带有可变参数的装饰器

      如果可变参数语法不理解请先阅读另一篇博客 默认参数和可变参数

import time

def deco(f):
    def wrapper(*args, **kwargs):
        # args = (3,4), kwargs = {}
        start_time = time.time()
        # args 和 kwargs 分别被解包后,再整体按顺序赋值给 f 的形参
        f(*args, **kwargs)
        end_time = time.time()
        execution_time = (end_time - start_time)*1000
        print("time is %d ms" % execution_time)
    return wrapper

@deco
def f(a,b):
    time.sleep(1)

f(3,4)

   4)使用多个装饰器,装饰一个函数

      多个装饰器装饰的顺序是从里到外,也可以说由近到远,直接来看一个例子吧。

def deco01(f):
    def wrapper(*args, **kwargs):
        print("this is deco01")
        f(*args, **kwargs)
        print("deco01 end here")
    return wrapper

def deco02(f):
    def wrapper(*args, **kwargs):
        print("this is deco02")
        f(*args, **kwargs)
        print("deco02 end here")
    return wrapper

@deco01
@deco02
def f(a,b):
    print("我是被装饰的函数")

f(3,4)

"""
output:
this is deco01
this is deco02
我是被装饰的函数
deco02 end here
deco01 end here
"""

   按由近到远的原则,首先先装饰deco02,便得到下面的函数体:

print("this is deco02")
f(*args, **kwargs)
print("deco02 end here")

   然后继续装饰deco01,在已经装饰了deco02的基础上,继续扩展代码,函数体就变成这样:

print("this is deco01")
print("this is deco02")
f(*args, **kwargs)
print("deco02 end here")
print("deco01 end here")

   给个图,一目了然:

   @函数装饰器_第1张图片

 

2. python内置的函数装饰器

   有3种,分别是 @staticmethod、@classmethod 和 @property。

   其中 staticmethod()、classmethod() 和 property() 都是 Python 的内置函数。

   - 先解释下python中的类变量(或叫做静态变量),它定义在类中的位置如下:

class Test:
    stc_attr = 1         # 类变量
    def __init__(self,attr1,attr2):
     self.attr1 = attr1
        self.attr2 = attr2

   1)staticmethod 是类的静态方法:其跟成员方法的区别是没有 self 参数,并且可以在类不进行实例化的情况下调用,也可以在实例化的情况使用。

      由于静态方法不包含 self 参数,所以它只能通过类名访问类的静态成员变量和静态成员函数,如 类名.属性名、类名.方法名。

      注:如果不通过类名访问静态成员变量,那其实是重新定义了一个变量。不通过类名访问静态成员函数,会提示函数无定义。

class Test(object):
    x = 0.1
    @staticmethod
    def f():
        print("call static method.")
        Test.x = 0.5

Test.f()       # 静态方法无需实例化
Test().f()     # 也可以实例化后调用
print(Test.x)

"""
output:
call static method.
call static method.
0.5
"""  

   2)classmethod 是类方法:与成员方法的区别在于所接收的第一个参数不是 self (类实例的指针),而是cls(当前类的具体类型,有子类继承时,就是子类类型)。

      其使用语法和staticmethod方法一致。但静态方法的行为就是一个普通的全局函数,而类方法包含cls参数,那cls参数有啥用呢?

      解释:比如静态方法想要调用非静态的成员,必须知道类的具体类型,然后在函数内部构造一个实例来调用,在存在派生类的代码中,知道具体类型还挺麻烦,如果

            类名被修改,那代码就也得改。但 classmethod 方法却可以直接知道类的具体类型,即cls。看一个例子便清楚了:

class Test(object):
    a = 123
    def normalFoo(self):
        print('call normalFoo.')

    @staticmethod
    def staticFoo():
        print('call staticFoo.')
        print(Test.a)
        Test().normalFoo()  # 访问非静态成员

    @classmethod
    def classFoo(cls):
        print('call classFoo.')
        print(Test.a)
        cls().normalFoo()   # 访问非静态成员

Test.staticFoo()
Test.classFoo()

   3)property:把一个方法变成属性调用,起到既能检查属性,还能用属性的方式来访问该属性。

      a. 为什么需要它呢?我们在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把属性随便改:

class Student(object):
    def __init__(self, score = 0):
        self.score = score

s = Student()
s.score = 100
s.score = 200  # 分数为200明显不合理
s.score = -50  # 分数为负数也不合理
print(s.score)

      b. 对值进行约束的一个明显解决方案是隐藏属性score(使其私有)并定义新的 getter 和 setter 接口来操作它。可以按照下面这样改:

class Student(object):
    def __init__(self, value=0):
        self.setScore(value)

    def getScore(self):
        return self._score

    def setScore(self, value):
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

s = Student()
s.setScore(100)
s.setScore(105)     # 报错
s.setScore(-50)     # 报错
print(s.getScore())

       请注意,Python中不存在私有变量,但还是有一些简单的准则可以遵循。Python语言本身并不会做限制。这次更新虽然成功地实现了新的限制,

         但存在的一个大问题是,所有在其程序中实现我们前面的类的客户都必须修改他们的代码,将 s.score 修改为 s.getScore(),并且将像 s.score= val

         的所有赋值语句修改为 s.setScore(val)。这种重构可能会给客户带来数十多万行代码的麻烦。

      c. 这时property就派上用场了。@property真正强大的就是可以对属性增加约束来限制属性的定义。

class Student(object):
    def __init__(self, value=0):
        self._score = value

    @property        # 以需要定义的属性为方法名,如果没有@属性名.setter,则就是一个只读属性
    def score(self):
        return self._score

    @score.setter    # @property定义可访问属性的语法:以属性名为方法名,并在方法名上增加@属性名.setter
    def score(self, value):  
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

s = Student()
s.score = 100    #
s.score = 105    # 报错
s.score = -50    # 报错
print(s.score)

         这样就可以像普通属性那样操作,又可以进行约束。

  

你可能感兴趣的:(@函数装饰器)