python高级之闭包与装饰器

1.魔法方法__call__()

让类的实例化对象可以像函数一样被调用

class Fib(object):
    def __init__(self):
        pass

    def __call__(self, num):
        a,b = 0,1
        self.lst = []

        for i in range(num):
            self.lst.append(a)
            a,b = b,a+b

        return self.lst

    def __str__(self):
        return str(self.lst)

f = Fib()
f(10)
print(f)

2.闭包

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

In [1]: def line(a, b):
   ...:     def get_y_axis(x):
   ...:         return a * x + b  # 内部函数使用了外部函数的变量a和b
   ...:     return get_y_axis     # 返回值是闭包函数名,注意不是函数调用没有小括号
   ...: 
   ...: 

In [2]: L1 = line(1, 1)  # 创建一条直线: y=x+1

In [3]: L1
Out[3]: .get_y_axis(x)>

In [4]: L1(3)  # 获取第一条直线中,横坐标是3时,纵坐标的值
Out[4]: 4

In [5]: L2 = line(2, 3)  # 创建一条直线: y=2x+3

In [6]: L2
Out[6]: .get_y_axis(x)>

In [7]: L2(3)  # 获取第二条直线中,横坐标是3时,纵坐标的值
Out[7]: 9

In [8]: L3 = line(2, 3)  # 再创建一条直线: y=2x+3

In [9]: L3
Out[9]: .get_y_axis(x)>

In [10]: L2 == L3  # 每次调用line()返回的都是不同的函数,即使传入相同的参数
Out[10]: False

In [11]: L2 is L3
Out[11]: False

注意: 闭包中不要引用外部函数中任何循环变量或后续会发生变化的变量

# 1. 错误的用法
def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()

# f1()、f2()和f3()的输出结果都是9,原因是调用这三个函数时,闭包中引用的外部函数中变量i的值已经变成3

# 2. 正确的用法
def count():
    def f(j):
        return lambda: j * j

    fs = []
    for i in range(1, 4):
        fs.append(f(i))  # f(i)立刻被执行,因此i的当前值被传入闭包lambda: j * j
    return fs


f1, f2, f3 = count()
print(f1())  # 输出1
print(f2())  # 输出4
print(f3())  # 输出9

3.装饰器

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

3.1被装饰的函数无参数

没有使用装饰器:

def f1():
    print('function f1...')

def f2():
    print('function f1...')

f1()  # 输出function f1...
f2()  # 输出function f2...

使用装饰器:

def login_required(func):
    def inner():  # inner是一个闭包,它使用了外部函数的变量func,即传入的原函数引用f1、f2...
        if func.__name__ == 'f1':  # 这里是权限验证的逻辑判断,此处简化为只能调用f1
            print(func.__name__, ' 权限验证成功')
            func()  # 执行原函数,相当于f1()或f2()...
        else:
            print(func.__name__, ' 权限验证失败')
    return inner

def f1():
    print('function f1...')

def f2():
    print('function f1...')

f1 = login_required(f1)  # 将f1引用传入装饰器,此时func指向了原f1函数体。返回inner引用,并赋值给f1,即现在是func指向原函数体,而f1重新指向了返回的inner闭包
f1()  # 执行函数,即执行inner(),这个闭包中使用的func变量指向原f1函数体

f2 = login_required(f2)  # 将f2引用传入装饰器,此时func指向了原f2函数体。返回inner引用,并赋值给f2,即现在是func指向原函数体,而f1重新指向了返回的inner闭包
f2()  # 执行函数,即执行inner(),func变量指向原f2,所以它不会通过权限验证,即不会执行func()

# 输出结果:
f1  权限验证成功
function f1...
f2  权限验证失败

可以用@Python语法糖简写为:

# 1. 定义时
@login_required
def f1():
    print('function f1...')

# 2. 调用时
f1()

3.2被装饰的函数有参数

def print_name(func):
    def inner(*args, **kwargs):
        print(func.__name__)  # 是对原有函数的加强
        # result = func(*args,**kwargs) #保持原有函数功能不变
        # return result
        func(*args, **kwargs)
    return inner


@print_name
def admin():
    print('function admin...')

@print_name
def f2():
    print('function f2...')

@print_name
def f1(a, b, c='dd'):
    print('function f1, args: ', a, b, c)
   

result = f1('hello', 3)
print(result)

#输出
f1
function f1, args:  hello 3 dd

3.3被装饰的函数有返回值

def print_name(func):
    def inner(*args, **kwargs):
        print(func.__name__)  # 是对原有函数的加强
        # result = func(*args,**kwargs) #保持原有函数功能不变
        # return result
        return func(*args, **kwargs)  # 被装饰函数的返回
    return inner


@print_name
def admin():
    print('function admin...')

@print_name
def f2():
    print('function f2...')

@print_name
def f1(a, b, c='dd'):
    print('function f1, args: ', a, b, c)
    return 'hello, world'


result = f1('hello', 3)
print(result)

#输出
f1
function f1, args:  hello 3 dd
hello, world

3.4装饰器带参数

import logging

# logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
from functools import wraps


def mylogging(level):
    if level == 'warning':
        logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.WARNING)
    elif level == 'info':
        logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
    else:
        pass

    def log(func):    
        @wraps(func)           
        def inner(x):   # @wraps是为了输出原函数的签名
            logging.debug('This is debug message'+func.__name__)
            logging.info('This is info message'+func.__name__)
            logging.warning('This is warning message'+func.__name__)
            logging.error('This is error message'+func.__name__)
            logging.critical('This is critical message'+func.__name__)
            func(x)
        return inner
    return log


@mylogging('warning')
def f1(x):
    print('f1 is called args:' + str(x))

f1(123)
print(f1.__name__)

# 输出
2019-12-01 16:18:18,981 - root - WARNING - This is warning messagef1
2019-12-01 16:18:18,982 - root - ERROR - This is error messagef1
2019-12-01 16:18:18,982 - root - CRITICAL - This is critical messagef1
f1 is called args:123
f1

3.5多个装饰器装饰同一个函数

def make_b(func):
    print('这个是加粗装饰器')
    def inner():
        result = func()
        return '' + result + ''
    return inner

def make_i(func):
    print('这个是变斜装饰器')
    def inner():
        result = func()
        return '' + result + ''
    return inner


@make_i
@make_b
def foo():
    return 'hello'

res = foo()
print(res)

# 输出
这个是加粗装饰器
这个是变斜装饰器
hello

3.6基于类实现的装饰器

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

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

@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)

# 输出
[日志级别 DEBUG]: 被装饰的函数名是 f1
function f1, args: a=10, b=20, c=30
hello, world

你可能感兴趣的:(python高级之闭包与装饰器)