python 里的闭包和装饰器

198 阅读3分钟

Python 闭包与装饰器

作用域

在一个 python 程序中,直接访问一个变量,会从内到外依次访问所有的作用域直到找到,否则会报未定义的错误。

Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python 的作用域一共有4种,分别是:

  • L(Local):最内层,包含局部变量,比如一个函数/方法内部。

  • E(Enclosing):包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,一个函数(或类) A 里面又包含了一个函数 B ,那么对于 B 中的名称来说 A 中的作用域就为nonlocal。 --闭包中最常见

  • G(Global):当前脚本的最外层,比如当前模块的全局变量。

  • B(Built-in): 包含了内建的变量/关键字等,最后被搜索

规则顺序: L –> E –> G –> B。

在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内置中找

a = 1
def fun1():
    global a  #  global 关键字声明
    print(a) 
    a = 123
    print(a)
fun1()
print(a)
def outer():
    a = 10
    def inner():
        nonlocal a   # nonlocal关键字声明
        a = a + 100
        print(a)
    inner()
    print(a)
outer()
a = abs(-1) # abs:Built-in
b = 3       # b:Global
def f1():
    print(b) # b:Global
    a = 3    # a:Local
    print(a)
def f2():
    a = 3         # a:Enclosing
    def f3():
        b = 3     # b:Local
        print(b)

闭包

什么是闭包?

在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用,那么就将这个函数以及用到的一些变量称之为闭包。

def outer():
    a = 10
    def inner():
        b= 10
        print(b)
        print(a)
    return inner

f = outer()
f()
print(f.__code__.co_freevars) # ('a',)
print(f.__code__.co_varnames) # ('b',)

# 在inner中,a是一个自由变量(free variable). 这是一个技术术语,指未在本地作用域绑定的变量

在这里a作为outer的局部变量,一般情况下会在函数结束的时候释放为a分配到的内存。但是在闭包中,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。

经典闭包题目:


a = [lambda x: i*x for i in range(4)]
for item in a:
    print(item(3))

如何修改上面的代码让其打印成 0,3,6,9?

装饰器

Python的装饰器本质上是一个嵌套函数,它接受被装饰的函数(func)作为参数,并返回一个包装过的函数。这样我们可以在不改变被装饰函数的代码的情况下给被装饰函数或程序添加新的功能。常见的装饰器: @classmethod, @staticmethod, @property等

常见装饰器分类:

  • 常规装饰器(无参数装饰器)
  • 有参数装饰器
  • 类装饰器
  • 装饰器链(组合装饰器)
常规装饰器
import time
# from functools import wraps

def print_time(func):
    # @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)
        end = time.time()
        print(f'函数{func.__name__} 的运行时间为 {end-start:.4f}')
        print(f'参数为:{args}, {kwargs}')
    # print(wrapper.__name__)
    return wrapper

@print_time
def func1(*args, **kwargs):
    print('这是func1')


if __name__ == '__main__':
    # print(func1.__name__)
    func1()
带参数的装饰器
def ms_param(yaml_file, api_name, current_f, y_p='params'):
    """装饰器:针对单个参数多个值的参数调用"""

    def _interface_param(func):
        @functools.wraps(func)
        def _p(self, *args, **kwargs):

            self.all_d, self.normal_multi_data = get_api_params(yaml_file, api_name, current_module_path=current_f,
                                                                p_type='ms', y_p=y_p)
            if GenerateParams.contains_custom_param(self.all_d['api_url']):
                v = url_format(self.all_d['api_url'], current_f)
                self.all_d['api_url'] = v
            for param in self.normal_multi_data:
                self.normal_d = param
                yield func(self, *args, **kwargs)

        return _p

    return _interface_param
类装饰器
  • 不带参数:
class deco:

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

    def __call__(self, *args, **kwargs):
        print("start。。。。")
        self.func( *args, **kwargs)
        print("end.....")

@deco
def f1():
    print("这是f1....")

f1()

  • 带参数:
class deco:


    def __init__(self, x, y):
        self.x = x
        self.y = y
        print("####",self.x)
        print("$$$$",self.y)

    def __call__(self, func):
        def _warp(*args, **kwargs):
            print("start01.....")
            func(*args, **kwargs)
            print("end01....")

        return _warp

@deco(1,2)
def f1():
    print("这是f1....")

f1()
组合装饰器
import time


def print_time(func):
    print('print_time.....')

    def wrapper(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)
        time.sleep(1)
        end = time.time()

        print(f'函数 {func.__name__} 运行时间 {end-start:0.2f}')
    return wrapper


def f1(func):
    print('f1 func')

    def _wrapper(*args, **kwargs):
        print(f'函数参数为:{args},{kwargs}')
        return func()
    return _wrapper


@print_time
@f1
def f2(*args, **kwargs):
    print('f2......')


f2(['hello', 'world'], hello=1, world=2)