Python之functools、装饰器应用、实现lru功能缓存


这里是一段防爬虫文本,请读者忽略。
本文原创首发于CSDN,作者IDYS
博客首页:https://blog.csdn.net/weixin_41633902/
本文链接:https://blog.csdn.net/weixin_41633902/article/details/108038071
未经授权,禁止转载!恶意转载,后果自负!尊重原创,远离剽窃!


写在开头的话

  • 请记住:实践是掌握知识的最快方法
  • 如果你只是怀着看看的态度去快速浏览文章,而不去认认真真的把文章里面讲的任何一个知识点去实践一遍,那么你永远也掌握不了它
  • 生命不息,折腾不止!

Python之functools

00. functools模块

  • partial方法

    • 偏函数,把函数部分的参数固定下来,相当于为部分的参数添加了一个固定的默认值,形成一 个新的函数并返回
    • partial生成的新函数,是对原函数的封装
  • 代码演示1

import functools


def add(x, y):
    print("x={}\ty={}".format(x,y))
    return x + y


add1 = functools.partial(add, 1, 2)
print(add1())

add2 = functools.partial(add, y=1)
print(add2(10))

add3 = functools.partial(add, x=1)
print(add3(y=10))

add4 = functools.partial(add, 10)
print(add4(20))

  • 运行结果
x=1	y=2
3
x=10	y=1
11
x=1	y=10
11
x=10	y=20
30

  • 代码演示2
import functools
import inspect


def add(x, y) -> int:
    return x + y


newadd = functools.partial(add, y=5)  # 相当于提前在函数中放了一个参数 y = 5
print(newadd(7))
print(newadd(7, y=6))
print(newadd(y=10, x=6))


print(inspect.signature(newadd))
  • 运行结果
12
13
16
(x, *, y=5) -> int

  • 代码演示3
import functools
import inspect

def add(x, y, *args):
    print(args)
    return x + y


add1 = functools.partial(add, 1, 2, 3, 4, 5, 6, 7)
print(add1(20, 50, 90))

add2 = functools.partial(add, 1)
add2(12, 45, 67, 78)

sig = inspect.signature(add2)
print(sig)
print(sig.parameters)
print(sig.return_annotation)
for key, value in sig.parameters.items():
    print(key, value.annotation)
    print(value.annotation is value.empty)

  • 运行结果
(3, 4, 5, 6, 7, 20, 50, 90)
3
(45, 67, 78)
(y, *args)
OrderedDict([('y', <Parameter "y">), ('args', <Parameter "*args">)])
<class 'inspect._empty'>
y <class 'inspect._empty'>
True
args <class 'inspect._empty'>
True

  • partial函数本质

def partial(func, *args, **keywords):  # partial 源码
    def newfunc(*fargs, **fkeywords): # 包装函数
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(args + fargs), **newkeywords)
    newfunc.func = func # 保留原函数
    newfunc.args = args # 保留原函数的位置参数
    newfunc.keywords = keywords # 保留原函数的关键字参数参数
    return newfunc


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


foo = partial(add, 4)
print(foo(5))
  • 运行结果
9

01. functools模块的 lru_cache 装饰器

  • @functools.lru_cache(maxsize=128, typed=False)
  • Least-recently-used装饰器。lru,最近最少使用。cache缓存
  • 如果maxsize设置为None,则禁用LRU功能,并且缓存可以无限制增长。当maxsize是二的幂 时,LRU功能执行得最好
  • 如果typed设置为True,则不同类型的函数参数将单独缓存。例如,f(3)f(3.0)将被视为具有不同结果的不同调用

  • 演示
import functools
import time


@functools.lru_cache()
def add(x, y):
    time.sleep(3)
    return x + y


if __name__ == "__main__":
    print(add(1, 2))
    print(add(1, 2.0))  # 没有等待
    print(add(1, y=2))
    print(add(y=2, x=1))
    print(add(x=1, y=2))
    print(add(1, 2))  # 没有等待

  • 运行结果
3
3
3
3
3
3

  • 项目演示2
dict_test = dict(x=123, y=234, z=890)
dict_test1 = dict(y=234, x=123, z=890)
print(dict_test)

sorted_item = sorted(dict_test.items())
sorted_item1 = sorted(dict_test1.items())

print(sorted_item)
print(sorted_item1)
test_zero = tuple()
print(test_zero)
for item in dict_test.items():
    test_zero += item
print(test_zero)

  • 运行结果
{'x': 123, 'y': 234, 'z': 890}
[('x', 123), ('y', 234), ('z', 890)]
[('x', 123), ('y', 234), ('z', 890)]
()
('x', 123, 'y', 234, 'z', 890)

  • lru_cache装饰器通过一个字典缓存被装饰函数的调用和返回值

  • 斐波那契数列递归方法的改造

import functools


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


if __name__ == "__main__":
    print(fbin(100))
  • lru_cache装饰器应用
    • 使用前提
      • 同样的函数参数一定得到同样的结果
      • 函数执行时间很长,且要多次执行
    • 本质是函数调用的参数=>返回值
    • 缺点
      • 不支持缓存过期,key无法过期、失效
      • 不支持清除操作
      • 不支持分布式,是一个单机的缓存
    • 适用场景,单机上需要空间换时间的地方,可以用缓存来将计算变成快速的查询

02. 利用装饰器写一个 lru(least-recently-used)的缓存系统

  • 要求:
    • 写一个缓存系统
    • 对缓存过的函数的值不用继续求值,直接得出结果
    • 对缓存中的数,设置超时时间,如果缓存中某个数的时间超过了设定的时间,那么则在缓存中清除这个数
    • 利用装饰器来实现这个功能

  • 代码实现
import inspect
import functools
import time
import datetime


def logger(duration):  # 装饰器传入的时间
    def _logger(fn):
        local_cache = {}   # 设置缓存

        @functools.wraps(fn)  # 属性复制,保持函数原有的属性
        def wrapper(*args, **kwargs):
            def expired_clear(my_local_cache):  # 缓存过期清除
                dict_remove = list()
                for cache_key, cache_value in my_local_cache.items():
                    if datetime.datetime.now().timestamp() - cache_value[1] > duration:
                        dict_remove.append(cache_key)
                for dict_k in dict_remove:
                    my_local_cache.pop(dict_k)
                print(my_local_cache)  # 主要是为了测试而添加,实际应该删除这行代码。但是为了直观的看到缓存过期清理功能。所以添加这一行
            expired_clear(local_cache)

            def make_key(key_fn):  # 设置函数表示符,达到命中
                sig = inspect.signature(key_fn)
                sig_par = sig.parameters
                sig_par_key_tuple = tuple(sig_par.keys())
                compare_dict = dict()
                for index, a_value in enumerate(args):
                    compare_dict[sig_par_key_tuple[index]] = a_value
                compare_dict.update(kwargs)
                for k in sig_par.keys():
                    if k not in compare_dict.keys():
                        compare_dict[k] = sig_par[k].default
                dict_key = tuple(sorted(compare_dict.items()))
                return dict_key
            key = make_key(fn)
            if key not in local_cache:   # 看传入的函数,是否已经存在于缓存当中,如果存在。那么就直接得出值。不存在则计算存入缓存当中
                ret = fn(*args, **kwargs)
                local_cache[key] = (ret, datetime.datetime.now().timestamp())
            else:
                local_cache[key] = (local_cache[key][0], datetime.datetime.now().timestamp())
            return local_cache[key][0]  # 返回函数的值
        return wrapper
    return _logger


@logger(6)   # 装饰器修饰
def add(x=11, y=10):
    time.sleep(3)
    return x + y


print(add())
print(add(11))
print(add(11, 10))
print(add(11, y=10))
print(add(y=10, x=11))
print(add(x=11, y=10))
print(add(34))
print(add(45))
print(add(12))

  • 运行结果
{}
21
{(('x', 11), ('y', 10)): (21, 1597545428.264142)}
21
{(('x', 11), ('y', 10)): (21, 1597545428.264142)}
21
{(('x', 11), ('y', 10)): (21, 1597545428.264142)}
21
{(('x', 11), ('y', 10)): (21, 1597545428.264142)}
21
{(('x', 11), ('y', 10)): (21, 1597545428.264142)}
21
{(('x', 11), ('y', 10)): (21, 1597545428.264142)}
44
{(('x', 11), ('y', 10)): (21, 1597545428.264142), (('x', 34), ('y', 10)): (44, 1597545431.264453)}
55
{(('x', 34), ('y', 10)): (44, 1597545431.264453), (('x', 45), ('y', 10)): (55, 1597545434.264718)}
22


03. 写一个命令分发器

  • 将某个函数注册到对应的某一个命令
  • 如果命令对应的函数重复,则报错
  • 提供注销命令的功能,如果注销对应命名没有则报错
  • 如果输入没有注册的命令,那么则提示该命令没有找到

  • 代码演示

def reg_dispatcher():
    command = {}

    def reg(name):
        def _reg(fn):
            if name in command.keys():
                error = "{} is repeated ".format(name)
                raise Exception(error)
            else:
                command.setdefault(name, fn)
            return fn
        return _reg

    def default():
        print("your command is not founded")

    def dispatcher():
        while True:
            cmd = input(">>")
            if cmd.strip() == "quit":
                break
            command.get(cmd.strip(), default)()

    def cancel(name):
        if name not in command.keys():
            error = "the command name is not founded "
            raise Exception(error)
        else:
            command.pop(name)
    return reg, dispatcher, cancel


reg, dispatcher, cancel = reg_dispatcher()


@reg("welcome")  # pywel = reg("welcome")(pywel)
def py_wel():
    print("welcome to python")


@reg("go")
def go_prin():
    print("go out")


cancel("go")

print(py_wel.__doc__)
dispatcher()

  • 运行结果
>>welcome
welcome to python
>>go
your command is not founded
>>


04. 装饰器的用途

  • 装饰器是AOP面向切面编程Aspect Oriented Programming的思想体现
  • 面向对象往往需要通过继承或者组合依赖等方式调用一些功能,这些功能的代码往往可能在多个类中出现,例如logger。这样会造成代码的重复,增加了耦合。logger的改变影响所有使用它的类或方法
  • AOP在需要的类或者方法上切下,前后的切入点可以加入增强的功能,让调用者和被调用者解耦
  • 这是一种不修改原来的业务代码,给程序动态添加功能的的技术,例如logger函数功能就是对业务函数增加日志的,而业务函数中应该把与业务无关的日志功能剥离干净

05. 装饰器应用场景

  • 日志、监控、权限、设计、参数检查、路由等处理
  • 这些功能与业务功能无关,很多业务都需要的公共功能,所以独立出来,需要的时候对目标对象增强。

写在最后的话:

  • 无论每个知识点的难易程度如何,我都会尽力将它描绘得足够细致
  • 欢迎关注我的CSDN博客,IDYS’BLOG
  • 持续更新内容
    linux基础 | 数据通信(路由交换,WLAN) | Python基础 | 云计算
  • 如果你有什么疑问,或者是难题。欢迎评论或者私信我。你若留言,我必回复!
  • 虽然我现在还很渺小,但我会做好每一篇内容。谢谢关注!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值