详解Python的装饰器
Python中的装饰器是你进入Python大门的一道坎,不管你跨不跨过去它都在那里。
为什么需要装饰器
我们假设你的程序实现了say_hello()
和say_goodbye()
两个函数。
def say_hello(): | |
print "hello!" | |
def say_goodbye(): | |
print "hello!" # bug here | |
if __name__ == '__main__': | |
say_hello() | |
say_goodbye() |
但是在实际调用中,我们发现程序出错了,上面的代码打印了两个hello。经过调试你发现是say_goodbye()
出错了。老板要求调用每个方法前都要记录进入函数的名称,比如这样:
[DEBUG]: Enter say_hello() | |
Hello! | |
[DEBUG]: Enter say_goodbye() | |
Goodbye! |
好,小A是个毕业生,他是这样实现的。
def say_hello(): | |
print "[DEBUG]: enter say_hello()" | |
print "hello!" | |
def say_goodbye(): | |
print "[DEBUG]: enter say_goodbye()" | |
print "hello!" | |
if __name__ == '__main__': | |
say_hello() | |
say_goodbye() |
很low吧? 嗯是的。小B工作有一段时间了,他告诉小A可以这样写。
def debug(): | |
import inspect | |
caller_name = inspect.stack()[1][3] | |
print "[DEBUG]: enter {}()".format(caller_name) | |
def say_hello(): | |
debug() | |
print "hello!" | |
def say_goodbye(): | |
debug() | |
print "goodbye!" | |
if __name__ == '__main__': | |
say_hello() | |
say_goodbye() |
是不是好一点?那当然,但是每个业务函数里都要调用一下debug()
函数,是不是很难受?万一老板说say相关的函数不用debug,do相关的才需要呢?
那么装饰器这时候应该登场了。
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。
概括的讲,装饰器的作用就是为已经存在的函数或对象添加额外的功能。
怎么写一个装饰器
在早些时候 (Python Version < 2.4,2004年以前),为一个函数添加额外功能的写法是这样的。
def debug(func): | |
def wrapper(): | |
print "[DEBUG]: enter {}()".format(func.__name__) | |
return func() | |
return wrapper | |
def say_hello(): | |
print "hello!" | |
say_hello = debug(say_hello) # 添加功能并保持原函数名不变 |
上面的debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖,下面代码等同于早期的写法。
def debug(func): | |
def wrapper(): | |
print "[DEBUG]: enter {}()".format(func.__name__) | |
return func() | |
return wrapper | |
@debug | |
def say_hello(): | |
print "hello!" |
这是最简单的装饰器,但是有一个问题,如果被装饰的函数需要传入参数,那么这个装饰器就坏了。因为返回的函数并不能接受参数,你可以指定装饰器函数wrapper
接受和原函数一样的参数,比如:
def debug(func): | |
def wrapper(something): # 指定一毛一样的参数 | |
print "[DEBUG]: enter {}()".format(func.__name__) | |
return func(something) | |
return wrapper # 返回包装过函数 | |
@debug | |
def say(something): | |
print "hello {}!".format(something) |
这样你就解决了一个问题,但又多了N个问题。因为函数有千千万,你只管你自己的函数,别人的函数参数是什么样子,鬼知道?还好Python提供了可变参数*args
和关键字参数**kwargs
,有了这两个参数,装饰器就可以用于任意目标函数了。
def debug(func): | |
def wrapper(*args, **kwargs): # 指定宇宙无敌参数 | |
print "[DEBUG]: enter {}()".format(func.__name__) | |
print 'Prepare and say...', | |
return func(*args, **kwargs) | |
return wrapper # 返回 | |
@debug | |
def say(something): | |
print "hello {}!".format(something) |
至此,你已完全掌握初级的装饰器写法。
高级一点的装饰器
带参数的装饰器和类装饰器属于进阶的内容。在理解这些装饰器之前,最好对函数的闭包和装饰器的接口约定有一定了解。(参见Python的闭包)
带参数的装饰器
假设我们前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出log信息,而且还需指定log的级别,那么装饰器就会是这样的。
def logging(level): | |
def wrapper(func): | |
def inner_wrapper(*args, **kwargs): | |
print "[{level}]: enter function {func}()".format( | |
level=level, | |
func=func.__name__) | |
return func(*args, **kwargs) | |
return inner_wrapper | |
return wrapper | |
@logging(level='INFO') | |
def say(something): | |
print "say {}!".format(something) | |
# 如果没有使用@语法,等同于 | |
# say = logging(level='INFO')(say) | |
@logging(level='DEBUG') | |
def do(something): | |
print "do {}...".format(something) | |
if __name__ == '__main__': | |
say('hello') | |
do("my work") |
是不是有一些晕?你可以这么理解,当带参数的装饰器被打在某个函数上时,比如@logging(level='DEBUG')
,它其实是一个函数,会马上被执行,只要这个它返回的结果是一个装饰器时,那就没问题。细细再体会一下。
基于类实现的装饰器
装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重载了__call__()
方法,那么这个对象就是callable的。
class Test(): | |
def __call__(self): | |
print 'call me!' | |
t = Test() | |
t() # call me |
像__call__
这样前后都带下划线的方法在Python中被称为内置方法,有时候也被称为魔法方法。重载这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个类对象拥有了被调用的行为。
回到装饰器上的概念上来,装饰器要求接受一个callable对象,并返回一个callable对象(不太严谨,详见后文)。那么用类来实现也是也可以的。我们可以让类的构造函数__init__()
接受一个函数,然后重载__call__()
并返回一个函数,也可以达到装饰器函数的效果。
class logging(object): | |
def __init__(self, func): | |
self.func = func | |
def __call__(self, *args, **kwargs): | |
print "[DEBUG]: enter function {func}()".format( | |
func=self.func.__name__) | |
return self.func(*args, **kwargs) | |
@logging | |
def say(something): | |
print "say {}!".format(something) |
带参数的类装饰器
如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。那么在构造函数里接受的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后在重载__call__
方法是就需要接受一个函数并返回一个函数。
class logging(object): | |
def __init__(self, level='INFO'): | |
self.level = level | |
def __call__(self, func): # 接受函数 | |
def wrapper(*args, **kwargs): | |
print "[{level}]: enter function {func}()".format( | |
level=self.level, | |
func=func.__name__) | |
func(*args, **kwargs) | |
return wrapper #返回函数 | |
@logging(level='INFO') | |
def say(something): | |
print "say {}!".format(something) |
内置的装饰器
内置的装饰器和普通的装饰器原理是一样的,只不过返回的不是函数,而是类对象,所以更难理解一些。
@property
在了解这个装饰器前,你需要知道在不使用装饰器怎么写一个属性。
def getx(self): | |
return self._x | |
def setx(self, value): | |
self._x = value | |
def delx(self): | |
del self._x | |
# create a property | |
x = property(getx, setx, delx, "I am doc for x property") |
以上就是一个Python属性的标准写法,其实和Java挺像的,但是太罗嗦。有了@语法糖,能达到一样的效果但看起来更简单。
@property | |
def x(self): ... | |
# 等同于 | |
def x(self): ... | |
x = property(x) |
属性有三个装饰器:setter
, getter
, deleter
,都是在property()
的基础上做了一些封装,因为setter
和deleter
是property()
的第二和第三个参数,不能直接套用@语法。getter
装饰器和不带getter
的属性装饰器效果是一样的,估计只是为了凑数,本身没有任何存在的意义。经过@property
装饰过的函数返回的不再是一个函数,而是一个property
对象。
>>> property() | |
<property object at 0x10ff07940> |
@staticmethod,@classmethod
有了@property
装饰器的了解,这两个装饰器的原理是差不多的。@staticmethod
返回的是一个staticmethod
类对象,而@classmethod
返回的是一个classmethod
类对象。他们都是调用的是各自的__init__()
构造函数。
class classmethod(object): | |
""" | |
classmethod(function) -> method | |
""" | |
def __init__(self, function): # for @classmethod decorator | |
pass | |
# ... | |
class staticmethod(object): | |
""" | |
staticmethod(function) -> method | |
""" | |
def __init__(self, function): # for @staticmethod decorator | |
pass | |
# ... |
装饰器的@语法就等同调用了这两个类的构造函数。
class Foo(object): | |
@staticmethod | |
def bar(): | |
pass | |
# 等同于 bar = staticmethod(bar) |
至此,我们上文提到的装饰器接口定义可以更加明确一些,装饰器必须接受一个callable对象,其实它并不关心你返回什么,可以是另外一个callable对象(大部分情况),也可以是其他类对象,比如property。
装饰器里的那些坑
装饰器可以让你代码更加优雅,减少重复,但也不全是优点,也会带来一些问题。
位置错误的代码
让我们直接看示例代码。
def html_tags(tag_name): | |
print 'begin outer function.' | |
def wrapper_(func): | |
print "begin of inner wrapper function." | |
def wrapper(*args, **kwargs): | |
content = func(*args, **kwargs) | |
print "<{tag}>{content}</{tag}>".format(tag=tag_name, content=content) | |
print 'end of inner wrapper function.' | |
return wrapper | |
print 'end of outer function' | |
return wrapper_ | |
@html_tags('b') | |
def hello(name='Toby'): | |
return 'Hello {}!'.format(name) | |
hello() | |
hello() |
在装饰器中我在各个可能的位置都加上了print语句,用于记录被调用的情况。你知道他们最后打印出来的顺序吗?如果你心里没底,那么最好不要在装饰器函数之外添加逻辑功能,否则这个装饰器就不受你控制了。以下是输出结果:
begin outer function. | |
end of outer function | |
begin of inner wrapper function. | |
end of inner wrapper function. | |
<b>Hello Toby!</b> | |
<b>Hello Toby!</b> |
错误的函数签名和文档
装饰器装饰过的函数看上去名字没变,其实已经变了。
def logging(func): | |
def wrapper(*args, **kwargs): | |
"""print log before a function.""" | |
print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__) | |
return func(*args, **kwargs) | |
return wrapper | |
@logging | |
def say(something): | |
"""say something""" | |
print "say {}!".format(something) | |
print say.__name__ # wrapper |
为什么会这样呢?只要你想想装饰器的语法糖@代替的东西就明白了。@等同于这样的写法。
say = logging(say) |
logging
其实返回的函数名字刚好是wrapper
,那么上面的这个语句刚好就是把这个结果赋值给say
,say
的__name__
自然也就是wrapper
了,不仅仅是name
,其他属性也都是来自wrapper
,比如doc
,source
等等。
使用标准库里的functools.wraps
,可以基本解决这个问题。
from functools import wraps | |
def logging(func): | |
@wraps(func) | |
def wrapper(*args, **kwargs): | |
"""print log before a function.""" | |
print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__) | |
return func(*args, **kwargs) | |
return wrapper | |
@logging | |
def say(something): | |
"""say something""" | |
print "say {}!".format(something) | |
print say.__name__ # say | |
print say.__doc__ # say something |
看上去不错!主要问题解决了,但其实还不太完美。因为函数的签名和源码还是拿不到的。
import inspect | |
print inspect.getargspec(say) # failed | |
print inspect.getsource(say) # failed |
如果要彻底解决这个问题可以借用第三方包,比如wrapt
。后文有介绍。
不能装饰@staticmethod 或者 @classmethod
当你想把装饰器用在一个静态方法或者类方法时,不好意思,报错了。
class Car(object): | |
def __init__(self, model): | |
self.model = model | |
@logging # 装饰实例方法,OK | |
def run(self): | |
print "{} is running!".format(self.model) | |
@logging # 装饰静态方法,Failed | |
@staticmethod | |
def check_model_for(obj): | |
if isinstance(obj, Car): | |
print "The model of your car is {}".format(obj.model) | |
else: | |
print "{} is not a car!".format(obj) | |
""" | |
Traceback (most recent call last): | |
... | |
File "example_4.py", line 10, in logging | |
@wraps(func) | |
File "C:\Python27\lib\functools.py", line 33, in update_wrapper | |
setattr(wrapper, attr, getattr(wrapped, attr)) | |
AttributeError: 'staticmethod' object has no attribute '__module__' | |
""" |
前面已经解释了@staticmethod
这个装饰器,其实它返回的并不是一个callable对象,而是一个staticmethod
对象,那么它是不符合装饰器要求的(比如传入一个callable对象),你自然不能在它之上再加别的装饰器。要解决这个问题很简单,只要把你的装饰器放在@staticmethod
之前就好了,因为你的装饰器返回的还是一个正常的函数,然后再加上一个@staticmethod
是不会出问题的。
class Car(object): | |
def __init__(self, model): | |
self.model = model | |
@staticmethod | |
@logging # 在@staticmethod之前装饰,OK | |
def check_model_for(obj): | |
pass |
如何优化你的装饰器
嵌套的装饰函数不太直观,我们可以使用第三方包类改进这样的情况,让装饰器函数可读性更好。
decorator.py
decorator.py 是一个非常简单的装饰器加强包。你可以很直观的先定义包装函数wrapper()
,再使用decorate(func, wrapper)
方法就可以完成一个装饰器。
from decorator import decorate | |
def wrapper(func, *args, **kwargs): | |
"""print log before a function.""" | |
print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__) | |
return func(*args, **kwargs) | |
def logging(func): | |
return decorate(func, wrapper) # 用wrapper装饰func |
你也可以使用它自带的@decorator
装饰器来完成你的装饰器。
from decorator import decorator | |
@decorator | |
def logging(func, *args, **kwargs): | |
print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__) | |
return func(*args, **kwargs) |
decorator.py
实现的装饰器能完整保留原函数的name
,doc
和args
,唯一有问题的就是inspect.getsource(func)
返回的还是装饰器的源代码,你需要改成inspect.getsource(func.__wrapped__)
。
wrapt
wrapt是一个功能非常完善的包,用于实现各种你想到或者你没想到的装饰器。使用wrapt实现的装饰器你不需要担心之前inspect中遇到的所有问题,因为它都帮你处理了,甚至inspect.getsource(func)
也准确无误。
import wrapt | |
# without argument in decorator | |
@wrapt.decorator | |
def logging(wrapped, instance, args, kwargs): # instance is must | |
print "[DEBUG]: enter {}()".format(wrapped.__name__) | |
return wrapped(*args, **kwargs) | |
@logging | |
def say(something): pass |
使用wrapt你只需要定义一个装饰器函数,但是函数签名是固定的,必须是(wrapped, instance, args, kwargs)
,注意第二个参数instance
是必须的,就算你不用它。当装饰器装饰在不同位置时它将得到不同的值,比如装饰在类实例方法时你可以拿到这个类实例。根据instance
的值你能够更加灵活的调整你的装饰器。另外,args
和kwargs
也是固定的,注意前面没有星号。在装饰器内部调用原函数时才带星号。
如果你需要使用wrapt写一个带参数的装饰器,可以这样写。
def logging(level): | |
@wrapt.decorator | |
def wrapper(wrapped, instance, args, kwargs): | |
print "[{}]: enter {}()".format(level, wrapped.__name__) | |
return wrapped(*args, **kwargs) | |
return wrapper | |
@logging(level="INFO") | |
def do(work): pass |
关于wrapt的使用,建议查阅官方文档,在此不在赘述。
- Getting Started — wrapt 1.13.0rc2 documentation
小结
Python的装饰器和Java的注解(Annotation)并不是同一回事,和C#中的特性(Attribute)也不一样,完全是两个概念。
装饰器的理念是对原函数、对象的加强,相当于重新封装,所以一般装饰器函数都被命名为wrapper()
,意义在于包装。函数只有在被调用时才会发挥其作用。比如@logging
装饰器可以在函数执行时额外输出日志,@cache
装饰过的函数可以缓存计算结果等等。
而注解和特性则是对目标函数或对象添加一些属性,相当于将其分类。这些属性可以通过反射拿到,在程序运行时对不同的特性函数或对象加以干预。比如带有Setup
的函数就当成准备步骤执行,或者找到所有带有TestMethod
的函数依次执行等等。
至此我所了解的装饰器已经讲完,但是还有一些内容没有提到,比如装饰类的装饰器。有机会再补充。谢谢观看。
相关文章:
详解Python的装饰器
Python中的装饰器是你进入Python大门的一道坎,不管你跨不跨过去它都在那里。 为什么需要装饰器 我们假设你的程序实现了say_hello()和say_goodbye()两个函数。 def say_hello():print "hello!"def say_goodbye():print "hello!" # bug hereif…...
k8s-Pod域名学习总结
k8s-Pod域名学习总结 大纲 k8s内置DNS服务 配置Pod的域名服务 CornDNS配置 默认Pod的域名 自定义Pod的域名 实战需求 1 Pod有自己的域名 2 集群内部的Pod可以通过域名访问其他的Pod 基础准备: 1 k8s 集群版本1.17 k8s内置DNS服务 k8s1.17安装完成后自动创建…...
0405习题总结-不定积分
文章目录1 不定积分的基本概念2 直接积分法-基本积分公式3 第一换元法-凑微分形式法4 第二类换元法5 分部积分求不定积分6 表格法积分7 有理函数求积分后记1 不定积分的基本概念 例1 f(x){x1,x≥012e−x12,x<0求∫f(x)dxf(x) \begin{cases} x1,\quad x\ge0\\ \frac{1}{2}e^…...
QT 常用控件类型命名参考
拟定的QT的控件命名规则:蛇形命名方式 控件类型开头,以下是QT控件类型命名的参考范例 Buttons Buttons起始字符串对象名称举例Push Buttonbuttonbutton_loginTool Buttontool_button / buttonbutton_switchRadio Buttonradio_button / radioradio_boy…...
MATLAB与图像处理的那点小事儿~
目录 一、学习内容 二、matlab基本知识 三、线性点运算 四、非线性点运算,伽马矫正 五、直方图 1、直方图均衡化 (1)使用histep函数实现图像均衡化 (2)使用自行编写的均衡化函数实现图像均衡化 2、直方图规定…...
第十四届蓝桥杯模拟赛(第三期)Java组个人题解
第十四届蓝桥杯模拟赛(第三期)Java组个人题解 今天做了一下第三期的校内模拟赛,有些地方不确定,欢迎讨论和指正~ 文章目录第十四届蓝桥杯模拟赛(第三期)Java组个人题解填空题部分第一题【最小数】第二题【E…...
Go语言之条件判断循环语句(if-else、switch-case、for、goto、break、continue)
一、if-else条件判断语句 Go中的if-else条件判断语句跟C差不多。但是需要注意的是,Go中强制规定,关键字if和else之后的左边的花括号"{“必须和关键字在同一行,若使用了else if结构,则前段代码快的右花括号”}"必须和关…...
深入理解AQS
概念设计初衷:该类利用 状态队列 实现了一个同步器,更多的是提供一些模板方法(子类必须重写,不然会抛错)。 设计功能:独占、共享模式两个核心,state、Queue2.1 statesetState、compareAndSetSta…...
JVM学习笔记十:执行引擎
0. 前言 声明: 感谢尚硅谷宋红康老师的讲授。 感谢广大网友共享的笔记内容。 B站:https://www.bilibili.com/video/BV1PJ411n7xZ 本文的内容基本来源于宋老师的课件,其中有一些其他同学共享的内容,也有一些自己的理解内容。 1. …...
【2023-03-10】JS逆向之美团滑块
提示:文章仅供参考,禁止用于非法途径 前言 目标网站:aHR0cHM6Ly9wYXNzcG9ydC5tZWl0dWFuLmNvbS9hY2NvdW50L3VuaXRpdmVsb2dpbg 页面分析 接口流程 1.https://passport.meituan.com/account/unitivelogin主页接口:需获取下面的参数࿰…...
全志V853芯片放开快启方案打印及在快起方式下配置isp led的方法
全志V85x芯片 如何放开快启方案的打印? 1.主题 如何放开快启方案的打印 2.问题背景 产品:v851系列快启方案 软件:tina 其他:特有版本信息添加自由描述 (如固件版本,复现概率,特定环境&#x…...
大数据 | (一)Hadoop伪分布式安装
大数据原理与应用教材链接:大数据技术原理与应用电子课件-林子雨编著 Hadoop伪分布式安装借鉴文章:Hadoop伪分布式安装-比课本详细 大数据 | (二)SSH连接报错Permission denied:SSH连接报错Permission denied 哈喽&a…...
Django/Vue实现在线考试系统-06-开发环境搭建-Django安装
1.0 基本介绍 Django 是一个由 Python 编写的一个开放源代码的 Web 应用框架。 使用 Django,只要很少的代码,Python 的程序开发人员就可以轻松地完成一个正式网站所需要的大部分内容,并进一步开发出全功能的 Web 服务 Django 本身基于 MVC 模型,即 Model(模型)+ View(…...
KaiwuDB 时序引擎数据存储内存对齐技术解读
一、理论1、什么是内存对齐现代计算机中内存空间都是按照 byte 划分的,在计算机中访问一个变量需要访问它的内存地址,从理论上看,似乎对任何类型的变量的访问都可以从任何地址开始。但在实际情况中,通常在特定的内存地址才能访问特…...
IR 808 Alkyne,IR-808 alkyne,IR 808炔烃,近红外吲哚类花菁染料
【产品理化指标】:中文名:IR-808炔烃英文名:IR-808 alkyne,Alkyne 808-IR CAS号:N/AIR-808结构式:规格包装:10mg,25mg,50mg,接受各种复杂PEGS定制服务&#x…...
elasticsearch
这里写目录标题1.初识ElasticSearch1.1 了解ES1.2 倒排索引1.2.1 正向索引1.2.2 倒排索引1.2.3 正向和倒排1.3 ES的一些概念1.3.1 文档和字段1.3.2 索引和映射1.3.3 mysql和elasticsearch1.4 安装ES、kibana1.初识ElasticSearch 1.1 了解ES elasticsearch是一款非常强大的开源…...
并发编程---java锁
java锁一 多线程锁synchronized案例分析1.1synchronized介绍1.2 synchronized案例分析1.2.1.标准访问,请问先打印邮件还是短信?1.2.2.邮件⽅法暂停4秒钟,请问先打印邮件还是短信?分析1.2.3.新增⼀个普通⽅法hello(&…...
品牌营销 | 学习如何最大限度地发挥品牌营销的作用
您是否想过如何最大限度地发挥品牌营销的潜力?这是一项艰巨的挑战,通过了解品牌营销的基本组成部分,您可以成功地推广您的品牌。 (图源:Pixabay) 品牌营销的基本组成部分 你需要做什么来发展稳固的品牌&am…...
Linux驱动的同步阻塞和同步非阻塞
在字符设备驱动中,若要求应用与驱动同步,则在驱动程序中可以根据情况实现为阻塞或非阻塞一、同步阻塞这种操作会阻塞应用程序直到设备完成read/write操作或者返回一个错误码。在应用程序阻塞这段时间,程序所代表的进程并不消耗CPU的时间&…...
LearnOpenGL-光照-5.投光物
本人刚学OpenGL不久且自学,文中定有代码、术语等错误,欢迎指正 我写的项目地址:https://github.com/liujianjie/LearnOpenGLProject 文章目录投光物平行光点光源聚光不平滑的例子平滑例子投光物 前面几节使用的光照都来自于空间中的一个点 即…...
【C语言】每日刷题 —— 牛客语法篇(1)
前言 大家好,今天带来一篇新的专栏c_牛客,不出意外的话每天更新十道题,难度也是从易到难,自己复习的同时也希望能帮助到大家,题目答案会根据我所学到的知识提供最优解。 🏡个人主页:悲伤的猪大…...
【深度学习】Subword Tokenization算法
在自然语言处理中,面临的首要问题是如何让模型认识我们的文本信息,词,是自然语言处理中基本单位,神经网络模型的训练和预测都需要借助词表来对句子进行表示。 1.构建词表的传统方法 在字词模型问世之前,做自然语言处理…...
五分钟了解支付、交易、清算、银行等专业名词的含义?
五分钟了解支付、交易、清算、银行等专业名词的含义?1. 支付类名词01 支付应用02 支付场景03 交易类型04 支付类型(按通道类型)05 支付类型(按业务双方类型)06 支付方式07 支付产品08 收银台类型09 支付通道10 通道类型…...
4个工具,让 ChatGPT 如虎添翼!
LightGBM中文文档 机器学习统计学,476页 机器学习圣经PRML中文版...
初识PO、VO、DAO、BO、DTO、POJO时
PO、VO、DAO、BO、DTO、POJO 区别分层领域模型规约DO(Data Object)DTO(Data Transfer Object)BO(Business Object)AO(ApplicationObject)VO(View Object)Query领域模型命名规约:一、PO :(persistant object ),持久对象二、VO :(value object) ࿰…...
[2.2.4]进程管理——FCFS、SJF、HRRN调度算法
文章目录第二章 进程管理FCFS、SJF、HRRN调度算法(一)先来先服务(FCFS, First Come First Serve)(二)短作业优先(SJF, Shortest Job First)对FCFS和SJF两种算法的思考(三…...
【代码随想录Day55】动态规划
583 两个字符串的删除操作 https://leetcode.cn/problems/delete-operation-for-two-strings/72 编辑距离https://leetcode.cn/problems/edit-distance/...
Java开发 - 消息队列前瞻
前言 学完了Redis,那你一定不能错过消息队列,要说他俩之间的关联?关联是有的,但也不见得很大,只是他们都是大数据领域常用的一种工具,一种用来提高程序运行效率的工具。常见于高并发,大数据&am…...
MySQL连接IDEA详细教程
使用IDEA的时候,需要连接Database,连接时遇到了一些小问题,下面记录一下操作流程以及遇到的问题的解决方法。 目录 MySQL连接IDEA详细教程 MySQL连接IDEA详细教程 打开idea,点击右侧的 Database 或者 选择 View --> Tool Wind…...
线程(操作系统408)
基本概念 我们说引入进程的目的是更好的使用多道程序并发执行,提高资源的利用率和系统吞吐量;而引入线程的目的则是减小程序在并发执行的时候所付出的时间开销,提高操作系统的并发性能。 线程可以理解成"轻量级进程",…...
网站建设公司电话/公司网站设计方案
正题一看到题目,就令人窒息。。。森林森林,也就是说一开始有很多棵树。然后要查找路径第k大。明显要找lca,就想到了树链剖分。每次往上跳把当前这一段记录下来,很明显要开n棵前缀主席树。然后再让找出来的op个区间相减(…...
网站开发与应用论文百度文库/石家庄百度seo排名
题目描述 给定两个字符串,请编写程序,确定其中一个字符串的字符重新排列后,能否变成另一个字符串。这里规定大小写为不同字符,且考虑字符串中的空格。 给定一个string stringA和一个string stringB,请返回一个bool&a…...
wordpress英文导航模板/seo是什么意思呢
前言 算法,在行业里越来越重要,一线互联网公司也非常注重算法,所以在面试时基本上都有涉及到。字节跳动是出了名的爱问算法题,几乎每一面都要问到算法。实际上,现在很多公司都会问算法,尤其是对于应届生来…...
我要自学网免费视频教程/seo网站关键词优化费用
iOS使用宏定义函数和代码块 今天在开发过程中碰到一个问题:就是父类中要向外发送通知,然后子类中或者其他类中来接收它。当然一般是把它写到类方法中去,但是有个问题,就是如果调用的类不是它的子类,就不能直接调用&…...
wordpress搭建首页/连接交换
java 开发领域日志框架 log4j log for java logback 来显示mybatis sql语句 1、在mybatis框架配置文件中 src/main/resource/mybatis-config.xml 加入 <settings> <setting name"logPrefix" value"dao."/> </settings> 加入效果如下&…...
苏州市住房和城乡建设局投折网站/以图搜图百度识图
前言 最近阅读了EventBus(3.0.0)的源码,这里也是记录下自己对EventBus的理解,功力善浅,如有错误地方还望各位大佬及时指正。 1、EventBus的简单使用 1、注册与反注册 Overrideprotected void onStart() {super.onStart…...