Python学习Day8(下) ---- 闭包与装饰器

1. __ call __方法

python中一切皆对象,一个方法可以调用,称可调用对象。
一个类可以变成一个可调用对象,只要实现了__ call __方法。

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

    def __call__(self, friend):
        print('My name is %s...' % self.name)
        print('My friend is %s...' % friend)

class Fib(object):
    def __init__(self):
        pass
    def __call__(self,num):
        a, b = 0, 1
        self.l = []

        for i in range(num):
            self.l.append(a)
            a, b = b, a+b
        return self.l
    def __str__(self):
        return str(self.l)

p = Person('Bob', 'male')
p('Tim')
f = Fib()
print(f(10))

>>> My name is Bob...
>>> My friend is Tim...
>>> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

函数内部再定义一个函数,并且内部函数用到了外部函数作用域里的变量(enclosing),那么将这个内部函数以及用到的外部函数内的变量一起称为闭包(Closure)。装饰器(decorator)接受一个callable对象(可以是函数或者实现了call方法的类)作为参数,并返回一个callable对象,它经常用于有切面需求的场景,比如:插入日志、性能测试(函数执行时间统计)、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用

2. 函数

  • 函数定义
    在讨论装饰器之前先谈谈函数,因为装饰器的本质终究还是函数。
def fun(n):
    print(n)

f = fun
print(f)
f(1)

>>> 
>>> 1

上面例子中声明了一个函数,名字叫fun,fun指向了一个函数function对象,地址在0x002DF7C8,调用函数只需给函数名加上括号(有参数就传参)

  • 函数作为返回值
    一直有提到的,python中一切皆对象,函数也可以作为其他函数的返回值
def fun():
    return 'this is fun'

def bar():
    return fun

print(bar()())
print(fun())

b = bar()
print(b)
print(fun)

>>> this is fun
>>> this is fun
>>> 
>>> 

bar()的返回值是函数对象,可以对返回值继续调用,调用bar()等价于fun,两者指向同一内存地址的对象

  • 函数作为参数
def fun():
    return 'this is fun'

def bar(a):
    return a()

print(bar(fun))

>>> this is fun

bar接收一个参数,参数是可调用的对象,返回该对象的调用执行结果,例子中a和fun两个变量名都指向了同一个函数对象,执行a()相当于执行了fun()

3. 闭包

def outer(x):
    def inner():
        print(x)

    return inner

closure = outer(5)
closure()

函数内部再定义一个函数,并且内部函数用到了外部函数作用域里的变量(enclosing),那么将这个内部函数以及用到的外部函数内的变量一起称为闭包(Closure)

4. 装饰器

经过上面对函数和闭包概念的了解,现在开始切入正题----装饰器

装饰器(decorator)接受一个callable对象(可以是函数或者实现了call方法的类)作为参数,并返回一个callable对象

它经常用于有切面需求的场景,比如:插入日志、性能测试(函数执行时间统计)、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

4.1 修饰无参的函数

举个简单的例子,假如执行下面的业务:

def work():
    print('工作中....')

为了方便维护和调试,需要加上日志:

import datetime

def work():
    print(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), '开始记录日志;')
    print('工作中....')
    print(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), '结束记录日志。')

work()

>>> 2019-07-12 18:29:39 开始记录日志;
>>> 工作中....
>>> 2019-07-12 18:29:39 结束记录日志。

要实现日志记录的功能就要侵入代码去修改,如果有很多个这样的函数需要加日志,name就会很麻烦,这时候装饰器就能派上用场了

import datetime

def outer(fun):
    def inner():
        print(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), '开始记录日志;')
        fun()
        print(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), '结束记录日志。')
    return inner

def work():
    print('工作中....')

w = outer(work)
w()

这样做没有修改原来work()里面的任何代码逻辑,其中outer函数就是一个装饰器,它接收的参数是一个函数,返回的是一个新函数的闭包,装饰器本质是一个函数,而闭包至少有里函数和外函数,那么一般写一个装饰器至少嵌套3个函数。

乍一看装饰器很麻烦,但装饰器一旦定义好,很多相同需求的函数都能使用,python中还提供了语法糖@,用在函数定义处:

@outer
def work():
    print('工作中....')

work()

>>> 2019-07-12 18:39:25 开始记录日志;
>>> 工作中....
>>> 2019-07-12 18:39:25 结束记录日志。

这样一看简洁、优雅了许多,

4.2 修饰的函数带参数
def login_required(func):
    def inner(*args, **kwargs):
        if func.__name__ == 'f1':
            print(func.__name__, ' 权限验证成功')
            func(*args, **kwargs)
        else:
            print(func.__name__, ' 权限验证失败')
    return inner

@login_required
def f1(a, b):
    print('function f1, args: ', a, ',', b)

f1(10, 20)

>>> f1  权限验证成功
>>> function f1, args:  10 , 20

Python中的* args和** kwargs可以匹配任意长度的位置参数或关键字参数,可以适应不同函数参数不相同的情况。

4.3 修饰的函数有返回值
def login_required(func):
    def inner(*args, **kwargs):
        if func.__name__ == 'f1':
            print(func.__name__, ' 权限验证成功')
            func(*args, **kwargs)
        else:
            print(func.__name__, ' 权限验证失败')
    return inner

@login_required
def f1(a, b, c):
    print('function f1, args: a={}, b={}, c={}'.format(a, b, c))
    return 'hello, world'

res = f1(10, 20, 30)
print(res)

>>> f1  权限验证成功
>>> function f1, args: a=10, b=20, c=30
>>> None

输出结果中多了一个None,原因是在调用f1(10, 20, 30)时,实际是调用inner(10, 20, 30),然后执行inner闭包的函数体,在执行到func(* args, ** kwargs)后,没有接收原f1函数体的返回值。当inner闭包执行完毕,Python解释器也没有发现有return语句,就默认返回None

在inner中接收func函数的返回值,然后return返回它,本示例中装饰器的inner执行完func(* args, ** kwargs)后没有其它代码了,所以可以直接修改为return func(* args, ** kwargs),如果还有其它逻辑,则用变量保存func的返回值res = func(* args, ** kwargs),inner最后一行返回return res

def login_required(func):
    def inner(*args, **kwargs):
        if func.__name__ == 'f1':
            print(func.__name__, ' 权限验证成功')
            return func(*args, **kwargs)
        else:
            print(func.__name__, ' 权限验证失败')
    return inner

@login_required
def f1(a, b, c):
    print('function f1, args: a={}, b={}, c={}'.format(a, b, c))
    return 'hello, world'

res = f1(10, 20, 30)
print(res)

>>> f1  权限验证成功
>>> function f1, args: a=10, b=20, c=30
>>> hello, world
4.4 装饰器带参数

假如装饰器带参数,那么把带参数的装饰器看做一个函数,他返回的是一个无参装饰器,即在一个装饰器外面再加上一层函数

def logging(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print('[日志级别 {}]: 被装饰的函数名是 {}'.format(level, func.__name__))
            return func(*args, **kwargs)
        return wrapper
    return decorator

@logging('DEBUG')  # 等价于 f1 = logging('DEBUG')(f1) ,即先执行loggin('DEBUG'),返回decorator引用(真正的装饰器),再用decorator装饰f1,返回wrapper
def f1(a, b, c):
    print('function f1, args: a={}, b={}, c={}'.format(a, b, c))
    return 'hello, world'

@logging('INFO')
def f2():
    print('function f2...')

res = f1(10, 20, 30)
print(res)
f2()

>>> [日志级别 DEBUG]: 被装饰的函数名是 f1
>>> function f1, args: a=10, b=20, c=30
>>> hello, world
>>> [日志级别 INFO]: 被装饰的函数名是 f2
>>> function f2...
4.5 @wraps

Python装饰器(decorator)在实现的时候,被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫wraps的decorator来消除这样的副作用。写一个decorator的时候,最好在实现之前加上functools的wrap,它能保留原有函数的名称和docstring。

  • 不加@wraps
def my_decorator(func):
    def wrapper(*args, **kwargs):
        """这是wrapper的文档"""
        print('calling start')
        func(*args, **kwargs)
        print('calling end')
    return wrapper

@my_decorator   
def exam():
    """这是exam的文档"""
    print('example....')

print(exam.__name__)
print(exam.__doc__)

>>> wrapper
>>> 这是wrapper的文档
  • 加@wraps
from functools import wraps
def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """这是wrapper的文档"""
        print('calling start')
        func(*args, **kwargs)
        print('calling end')
    return wrapper

@my_decorator   
def exam():
    """这是exam的文档"""
    print('example....')

print(exam.__name__)
print(exam.__doc__)

>>> exam
>>> 这是exam的文档
4.6 基于类实现装饰器

实现了call方法,那么类实例化后的对象就是callable,即拥有了被直接调用的能力。装饰器接受一个callable对象作为参数,并返回一个callable对象,那么我们可以让类的构造函数init ()接受一个函数,然后重载call ()并返回一个函数,也可以达到装饰器函数的效果:

class logging(object):
    def __init__(self, func):
        self._func = func

    def __call__(self, *args, **kwargs):
        print('[DEBUG]: 被装饰的函数名是 {}'.format(self._func.__name__))
        return self._func(*args, **kwargs)

@logging
def f1(a, b, c):
    """This is f1 function"""
    print('function f1, args: a={}, b={}, c={}'.format(a, b, c))
    return 'hello, world'

res = f1(10, 20, 30)
print(res)

>>> [DEBUG]: 被装饰的函数名是 f1
>>> function f1, args: a=10, b=20, c=30
>>> hello, world

如果类装饰器带参数,那么就需要在__ call __方法中返回闭包

class logging(object):
    def __init__(self, level='INFO'):
        self._level = level

    def __call__(self, func):  # 接受函数
        def wrapper(*args, **kwargs):
            print('[日志级别 {}]: 被装饰的函数名是 {}'.format(self._level, func.__name__))
            return func(*args, **kwargs)
        return wrapper  # 返回闭包

@logging('DEBUG')
def f1(a, b, c):
    """This is f1 function"""
    print('function f1, args: a={}, b={}, c={}'.format(a, b, c))
    return 'hello, world'

res = f1(10, 20, 30)
print(res)

很多笔记直接抄的课件,但也相当于复习了一边,加深了印象,改天再找些实战练习一下

你可能感兴趣的:(Python学习Day8(下) ---- 闭包与装饰器)