1. 闭包函数

python是一种面向对象的编程语言,在python中一切皆对象,这样就使得变量所拥有的属性,函数也同样拥有。这样我们就可以理解在函数内创建一个函数的行为是完全合法的。这种函数被叫做内嵌函数,这种函数只可以在外部函数的作用域内被正常调用,在外部函数的作用域之外调用会报错。
而如果内部函数里引用了外部函数里定义的对象(甚至是外层之外,但不是全局变量),那么此时内部函数就被称为闭包函数。闭包函数所引用的外部定义的变量被叫做自由变量。闭包从语法上看非常简单,但是却有强大的作用。闭包可以将其自己的代码和作用域以及外部函数的作用结合在一起。下面给出一个简单的闭包的例子:

def count():
    a = 1
    b = 1
    def sum():
        c = 1
        return a + c  # a - 自由变量
    return sum

条件:函数内部定义的函数;引用了外部变量但非全局变量。

2. python装饰器

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象(函数的指针)。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。

2.1 属性

实质: 是一个函数
参数:是你要装饰的函数名(并非函数调用)
返回:是装饰完的函数名(也非函数调用)
作用:为已经存在的对象添加额外的功能
特点:不需要对对象做任何的代码上的变动

2.2 函数装饰器

2.2.1 函数的函数装饰器(不带参数)

给函数添加计时功能:

import time

def decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        print(end_time - start_time)

    return wrapper

@decorator 
def func():
    time.sleep(0.8)

func() # 函数调用# 输出:0.8003056049346924

@decorator这个语法相当于 执行func = decorator(func),为func函数装饰并返回。装饰器函数- decorator,该函数的传入参数是func(被装饰函数),返回参数是内层函数。这里的内层函数-wrapper,其实就相当于闭包函数,它起到装饰给定函数的作用,wrapper参数为*args, **kwargs。*args表示的参数以列表的形式传入;**kwargs表示的参数以字典的形式传入。凡是以key=value形式的参数均存在kwargs中,剩下的所有参数都以列表的形式存于args中。使用*args, **kwargs可以保证装饰器可以应用于任何目标函数,否则需要保证内层函数wrapper和被装饰函数func的传入参数和返回值类型必须保持一致。

2.2.2 函数的函数装饰器(带参数)

def logging(level):
    def wrapper(func):
        def inner_wrapper(*args, **kwargs):
            print("[{level}]: | {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('hello')
# 输出结果为:
# [INFO]: | say()
# say hello!

上述代码使用了format方式,此外可以使用%方式

2.2.3 类方法的函数装饰器

import time

def decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        func(*args, **kwargs)
        end_time = time.time()
        print(end_time - start_time)
    return wrapper

class Method(object):

    @decorator 
    def func(self):
        time.sleep(0.8)

Method().func() # 函数调用

2.3 类装饰器

将一个类当做装饰器进行使用

class Decorator(object):
    def __init__(self, f):
        self.f = f

    def __call__(self):
        print("decorator start")
        self.f()
        print("decorator end")


@Decorator
def func():
    print("func")


func()

call()是一个特殊方法,它可将一个类实例变成一个可调用对象:

p = Decorator(func) # p是类Decorator的一个实例
p() # 实现了__call__()方法后,p可以被调用

要使用类装饰器必须实现类中的__call__()方法,就相当于将实例变成了一个方法。

2.4 装饰器链

多个装饰器的执行顺序:是从近到远依次执行(从最下方的开始)。

2.5 python装饰器库 - functools

def decorator(func):
    def inner_function():
        pass
    return inner_function

@decorator
def func():
    pass

print(func.__name__)

# 输出: inner_function

从结果看被装饰函数自身的信息丢失了,为了避免这种情况,需要使用借助functools.wraps()函数

from functools import wraps
def decorator(func):
    @wraps(func) 
    def inner_function():
        pass
    return inner_function

@decorator
def func():
    pass

print(func.__name__)

#输出: func

2.6 内置的装饰器

内置的装饰器和普通的装饰器原理是一样的,只不过返回的不是函数,而是类对象

2.6.1 @property装饰器:将方法变成属性调用的,可快速实现set和get方法

class Student:
    # 构造函数,私有化变量
    def __init__(self,name,score):
        self.__name=name
        self.__score=score

    @property
    def score(self):
        return self.__score
    
    @score.setter
    def score(self,score):
        if 0<score<101:
            self.__score=score
        else:
            print('请输入正确的分数!')

st1=Student("小明",98)
# 调用get方法
print(st1.score)
# 调用set方法
st1.score=68

print(st1.score)

2.6.2 @staticmethod@classmethod装饰器

作用:使用上述两个装饰器后,不需要实例化对象再调用方法,可以直接使用类名.方法名调用
差异:@staticmethod不需要表示自身对象的self和自身类的cls参数,就跟使用函数一样。@classmethod也不需要self参数,但第一个参数需要是表示自身类的cls参数。

2.7 使用第三方包

2.7.1 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实现的装饰器能完整保留原函数的namedocargs,唯一有问题的就是查看对象的源码inspect.getsource(func)返回的还是装饰器的源代码,需要改成inspect.getsource(func.__wrapped__)

2.7.2 wrapt

使用wrapt实现的装饰器不需要担心之前inspect中遇到的所有问题,nspect.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

2.8 注意点

不能装饰@staticmethod 或者@classmethod,装饰器不能使用在静态方法和类方法上

2.9 小结

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

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

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

Q.E.D.


Nothing really matters to me!