8.python-闭包和装饰器

闭包和装饰器

闭包

形式和作用

闭包是指函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。

闭包的使用,可以隐藏内部函数的工作细节,只给外部使用者提供一个可以执行的内部函数的引用。

1.外函数的内部定义了一个内函数。

2.内函数使用了外函数的临时变量。

3.外函数的返回值是内函数的引用。

# 外部函数
def func_out(num1):
	
    # 内部函数
    def func_in(num2):
        
        # 内部函数使用外部的函数变量
        num=num1+num2
        print(num)
        return  num
    # 返回内部函数的地址
    return  func_in

func=func_out(10)

res1=func(20)
res2=func(30)

# 30
# 40

判断闭包,__closure__内置属性

def func():
    name = 'python'
    def inner():
        print(name)
    print(inner.__closure__) 
    # (<cell at 0x0000027C14EB85E8: str object at 0x0000027C14F54960>,)
    return inner

f = func()
f()

# (<cell at 0x000002A2455DC820: str object at 0x000002A25DFD03F0>,)
# python
 

闭包内修改外部变量

闭包的作用——保存函数的状态信息

def Maker(step):  # 包装器
    num = 1

    def fun1():  # 内部函数
        nonlocal num
        # 而是外部嵌套函数内的变量。

        num = num + step  # 改变外部变量的值
        print(num)

    return fun1


# =====================================#
j = 1
func2 = Maker(3)  # 调用外部包装器
while (j < 5):
    func2() 
    # 调用内部函数4次 输出的结果是 4、7、10、13
    j += 1


通常来讲,闭包的内部变量对于外界来讲是完全隐藏的。但是,你可以通过编写访问函数并将其作为函数属性绑定到闭包上来实现这个目的。

# 来至cookbook-p227

def sample():
    n = 0

    # Closure function
    def func():
        print('n=', n)
        # Accessor methods for n

    def get_n():
        return n

    def set_n(value):
        nonlocal n
        n = value
        # Attach as function attributes

    func.get_n = get_n
    func.set_n = set_n

    return func


f = sample()
f()

f.set_n(10)
print(f.get_n())
f()

装饰器

装饰器本身就是一个函数

作用和功能

  • 引入日志
  • 统计计算函数执行时间
  • 执行函数前的预处理工作
  • 执行函数后清理工作
  • 权限校验设置
  • 缓存

基础用法-时间计时器

# 这是装饰函数
from functools import wraps

def timer(func):
    @wraps
    def wrapper(*args, **kw):
        t1=time.time()
        func(*args, **kw)
        t2=time.time()

        # 计算函数执行时长
        cost_time = t2-t1 
        print("花费时间:{}秒".format(cost_time))
    return wrapper

@timeer

def add(a,b)
	return a,b

add(1,2)
# 花费时间:0秒

解除装饰器

functools.wraps

使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、name、参数列表

通过@wraps__wrapped__ 可以解除装饰器

注意:多个装饰器接触时,使用 @wraps__wrapped__ 可能会报错。

from functools import wraps


def decorator(func):
    @wraps(func)  # 保存函数的元信息
    def wrapper(*args, **kwargs):
        print('Decorator 1')
        return func(*args, **kwargs)

    return wrapper

@decorator
def add(x, y):
    return x + y

res=add(3,4)
print(res)
#Decorator 1
#7
res = add.__wrapped__(2,4)  
print(res)
#6py

不带参数装饰器

# from cookbook-p302
import time
from functools import wraps

def timethis(func):
    @wraps(func)   # 保存函数的元信息
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end - start)
        return result

    return wrapper


@timethis
def countdown(n):
    sumn = 0
    while n > 0:
        n -= 1
        sumn += n
    return sumn

sumn=countdown(100000)
print(sumn)
# countdown 0.009002208709716797
# 4999950000

带可选参数的装饰器

写法1、三层结构

def lggerd(msg=None):
    def dector(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            func(*args, **kwargs)
            print("调用函数", func.__name__)
            if msg:
                print(msg)

        return wrapper

    return dector


@lggerd(msg='info')
def add0(a,b):
    print(a+b)
add0(1,2)
# 调用函数 add0
# 3

@lggerd()
def add0(a,b):
    print(a+b)
add0(1,2)

# 调用函数 add0
# 3

这种结构的写法在使用装饰器时必须带括号 lggerd()

写法2 partial修饰结构

def logged(func=None, *, level=logging.DEBUG, name=None, message=None):
    if func is None:
        return partial(logged, level=level, name=name, message=message)
    logname = name if name else func.__module__
    log = logging.getLogger(logname)
    logmsg = message if message else func.__name__

    @wraps(func)
    def wrapper(*args, **kwargs):
        print("调用 wrapper")
        log.log(level, logmsg)
        return func(*args, **kwargs)

    return wrapper


# Example use
@logged
def add(x, y):
    return x + y


# print(add(1, 2))


@logged(level=logging.CRITICAL, name='example')
def spamfun():
    print('Spam!')
    return 1

初始调用 logged() 函数时,被包装函数并没有传递进来。因此在装饰器内,它必须是可选的。这个反过来会迫使其他参数必须使用关键字来指定。并且,但这些参数被传递进来后,装饰器要返回一个接受一个函数参数并包装它的函数。为了这样做,我们使用了一个技巧,就是利用 functools.partial 。它会返回一个未完全初始化的自身,除了被包装函数外其他参数都已经确定下来了。

设置类方法为装饰器

from functools import wraps
class A:
    # Decorator as an instance method
    def decorator1(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print('Decorator 1')
            return func(*args, **kwargs)
        return wrapper
# Decorator as a class method
    @classmethod
    def decorator2(cls, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print('Decorator 2')
            return func(*args, **kwargs)
        return wrapper
    
    

a = A()
@a.decorator1
def spam():
    pass
# As a class method
@A.decorator2
def grok():
    pass

类装饰器

基于类装饰器的实现,必须实现 __call____init__两个内置函数。

  • __init__ :接收被装饰函数 __call__ :实现装饰逻辑

不带参数的类装饰器

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

    def __call__(self, *args, **kwargs):
        print("[INFO]: the function {func}() is running..."\
            .format(func=self.func.__name__))
        return self.func(*args, **kwargs)

@Logger
def say(something):
    print("say {}!".format(something))

say("hello")

# [INFO]: the function say() is running...
# say hello!

带参数的类装饰器

带参数和不带参数的类装饰器有很大的不同。

__init__ :不再接收被装饰函数,而是接收传入参数。

__call__ :接收被装饰函数,实现装饰逻辑。

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

    def __call__(self, func): # 接受函数
        def wrapper(*args, **kwargs):
            print("[{level}]: the function {func}() is running..."\
                .format(level=self.level, func=func.__name__))
            func(*args, **kwargs)
        return wrapper  #返回函数

@logger(level='WARNING')
def say(something):
    print("say {}!".format(something))

say("hello")

使用偏函数与类实现装饰器

实现了 __call__ 的类,delay 返回一个偏函数,在这里 delay 就可以做为一个装饰器

import time
import functools

class DelayFunc:
    def __init__(self,  duration, func):
        self.duration = duration
        self.func = func

    def __call__(self, *args, **kwargs):
        print(f'Wait for {self.duration} seconds...')
        time.sleep(self.duration)
        return self.func(*args, **kwargs)

    def eager_call(self, *args, **kwargs):
        print('Call without delay')
        return self.func(*args, **kwargs)

def delay(duration):
    """
    装饰器:推迟某个函数的执行。
    同时提供 .eager_call 方法立即执行
    """
    # 此处为了避免定义额外函数,
    # 直接使用 functools.partial 帮助构造 DelayFunc 实例
    return functools.partial(DelayFunc, duration)


add(3,5)  # 直接调用实例,进入 __call__
# Wait for 2 seconds...
# 8

参考文献:

https://www.cnblogs.com/wj-1314/p/8538716.html

posted @ 2021-11-20 21:59  贝壳里的星海  阅读(44)  评论(0编辑  收藏  举报