Python装饰器及functools模块的使用

Python装饰器

装饰器

  • 需求

    • 一个加法函数,想增加它的功能,能够输出被调用过以及调用的参数信息

      • def add(x, y):
        	return x + y
        增加信息输出功能
        def add(x, y):
        	print("call add, x + y")  # 日志输出到控制台
        	return x + y
        
      • 上面的加法函数是完成了需求,但是有 以下的缺点

        • 打印是一个功能,这条语句和add函数耦合太高
        • 加法函数属于业务功能,而输出信息的功能,属于非业务功能代码,不该在业务函数add中
    • 下面代码做到了业务功能分离,但是fn函数调用传参是个问题

      • def add(x, y):
        	return x + y
        	
        def logger(fn):
        	print('begin')  # 增强的输出
        	x = fn(4, 5)  # 参数写死了
        	print('end')  # 增强的功能
        	return x
        	
        print(logger(add))
        
    • 解决了传参的问题,进一步改变

      • def add(x, y):
        	return x + y
        	
        def logger(fn, *args, **kwargs):
        	print('begin')
        	x = fn(*args, **kwargs)
        	print('end')
        	return x
        	
        print(logger(add, 5, y=60))
        
    • 柯里化

      • def add(x, y):
        	return x + y
        
        def logger(fn):
        	def wrapper(*args, **kwargs):
        		print('begin')
        		x = fn(*args, **kwargs)
        		print('end')
        		return x
        	return wrapper
        	
        print(logger(add)(5, y=50))
        
    • 装饰器语法糖

      • def logger(fn):
        	def wrapper(*args, **kwargs):
        		print('begin')
        		x = fn(*args, **kwargs)
        		print('end')
        		return x
        	return wrapper
        	
        @logger  # 等价于add = logger(add)
        def add(x, y):
            return x + y
        print(add(45, 40))
        --->
        begin
        end
        85
        
    • 装饰器(无参)

      • 它是一个函数

      • 函数作为它的形参,无参装饰器实际上就是一个单形参函数

      • 返回值也是一个函数

      • 可以使用@functionname方式,简化调用

        注:此处装饰器的定义只是目前所学的总结,并不准确,只是方便理解

    • 装饰器和高阶函数

      • 装饰器可以是高阶函数,但装饰器是对传入函数的功能的装饰(功能增强)
    • import datetime
      import time
      
      def logger(fn):
      	def wrapper(*args, **kwargs):
      		# before功能增强
      		print('args={}, kwargs={}'.format(args, kwargs))
      		start = datetime.datetime.now() # 计算时长
      		ret = fn(*args, **kwargs)
      		# after功能增强
      		duration = datetime.datetime.now() - start
      		print('function{}took{}s.'.format(fn.__name__,duration.total_seconds()))
      		return ret
      	return wrapper
      	
      @logger  # 相当于add = logger(add)
      def add(x, y):
      	print('===call add=============')
      	time.sleep(2)
      	return x + y
      	
      print(add(4, y=7))
      --->
      args=(4,), kwargs={'y': 7}
      ===call add=============
      functionaddtook2.000828s.
      11
      

文档字符串

  • Python的文档

    • Python文档字符串Documentation Strings

    • 在函数语句块的第一行,且习惯是多行文本,所以多使用三引号

    • 文档字符串也算是合法的一条语句

    • 惯例是首字母大写,第一行写概述,空一行,第三行写详细描述

    • 可以使用特殊属性____doc____访问这个文档

    • def add(x, y):
      	"""This is a function of addition"""
          return x + y
      
      print('name={}\ndoc={}'.format(add.__name__,add.__doc__))
      print(help(add))
      --->
      name=add
      doc=This is a function of addition
      Help on function add in module __main__:
      
      add(x, y)
          This is a function of addition
      
      None
      

装饰器

  • 副作用

    • def logger(fn):
      	def wrapper(*args, **kwargs):
      		'i am wrapper'
      		print('begin')
      		x = fn(*args, **kwargs)
      		print('end')
      		return x
      	return wrapper
      	
      @logger  # add = logger(add)
      def add(x, y):
      	"""This is a function for add"""
          return x + y
      
      print('name={}, doc={}'.format(add.__name__, add.__doc__))
      --->
      name=wrapper, doc=i am wrapper
      

      原函数对象的属性都被替换了,而使用装饰器,我们的需求是查看被封装函数的属性,如何解决?

  • 提供一个函数,被封装函数属性 copy> 包装函数属性

    • def copy_properties(src, dst): # 可以改造成装饰器
      	dst.__name__ = src.__name__
      	dst.__doc__ = src.__doc__
      	
      def logger(fn):
      	def wrapper(*args, **kwargs):
      		'i am wrapper'
      		print('begin')
      		x = fn(*args, **kwargs)
      		print('end')
      		return x
      	copy_properties(fn, wrapper)
      	return wrapper
      
      @logger # add = logger(add)
      def add(x, y):
      	"""This is a function for add"""
      	return x + y
      	
      print('name={}, doc={}'.format(add.__name__,add.__doc__))
      --->
      name=add, doc=This is a function for add
      
  • 通过copy_properties函数将被被包装函数的属性覆盖掉包装函数

  • 凡是被装饰的函数都需要赋值这些属性,这个函数很通用

  • 可以将复制属性的函数构建成装饰器函数,带参数装饰

  • 提供一个函数,被封装函数属性 copy> 包装函数属性,改造成带参装饰器

    • def copy_properties(src):
      	def _copy(dst):
      		dst.__name__ = src.__name__
      		dst.__doc__ = src.__doc__
      		return dst
      	return _copy
      	
      def logger(fn):
      	@copy_properties(fn) # wrapper = copy_properties(fn)(wrapper)
      	def wrapper(*args, **kwargs):
      		'I am wrapper'
      		print('begin')
      		x = fn(*args, **kwargs)
      		print('end')
      		return x
      	return wrapper
      
      @logger  # add = logger(add)
      def add(x, y):
      	"""This is a function for add"""
      	return x + y
      print('name={}, doc={}'.format(add.__name__, add.__doc__))
      --->
      name=add, doc=This is a function for add
      
  • 需求:获取函数的执行时长,对时长超过阈值的函数记录一下

    • def logger(duration):
      	def _logger(fn):
      		@copy_properties(fn) # wrapper = wrapper(fn)(wrapper)
      		def wrapper(*args, **kwargs):
      			start = datetime.datetime.now()
      			ret = fn(*args, **kwargs)
      			delta = (datetime.datetime.now() - start).total_seconds()
      			print('so slow') if delta > duration else print('so fast')
      			return ret
      		return wrapper
      	return _logger
      	
      @logger(5) # add = logger(5)(add)
      def add(x, y):
      	time.sleep(3)
      	return x + y
      print(add(5, 6))
      --->
      so fast
      11
      
  • 带参装饰器

    • 它是一个函数
    • 函数作为它的形参
    • 返回值是一个不带参的装饰器函数
    • 使用@functionname(参数列表)方式调用
    • 可以看做在装饰器外层又加了一层函数,这个函数可以多参数
  • 将记录的功能提取出来,这样就可以通过外部提供的函数来灵活的控制输出

    • def logger(duration, func=lambda name, delta:print('{}took{:.2f}s'.format(name, delta))):
      	def _logger(fn):
      		@copy_properties(fn) # wrapper = wrapper(fn)(wrapper)
      		def wrapper(*args, **kwargs):
      			start = datetime.datetime.now()
      			ret = fn(*args, **kwargs)
      			delta = (datetime.datetime.now() - start).total_seconds()
      			if delta > duration:
      				func(fn.__name__, delta)
      			return ret
      		return wrapper
      	return _logger
      

functools模块

  • functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, update=WRAPPER_UPDATES)

    • 类似copy_properties功能

    • wrapper包装函数、被更新者,wrapped被包装函数、数据源

    • 元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性

      module’,‘name’,‘qualname’,’_doc’,‘annotations’ 模块名、名称、限定名、文档、参数注解

    • 元组WRAPPER_UPDATES中是要被更新的属性,____dict____属性字典

    • 增加一个____wrapped____属性,保留着wrapped函数

    • import datetime, time, functools
      
      def logger(duration, func=lambda name, delta:print('{}took{:.2f}s'.format(name, delta))):
      	def _logger(fn):
      		def wrapper(*args, **kwargs):
      			start = datetime.datetime.now()
      			ret = fn(*args, **kwargs)
      			delta = (datetime.datetime.now() - start).total_seconds()
      			if delta > duration:
      				func(fn.__name__,duration)
      			return ret
      		return functools.update_wrapper(wrapper, fn)
      	return _logger
      
      @logger(5) # add = logger(5)(add)
      def add(x, y):
      	time.sleep(1)
      	return x + y
      	
      print(add(5, 6), add.__name__, add.__wrapped__,add.__dict__,sep='\n')
      --->
      11
      add
      <function add at 0x00000230B88369D8>
      {'__wrapped__': <function add at 0x00000230B88369D8>}			
      
    • @functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS,update=WRAPPER_UPDATES)

      • 类似copy_properties功能

      • wrapped被包装函数

      • 元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性

        module’,‘name’,‘qualname’,’_doc’,‘annotations’ 模块名、名称、限定名、文档、参数注解

      • 元组WRAPPER_ASSIGNMENTS中是要被更新的属性,____dict____属性字典

      • 增加一个____wrapped____属性,保留着wrapped函数

    • import datetime, time, functools
      
      def logger(duration, func=lambda name,duration:print('{}took{}s'.format(name, duration))):
      	def _logger(fn):
      		@functools.wraps(fn)
      		def wrapper(*args, **kwargs):
      			start = datetime.datetime.now()
      			ret = fn(*args, **kwargs)
      			delta = (datetime.datetime.now() - start).total_seconds()
      			if delta > duration:
      				func(fn.__name__, duration)
      			return ret
      		return wrapper
      	return _logger
      	
      @logger(5)  # add = logger(5)(add)
      def add(x, y):
      	time.sleep(1)
      	return x + y
      	
      print(add(5,  6), add.__name__, add.__wrapped__, add.__dict__,sep='\n')
      --->
      11
      add
      <function add at 0x00000230B88F2C80>
      {'__wrapped__': <function add at 0x00000230B88F2C80>}
      

Python类型注解

函数定义的弊端

  • python是动态语言,变量随时可以被赋值,且能赋值为不同的类型

  • python不是静态编译型语言,变量类型是在运行期决定的

  • 动态语言很灵活,但是这种特性也是弊端

    • def add(x, y):
      	reutrn x + y
          
      print(add(4, 5))  # 9
      print(add('hello', 'world'))  # 'hello world'
      add(4, 'hello')  # 报错
      -------------------------------------------------
      难发现:由于不做任何类型检查,直到运行期间问题才显示出来,或者上线才能暴露出问题
      难使用:函数的使用者看到的函数的时候,并不知道你的函数的设计,并不知道应该传什么类型的数据
      
  • 如何解决这种动态语言定义的弊端呢?

    • 增加文档Documentation String

      • 这只是一个惯例,不是强制标准,不能要求程序员一定为函数提供说明文档

      • 函数定义更新,文档未必同步更新

      • def add(x, y):
        	"""
        	:param x: int
        	:param y: int
        	:return: int
        	"""
        	return x + y
        	print(help(add))
        
  • 如何解决这种动态语言定义的弊端?

    • 函数注解

      • def add(x:int, y:int) -> int:
        	"""
        	:param x: int
        	:param y: int
        	:return: int
        	"""
        	return x + y
        	
        	print(help(add))
        	print(add(4, 5))
        	print(add('mag', 'edu'))
        
  • 函数注解

    • python 3.5引入

    • 对函数的参数进行类型注解

    • 对函数的返回值进行类型注解

    • 只对函数参数做一个辅助的说明,并不对函数参数进行类型检查,非强制

    • 提供给第三方工具,做代码分析,发现隐藏的bug

    • 函数注解的信息,保存在____annotations____属性中,字典类型

      • add.__annotations__
        {'x':<class 'int'>, 'y':<class 'int'>, 'return':<class 'int'>}
        
    • 变量注解

      • Python 3.6引入。注意:它也是一种对变量的说明,非强制
  • 业务应用

    • 思路
      • 函数参数的检查,最好是在函数外,尽量不要把检查代码写在函数中
      • 函数应该作为参数,传入到检查函数中
      • 检查函数拿到函数函数传入的实际参数,与形参声明对比
      • ____annotations____属性是一个字典,其中包括返回值类型的声明。假设要做位置参数的判断,无法和字典中的声明对应。使用inspect模块解决
    • inspect模块
      • 提供获取对象信息的函数,可以检查函数和类、类型检查

inspect模块

  • inspect.isfunction(add),是否是函数
  • inspect.ismethod(pathlib.Path().absolute),是否是类的方法,要绑定
  • inspect.isgenerator(add),是否是生成器对象
  • inspect.isgeneratorfunction(add),是否是生成器函数
  • inspect.isclass(add),是否是类
  • inspect.ismodule(inspect),是否是模块
  • inspect.isbuiltin(print),是否是内建对象
  • 还有很多is函数,需要的时候查阅inspect模块帮助

signature(callable),获取签名(函数签名包含了一个函数的信息,包括函数名、它的参数类型、它所在的类和名称空间及其他信息)

import inspect

def add(x:int, y:int, *args, m:int, n:int, **kwargs) -> int:
	return x + y + m + n
	
sig = inspect.signature(add)  # 函数签名
print(sig, type(sig))
params = sig.parameters
print(type(params), params)  # OrderedDict
print(sig.return_annotation)

for k,v in params.items():
	print(k,v.annotation, type(v))
------------------------------------->
(x:int, y:int, *args, m:int, n:int, **kwargs) -> int <class 'inspect.Signature'>
<class 'mappingproxy'> OrderedDict([('x', <Parameter "x:int">), ('y', <Parameter "y:int">), ('args', <Parameter "*args">), ('m', <Parameter "m:int">), ('n', <Parameter "n:int">), ('kwargs', <Parameter "**kwargs">)])
<class 'int'>
x <class 'int'> <class 'inspect.Parameter'>
y <class 'int'> <class 'inspect.Parameter'>
args <class 'inspect._empty'> <class 'inspect.Parameter'>
m <class 'int'> <class 'inspect.Parameter'>
n <class 'int'> <class 'inspect.Parameter'>
kwargs <class 'inspect._empty'> <class 'inspect.Parameter'>
  • Parameter对象

    • 保存在元组中,是只读的

    • name,参数的名字

    • annotation,参数的注解,可能没有定义

    • default,参数的缺省值,可能没有定义

    • empty,特殊的类,用来标记default属性或者注释annotation属性的空值

    • kind,实参如何绑定到形参,就是形参的类型

      • POSITIONAL_ONLY,值必须是位置参数提供
      • POSITIONAL_OR_KEYWORLD,值可以作为关键字或者位置参数提供
      • VAR_POSITIONAL,可变位置参数,对应*args
      • KEYWORD_ONLY,keyword_only参数,对应*或者**args之后的出现的非可变关键字参数
      • VAR_KEYWORD,可变关键字参数,对应**kwargs
    • 举例

      • import inspect
        
        def add(x, y:int=7, *args, m:int, n=10, **kwargs) -> int:
        	return x + y + m + n
        	
        sig = inspect.signature(add)  # 函数签名
        print(sig, type(sig))
        params = sig.parameters
        print(type(params), params)  # OrderedDict
        print(sig.return_annotation)
        
        for i,(k,v) in enumerate(params.items(), 1):
        	print(i, k,v.name, v.kind, v.default, v.annotation, sep='\t')
         
        -----------------------------------------------
        (x, y:int=7, *args, m:int, n=10, **kwargs) -> int <class 'inspect.Signature'>
        <class 'mappingproxy'> OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y:int=7">), ('args', <Parameter "*args">), ('m', <Parameter "m:int">), ('n', <Parameter "n=10">), ('kwargs', <Parameter "**kwargs">)])
        <class 'int'>
        1	x	x	POSITIONAL_OR_KEYWORD	<class 'inspect._empty'>	<class 'inspect._empty'>
        2	y	y	POSITIONAL_OR_KEYWORD	7	<class 'int'>
        3	args	args	VAR_POSITIONAL	<class 'inspect._empty'>	<class 'inspect._empty'>
        4	m	m	KEYWORD_ONLY	<class 'inspect._empty'>	<class 'int'>
        5	n	n	KEYWORD_ONLY	10	<class 'inspect._empty'>
        6	kwargs	kwargs	VAR_KEYWORD	<class 'inspect._empty'>	<class 'inspect._empty'>
        
  • 有函数如下

    • def add(x, y:int=7) -> int:
      	return x + y
      
      ------------------------------------------
      请检查用户输入是否符合参数注解的要求?
      思路:
      调用时,用户传入实参,才能判断用户输入的实参是否符合要求
      调用时,用户感觉上还是在调用add函数
      对用户输入的数据和声明的类型进行对比,如果不符合,提示用户
      ------------------------------------------
      import inspect
      
      def check(fn):
      	def wrapper(*args, **kwargs):
      		sig = inspect.signature(fn)  # 函数签名
      		params = sig.parameters
      		values = list(params.values())
      		print(sig, params, values)
      		for i,p in enumerate(args):
      			if isinstance(p, values[i].annotation):
      				print(p, values[i].annotation)
      		for k,v in kwargs.items():
      			if isinstance(v, params[k].annotation):
      				print(v, params[k].annotation)
      		return fn(*args, **kwargs)
      	return wrapper
      	
      def add(x, y:int=7) -> int:
      	return x + y
      ----------------------------------------------------------
      check(add)(20, 10)
      (x, y:int=7) -> int OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y:int=7">)]) [<Parameter "x">, <Parameter "y:int=7">]
      10 <class 'int'>
      30
      ----------------------------------------------------------
      check(add)(20, y=10)
      (x, y:int=7) -> int OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y:int=7">)]) [<Parameter "x">, <Parameter "y:int=7">]
      10 <class 'int'>
      30
      ----------------------------------------------------------
      check(add)(y=10, x=20)
      (x, y:int=7) -> int OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y:int=7">)]) [<Parameter "x">, <Parameter "y:int=7">]
      10 <class 'int'>
      30
      
    • import inspect
      
      def check(fn):
      	def wrapper(*args, **kwargs):
      		sig = inspect.signature(fn)  # 函数签名
      		params = sig.parameters
      		values = list(params.values())
      		print(sig, params, values)
      		for i,p in enumerate(args):
      			value = values[i]
      			if value.annotation is not value.empty and not isinstance(p, values[i].annotation):
      				print(p, 'is not',values[i].annotation)
      		for k,v in kwargs.items():
      			if params[k].annotation is not inspect._empty and not isinstance(v, params[k].annotation):
      				print(v, 'is not', params[k].annotation)
      		return fn(*args, **kwargs)
      	return wrapper
      
      @check
      def add(x, y:int=7) -> int:
      	return x + y
      
      ----------------------------------------------------
      add(20, 10)
      (x, y:int=7) -> int OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y:int=7">)]) [<Parameter "x">, <Parameter "y:int=7">]
      30
      ----------------------------------------------------
      add(20, y=10)
      (x, y:int=7) -> int OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y:int=7">)]) [<Parameter "x">, <Parameter "y:int=7">]
      30
      ----------------------------------------------------
      add(y=10, x=20)
      (x, y:int=7) -> int OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y:int=7">)]) [<Parameter "x">, <Parameter "y:int=7">]
      30
      

Python之functools

functools模块

  • reduce方法

    • reduce方法,顾名思义就是减少

    • reduce(function,sequence[,initial]) -> value

    • 可迭代对象不能为空;初始值没提供就在可迭代对象中取一个元素

      • from functools import reduce
        
        nums = [6, 9, 4, 2, 4, 10, 5, 9, 6, 9]
        print(nums)
        print(sum(nums))
        print(reduce(lambda val, x:val + x, nums))
        --->
        [6, 9, 4, 2, 4, 10, 5, 9, 6, 9]
        64
        64
        
  • partial方法

    • 偏函数,把函数部分的参数固定下来,相当于为部分的参数添加了一个固定的默认值,形成一个新的函数并返回

    • 从partial生成的新函数,是对原函数的封装

    • 举例

      • import functools
        
        def add(x, y) -> int:
        	return x + y
        	
        newadd = functools.partial(add, y=5)
        
        print(newadd(7))  # 12
        print(newadd(7, y=6))  # 13
        print(newadd(y=10, x=6))  # 16
        
        import inspect
        print(inspect.signature(newadd))  # (x, *, y=5) -> int
        
      • # partial方法举例
        import functools
        
        def add(x, y, *args) -> int:
        	print(args)
        	return x + y
        	
        newadd = functools.partial(add, 1, 3, 6, 5)
        
        print(newadd(7))  # (6, 5, 7) 4
        print(newadd(7, 10))  # (6, 5, 7, 10) 4
        print(newadd(9, 10, y=20, x=26))  # 报错
        print(newadd())  # (6, 5) 4
        
        import inspect
        print(inspect.signature(newadd))  # (*args) -> int   
        
  • partial函数本质

    • def partial(func, *args, **keywords):
      	def newfunc(*fargs, **fkeywords):  # 包装函数
      		newkeywords = kewwords.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)
      foo(5)
      --->
      9
      
  • partial函数—分析functools.wraps的实现

    • @functools.lre_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, z=3):
        	time.sleep(z)
        	return x + y
        	
        add(4, 5)  # 9
        add(4.0, 5)  # 9.0
        add(4, 6)  # 10
        add(4, 6, 3)  # 10
        add(6, 4)  # 10
        add(4, y=6)  # 10
        add(x=4, y=6)  # 10
        add(y=6, x=4)  # 10
        
  • lru_cache装饰器

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

      functools._make_key((4, 6),{'z':3},False) ->
      [4, 6, <object at 0x1bb715ab0b0>, 'z', 3]
      -----------------------------------------------
      functools._make_key((4, 6, 3), {}, False) ->
      [4, 6, 3]
      -----------------------------------------------
      functools._make_key(tuple(), {'z':3, 'x':4, 'y':6}, False) ->
      [<object at 0x1bb715ab0b0>, 'z', 3, 'x', 4, 'y', 6]
      -----------------------------------------------
      functools._make_key((), {'z':3, 'x':4, 'y':6}, True) ->
      [<object at 0x1bb715ab0b0>, 'z', 3, 'x', 4, 'y', 6, int, int, int]
      
    • 斐波那契数列递归方法的改造

      • import functools
        
        @functools.lru_cache()  # maxsize = None
        def fib(n):
        	return 1 if n < 3 else fib(n-1) + fib(n-2)
        	
        print([fib(i + 1) for i in range(1, 36)])
        --->
        [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352]
        
    • lru_cache装饰器应用

      • 使用前提
        • 同样的函数参数一定得到同样的结果
        • 函数执行时间很长,且要多次执行
      • 本质时函数调用的参数 => 返回值
      • 缺点
        • 不支持缓存过期,key无法过期、失效
        • 不支持清除操作
        • 不支持分布式,是一个单机的缓存
      • 使用场景:单机上需要空间换时间的地方,可以使用缓存来将计算变成快速的查询
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值