函数装饰器和闭包

本文深入探讨了Python装饰器的基础知识,包括装饰器的工作原理、如何使用它们来增强函数的功能,以及装饰器在实际开发中的应用案例。此外,还详细讲解了闭包的概念、自由变量的作用,并展示了如何利用闭包实现函数状态的记忆。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

装饰器基础

  • 装饰器是可调用对象,其参数是另一个函数。装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。
def deco(func):
    def inner():
        print('running inner()')
    return inner

@deco
def target():
    print('running target()')

# 调用的是inner函数
target()
  • 装饰器在被函数定义之后立即运行,通常是在导入装饰器模块的时候。被装饰的函数则是在被调用时运行。

变量作用域

  • 全局变量
def f1(a):
    print(a)
    print(b)
#这里将会报NameError错误,由于没有定义b
f1(3)

# 在调用f1前定义b,则不会报错
b = 6
f1(3)

# 下面的产生了赋值b的语句,所以b将变成局部变量,print(b)没有值,将报错
b = 6
def f2(a):
    print(a)
    print(b)
    b = 9

f2(3)

# 让上面的f2 b变成全局变量,用global声明
b = 6
def f2(a):
    global b
    print(a)
    print(b)
    b = 9

f3(3)
  • 闭包:指延伸了作用域的函数,它能访问定义体之外定义的非全局变量
def make_averager():
    series = []
    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)

avg = make_averager()
avg(10)
avg(4)

这里averager函数中的series是自由变量(指未在本地作用域中绑定的变量),averager的闭包延伸到那个函数的作用域之外,包含自由变量series的绑定。

# __code__属性中保存局部变量和自由变量的名称
avg.__code__.co_varnames
avg.__code__.co_freevars

# series在__closure__属性中
avg.__closure__
# cell_contents中保存着真正的值
avg.__closure__[0].cell_contents

闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。

  • nonlocal:声明自由变量
# 这里和上面的f2一样,count += 1 赋值了,将count变成了局部变量,运行将会报错
def make_averager():
    count = 0
    total = 0
    def averager(new_value):
        count += 1
        total +=new_value
        return total / count
    return averager

# 这里可以使用nonlocal将count、total声明为自由变量
def make_averager():
    count = 0
    total = 0
    def averager(new_value):
        nonlocal count, total
        count += 1
        total +=new_value
        return total / count
    return averager

装饰器

# 函数运行时间
import time

def clock(func):
    
    def clocked(*args):
        t0 = time.perf_counter()
        # 函数开始执行
        result = func(*args) # 自由变量func, 相当于执行被修饰的函数
        # 函数执行结束
        elapsed = time.perf_counter() - t0 
        name = func.__name__
        arg_str = ', '.join(repr(arg) for arg in args)
        print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
    return clocked # 返回取代被装饰的函数

@clock
def snooze(seconds):
    time.sleep(seconds)
    
@clock
def factorial(n):
    return 1 if n < 2 else n * factorial(n-1)

snooze(.123)
factorial(6)
  • functools.wraps装饰器:把相关的属性从func复制到clocked中
import time 
import functools

def clock(func):
    @functools.wraps(func)
    def clocked(*args, **kwargs):
        t0 = time.time()
        
        result = func(*args, **kwargs)
        
        elapsed = time.time() - t0
        name = func.__name__
        arg_lst = []
        if args:
            arg_lst.append(', '.join(repr(arg) for arg in args))
        # 处理关键字参数
        if kwargs:
            pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
        arg_str = ', '.join(arg_lst)
        print('[%0.8fs] %s(%s) -> %r ' % (elapsed, name, arg_str, result))
        return result
    return clocked
  • functools.lru_cache:实现缓存优化
import functools

@functools.lru_cache()
@clock
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 2) + fibonacci(n - 1)

fibonacci(6)
  • functools.singledispatch:把普通函数变成泛函数,根据第一个参数的类型,以不同方式执行相同的操作
from functools import singledispatch
from collections import abc
import numbers
import html

@singledispatch
def htmlize(obj):
    content = html.escape(repr(obj))
    return '<pre>{}</pre>'.format(content)

@htmlize.register(str)
def _(text):
    content = html.escape(text).replace('\n', '<br>\n')
    return '<p>{0}</p>'.format(content)

@htmlize.register(numbers.Integral)
def _(n):
    return '<pre>{0} (0x{0:x})</pre>'.format(n)

@htmlize.register(tuple)
@htmlize.register(abc.MutableSequence)
def _(seq):
    inner = '</li>\n<li>'.join(htmlize(item) for item in seq)
    return '<ul>\n<li>' + inner + '</li>\n</ul>'
  • 参数化装饰器:通过装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上
#
registry = set()
def register(active=True):
    def decorate(func):
        print('running register(active=%s)->decorate(%s)' % (active, func))
        if active:
            registry.add(func)
        else:
            registry.discard(func)
        return func
    return decorate

@register(active=False)
def f1():
    print('running f1()')
    
@register()
def f2():
    print('running f2()')
    
def f3():
    print('running f3()')

registry
registry()(f3)
registry(active=False)(f2)
registry              
  • 参数化clock装饰器
import time

DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'

def clock(fmt=DEFAULT_FMT):
    def decorate(func):
        def clocked(*_args):
            t0 = time.time()
            
            _result = func(*_args)
            
            elapsed = time.time() - t0
            name = func.__name__
            args = ', '.join(repr(arg) for arg in _args)
            result = repr(_result)
            print(fmt.format(**locals()))
            return _result
        return clocked
    return docorate

@clock()
def snooze(seconds):
    time.sleep(seconds)
for i in range(3):
    snooze(.123)
    
@clock('{name}: {elapsed}s')
def snooze(seconds):
    time.sleep(seconds)
for i in range(3):
    snooze(.123)

@clock('{name}({args}) dt={elapsed:0.3f}s')
def snooze(seconds):
    time.sleep(seconds)
for i in range(3):
    snooze(.123)

总结

  • 进一步了解了装饰器的概念和基础知识,其实就是语法糖,可以简单理解为给被装饰的函数添加功能,如Flask中登录视图就用到装饰器,通过装饰器判断用户session是否登录。
  • 闭包的概念,闭包与匿名函数,还有自由变量
  • 参数化装饰器,通过调用装饰器工厂函数返回装饰器,再应用到要装饰的函数上,实现参数化装饰器

流畅的python2015

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值