Python日志logging实战教程

一、什么是日志

在《网络安全之认识日志采集分析审计系统》中我们认识了日志。日志数据的核心就是日志消息或日志,日志消息是计算机系统、设备、软件等在某种刺激下反应生成的东西。

日志数据(log data)就是一条日志消息的内在含义,用来告诉你为什么生成日志消息的信息。日志(log)指用于展示某些事件全貌的日志消息的集合。

二、为什么要写日志

日志是对软件执行时所发生事件的一种追踪方式。软件开发人员对他们的代码添加日志调用,借此来指示某事件的发生。一个事件通过一些包含变量数据的描述信息来描述。对于软件系统来说,健全的日志记录是程序调试、故障定位、事件追溯的有效手段。

日志通用的几种类型:

  • 信息(Info):告诉用户和管理员发生了一些没有风险的事情。
  • 调试(Debug):在应用程序代码运行时生成调试信息,给软件开发人员提供故障检测和定位问题的帮助。
  • 警告(Warning):缺少需要的文件、参数、数据,但又不影响系统运行时生成警告。
  • 错误(Error):传达在计算机系统重出现的各种级别的错误。许多错误消息只能给出为什么出错的起点,要寻找出导致错误发生的根本原因还需要进一步的调查。

三、Python日志logging模块实战

在进行Python程序开发时,Python提供了logging模块,能够很好的帮助开发人员很方便的的记录日志信息。

对于简单的日志使用来说日志功能提供了一系列便利的函数。它们是 debug(),info(),warning(),error() 和 critical()。想要决定何时使用日志,请看下表,其中显示了对于每个通用任务集合来说最好的工具。

你想要执行的任务此任务的最好的工具
对于命令行或程序的应用,结果显示在控制台。print()
在对程序的普通操作发生时提交事件报告(比如:状态监控和错误调查)logging.info() 函数(当有诊断目的需要详细输出信息时使用 logging.debug() 函数)
提出一个警告信息基于一个特殊的运行时事件warnings.warn() 位于代码库中,该事件是可以避免的,需要修改客户端应用以消除告警logging.warning() 不需要修改客户端应用,但是该事件还是需要引起关注
对一个特殊的运行时事件报告错误引发异常
报告错误而不引发异常(如在长时间运行中的服务端进程的错误处理)logging.error(), logging.exception() 或 logging.critical() 分别适用于特定的错误及应用领域

日志功能应以所追踪事件级别或严重性而定。各级别适用性如下(以严重性递增):

级别何时使用
DEBUG细节信息,仅当诊断问题时适用。
INFO确认程序按预期运行。
WARNING表明有已经或即将发生的意外(例如:磁盘空间不足)。程序仍按预期进行。
ERROR由于严重的问题,程序的某些功能已经不能正常执行
CRITICAL严重的错误,表明程序已不能继续执行

默认的级别是 WARNING,意味着只会追踪该级别及以上的事件,除非更改日志配置。

所追踪事件可以以不同形式处理。最简单的方式是输出到控制台。另一种常用的方式是写入磁盘文件。

Python的logging库采用模块化方法,并提供了几类组件:记录器,处理程序,过滤器和格式化程序。

  • 记录器(Logger):提供应用程序代码直接使用的接口。
  • 处理器(Handler):将日志记录(由记录器创建)发送到适当的目的地。
  • 筛选器(Filter):提供了更细粒度的功能,用于确定要输出的日志记录。
  • 格式器(Formatter):程序在最终输出日志记录的内容格式。

logging的工作流程:以记录器Logger为对象,设置合适的处理器Handler,辅助以筛选器Filter、格式器Formatter,设置日志级别以及常用的方法,最终输出理想的日志记录给到指定目标
一个Logger可以包含多个Handler;
每个Handler可以设置自己的Filter和Formatter;
记录器和处理器中的日志事件信息流程如下图所示:
日志事件信息流程

接下来我们通过几个简单的应用场景来进行日志记录实战

1、最简单的日志记录

main.py

import logging

def print_hi(name):
    logging.debug('this is print_hi debug')
    logging.info('this is print_hi info')
    logging.warning('this is print_hi warning')
    logging.error('this is print_hi error')
    logging.critical('this is print_hi critical')
    print(f'Hi print_hi, {name}')

if __name__ == '__main__':
    print_hi('XieJava')

结果如下:
最简单的日志记录结果

这里体现了两个问题:
1.通过print()在控制台打印的日志,比logging打印的日志提前打印显示,说明日志记录是多线程的。在平时日志调试跟踪的时候注意这一点,print()的信息有时会打印在logging前有时会在logging后。

2.debug和info的日志没有打印出来,说明logging默认的日志级别是waring。
如果要设置改变默认的日志级别可以通过配置来设置日志级别如:level=logging.DEBUG

2、设置日志级别

import logging

logging.basicConfig(level=logging.DEBUG) #设置日志级别

def print_hi(name):
    logging.debug('this is print_hi debug')
    logging.info('this is print_hi info')
    logging.warning('this is print_hi warning')
    logging.error('this is print_hi error')
    logging.critical('this is print_hi critical')
    print(f'Hi print_hi, {name}')

if __name__ == '__main__':
    print_hi('XieJava')

设置日志级别结果

这下从DEBUG到CRITICAL级别的都打印出来了。

3、设置日志显示格式

默认的日志打印显示的格式是, 日志级别:logger实例名称(默认是root):日志消息内容
如这里显示的是:

DEBUG:root:this is print_hi debug
INFO:root:this is print_hi info
WARNING:root:this is print_hi warning
ERROR:root:this is print_hi error
CRITICAL:root:this is print_hi critical

在真实使用的场景下,一般都要显示日志的时间,我们可以通过设置日志显示格式来调整我们需要显示的日志格式和内容。

LOG_FORMAT = "%(asctime)s - %(levelname)s %(name)s %(filename)s [line:%(lineno)d] - %(message)s"
logging.basicConfig(format=LOG_FORMAT,level=logging.DEBUG)

这里设置了日志发生时间、日志级别、logger实例名称、日志发生的文件名、日志发生所在的行、日志消息内容。
设置日志格式

更多的logRecord属性如下:

属性名称格式描述
args此属性不需要用户进行格式化。合并到 msg 以产生 message 的包含参数的元组,或是其中的值将被用于合并的字典(当只有一个参数且其类型为字典时)。
asctime%(asctime)s表示 LogRecord 何时被创建的供人查看时间值。 默认形式为 ‘2003-07-08 16:49:45,896’ (逗号之后的数字为时间的毫秒部分)。
created%(created)fLogRecord 被创建的时间(即 time.time() 的返回值)。
exc_info此属性不需要用户进行格式化。异常元组(例如 sys.exc_info)或者如未发生异常则为 None。
filename%(filename)spathname 的文件名部分。
funcName%(funcName)s函数名包括调用日志记录。
levelname%(levelname)s消息文本记录级别(‘DEBUG’,‘INFO’,‘WARNING’,‘ERROR’,‘CRITICAL’)
levelno%(levelno)s消息数字的记录级别 (DEBUG, INFO, WARNING, ERROR, CRITICAL)
lineno%(lineno)d发出日志记录调用所在的源行号(如果可用)。
message%(message)s记入日志的消息,即 msg % args 的结果。 这是在发起调用 Formatter.format() 时设置的。
module%(module)s模块 (filename 的名称部分)。
msecs%(msecs)dLogRecord 被创建的时间的毫秒部分。
msg此属性不需要用户进行格式化。在原始日志记录调用中传入的格式字符串。 与 args 合并以产生 message,或是一个任意对象 (参见 使用任意对象作为消息)。
name%(name)s用于记录调用的日志记录器名称。
pathname%(pathname)s发出日志记录调用的源文件的完整路径名(如果可用)。
process%(process)d进程ID(如果可用)
processName%(processName)s进程名(如果可用)
relativeCreated%(relativeCreated)d以毫秒数表示的 LogRecord 被创建的时间,即相对于 logging 模块被加载时间的差值。
stack_info此属性不需要用户进行格式化。当前线程中从堆栈底部起向上直到包括日志记录调用并引发创建当前记录堆栈帧创建的堆栈帧信息(如果可用)。
thread%(thread)d线程ID(如果可用)
threadName%(threadName)s线程名(如果可用)

4、记录日志到日志文件

logging默认是显示在控制台,在真实生产环境肯定时需要将日志记录到日志文件的。logging也是可以通过配置很方便的将日志记录到日志文件。

LOG_FORMAT = "%(asctime)s - %(levelname)s %(name)s %(filename)s [line:%(lineno)d] - %(message)s"
logging.basicConfig(filename='log.log',format=LOG_FORMAT,level=logging.DEBUG)

输出到日志文件中
输出到日志文件
输出到日志文件效果
将日志输出到日志文件,如果是日志量非常大,在实际生产环境经常碰到的是要对日志文件进行分隔,根据日志文件的大小或日期来分割生成多个日志文件。
这里介绍通过日志文件大小分割和通过日期来分割日志文件。

1.通过日志文件大小分割

RotatingFileHandler
日志记录到文件中,且支持指定日志文件大小,备份文件数量

logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False)

maxBytes:日志文件大小,单位为字节
backupCount:备份文件数量

LOG_FORMAT = "%(asctime)s - %(levelname)s %(name)s %(filename)s [line:%(lineno)d] - %(message)s"
rfh=logging.handlers.RotatingFileHandler(filename='log.log',encoding='UTF-8', maxBytes=1024, backupCount=2)
logging.basicConfig(format=LOG_FORMAT,level=logging.DEBUG,handlers=[rfh])

这里设置的是当文件超过1024bytes就会对文件进行分割,备份文件数量为2,得到log.log.1、log.log.2,当log.log.2写满时又回循环写到log.log中。
根据日志文件大小循环切割生成日志文件

2.通过日期来分割日志文件

TimedRotatingFileHandler
日志记录到文件中,支持按时间间隔来更新日志

logging.handlers.TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None)

指定的文件会被打开并用作日志记录的流。 对于轮换操作它还会设置文件名前缀。 轮换的发生是基于 when 和 interval 的积。
你可以使用 when 来指定 interval 的类型。 可能的值列表如下。 请注意它们不是大小写敏感的。

间隔类型如果/如何使用 atTime
‘S’忽略
‘M’分钟忽略
‘H’小时忽略
‘D’忽略
‘W0’-‘W6’工作日(0=星期一)用于计算初始轮换时间
‘midnight’如果未指定 atTime 则在午夜执行轮换,否则将使用 atTime。用于计算初始轮换时间
import logging
from datetime import time
from logging.handlers import RotatingFileHandler

LOG_FORMAT = "%(asctime)s - %(levelname)s %(name)s %(filename)s [line:%(lineno)d] - %(message)s"
tfh=logging.handlers.TimedRotatingFileHandler('tfh_log.log', when='S', interval=1.5, backupCount=2, encoding='UTF-8', delay=False, utc=False, atTime=time)
rfh=logging.handlers.RotatingFileHandler(filename='log.log',encoding='UTF-8', maxBytes=1024, backupCount=2)
logging.basicConfig(format=LOG_FORMAT,level=logging.DEBUG,handlers=[rfh,tfh])

为了演示方便,这里when=‘S’, interval=1.5 即1.5秒循环生成一个日志文件。在实际生产环境一般根据日志量的大小,可以配置成每天生成一个日志文件。
通过日期来分割日志文件

5、既生成日志文件又在控制台打印日志

有时候为了调试方便,还是想在控制台打印日志。能不能既生成日志文件又在控制台打印日志呢?通过配置logging的StreamHandler也是可以做到的。
StreamHandler
StreamHandler 类位于核心 logging 包,它可将日志记录输出发送到数据流例如 sys.stdout, sys.stderr 或任何文件类对象(或者更精确地说,任何支持 write() 和 flush() 方法的对象)。

sh=logging.StreamHandler()
logging.basicConfig(format=LOG_FORMAT,level=logging.DEBUG,handlers=[rfh,tfh,sh])

既生成日志文件又在控制台打印日志

6、多个模块中记录日志

在实际项目使用过程中,一个好的实践是将日志配置的模块封装好成为一个通用的日志模块组件,可以给项目中所有的模块使用。
这里我们将配置好的日志logging从main.py中抽出来形成一个logutils.py的通用模块,其他模块就可以使用了
logutils.py

import logging
from datetime import time
from logging.handlers import RotatingFileHandler

LOG_FORMAT = "%(asctime)s - %(levelname)s %(name)s %(filename)s [line:%(lineno)d] - %(message)s"
tfh=logging.handlers.TimedRotatingFileHandler('tfh_log.log', when='S', interval=1.5, backupCount=2, encoding='UTF-8', delay=False, utc=False, atTime=time)
rfh=logging.handlers.RotatingFileHandler(filename='log.log',encoding='UTF-8', maxBytes=1024, backupCount=2)
sh=logging.StreamHandler()
logging.basicConfig(format=LOG_FORMAT,level=logging.DEBUG,handlers=[rfh,tfh,sh])

公共日志模块

如在othermodule.py中使用

from logutils import logging

class TestModule():

    def print_log(self):
        logging.info('this is TestModule.print_log() info')

    @staticmethod
    def print_log_staic():
        logging.info('this is TestModule.print_log_staic info')

在main.py中使用

from logutils import logging
from othermodule import TestModule

def print_hi(name):
    logging.debug('this is print_hi debug')
    logging.info('this is print_hi info')
    logging.warning('this is print_hi warning')
    logging.error('this is print_hi error')
    logging.critical('this is print_hi critical')
    print(f'Hi print_hi, {name}')


if __name__ == '__main__':
    print_hi('XieJava')
    TestModule.print_log_staic() #类方法中打印日志
    testModule=TestModule()
    testModule.print_log()  #实例方法中打印日志

其他模块中显示日志

项目工程中所有的模块只要通过 from logutils import logging 引入logging就可以使用配置好的logging记录日志了。

7、每个不同的模块使用不同的日志记录器记录日志

现在我们在所有的模块中都是用的默认的root记录器来记录的日志,实际上也可以让每个不同的模块使用不同的日志记录器记录日志。
日志事件信息在 LogRecord 实例中的记录器、处理器、过滤器和格式器之间传递。
通过调用 Logger 类(以下称为 loggers , 记录器)的实例来执行日志记录。
在命名记录器时使用的一个好习惯是在每个使用日志记录的模块中使用模块级记录器,命名如下:

logger = logging.getLogger(__name__)

这意味着记录器名称跟踪包或模块的层次结构,并且直观地从记录器名称显示记录事件的位置。
记录器层次结构的根称为根记录器。 这是函数 debug() 、 info() 、 warning() 、 error() 和 critical() 使用的记录器,它们就是调用了根记录器的同名方法。 函数和方法具有相同的签名。 根记录器的名称在输出中打印为 ‘root’ 。
实际上我们只要通过 logger = logging.getLogger(__name__) 给每个模块定义一个记录器就可以了。
main.py

from logutils import logging
from othermodule import TestModule
logger=logging.getLogger(__name__)  #定义模块日志记录器

def print_hi(name):
    logger.debug('this is print_hi debug')
    logger.info('this is print_hi info')
    logger.warning('this is print_hi warning')
    logger.error('this is print_hi error')
    logger.critical('this is print_hi critical')
    print(f'Hi print_hi, {name}')


if __name__ == '__main__':
    print_hi('XieJava')
    TestModule.print_log_staic() #类方法中打印日志
    testModule=TestModule()
    testModule.print_log()  #实例方法中打印日志

othermodule.py

from logutils import logging
logger=logging.getLogger(__name__) #定义模块日志记录器

class TestModule():

    def print_log(self):
        logger.info('this is TestModule.print_log() info')

    @staticmethod
    def print_log_staic():
        logger.info('this is TestModule.print_log_staic info')

每个不同的模块使用不同的日志记录器记录日志
可以看到模块日志记录器打印出来的日志中模块名不再是默认的root,而是各自的模块名。

8、通过配置文件配置日志记录器

在实际项目应用的过程中,通常通过配置文件来配置日志记录器的各种配置,这样的好处就是改变日志记录的配置不需要修改代码,直接修改配置文件就可以了。
接下来介绍如何通过配置文件配置logging日志记录器
新建 logging.conf 配置文件,通过如下配置将前面代码中的处理器,日志记录器,通过配置文件的方式配置好。
logging.conf

[loggers]
keys=root,logger01

[logger_root]
level=DEBUG
handlers=sh

[logger_logger01]
level=DEBUG
handlers=sh,tfh,rfh
qualname=logger01
propagate=0

[handlers]
keys=sh,tfh,rfh

[handler_sh]
class=StreamHandler
level=DEBUG
formatter=form01
args=(sys.stderr,)

[handler_tfh]
class=handlers.TimedRotatingFileHandler
level=DEBUG
formatter=form01
args=('tfh_log.log','S',1.5,2,)

[handler_rfh]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=form01
args=('log.log','a',1024,2)

[formatters]
keys=form01

[formatter_form01]
format=%(asctime)s - %(levelname)s %(name)s %(filename)s [line:%(lineno)d] - %(message)s

在logutils.py中应用配置文件

import logging.config
logging.config.fileConfig("logging.conf")

logutils.py中的代码就异常简单了,应为原来通过代码实现的配置,都写到了logging.conf配置文件中了。
配置文件实现了日志代码中的功能

这里要注意的是,在应用日志记录器的时候,需要引用配置文件中配置的记录器,如配置文件中配置了root和logger01,在应用的时候可以引用这两个记录器,当然也可以在配置文件中配置更多的记录器。
在mian.py和othermodule.py中应用日志记录器的时候,需要注意记录器用要配置文件中定义的记录器,这里是logger01。

logger=logging.getLogger('logger01')  #定义模块日志记录器

定义模块日志记录器

8、日志中中文显示

当日志信息中有中文的时候,在控制台输出会自动的转码,但有时在文件输出的时候会出现乱码。
中文信息控制台输出没有问题

控制台输出中文,但日志文件中是乱码。
日志文件中是乱码

对照python官方说明文档设置编码,设置处理器的编码为UTF-8

[handler_tfh]
class=handlers.TimedRotatingFileHandler
level=DEBUG
formatter=form01
args=('tfh_log.log','S',1.5,2,'UTF-8')

[handler_rfh]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=form01
args=('log.log','a',1024,2,'UTF-8')

配置文件的参数
现在重新执行main.py,可以看到日志文件中可以正常显示中文。
日志文件显示中文

至此,我们从一个简单的日志记录实战,一步一步实现了自定义日志格式、写日志文件、抽出公共日志模块让其他模块用、同时写多个日志文件并进行日志文件切割、通过配置文件实现日志参数的定义、解决日志中文显示问题。基本覆盖了真实应用场景日志的使用。


博客:http://xiejava.ishareread.com/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/2851.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

利用Cookie劫持+HTML注入进行钓鱼攻击

目录 HTML注入和cookie劫持: 发现漏洞 实际利用 来源 HTML注入和cookie劫持: HTML注入漏洞一般是由于在用户能够控制的输入点上,由于缺乏安全过滤,导致攻击者能将任意HTML代码注入网页。此类漏洞可能会引起许多后续攻击&#…

服务端测试知识汇总

目录 服务端测试思想 经济学⻆度 ⾦字塔模型 技术⻆度 HTTP协议 三次握⼿ HTTP完整请求 通信模式 URI信息 请求⽅法 请求状态码 请求/响应头 常⽤请求数据格式 COOKIE请求流程 SESSION请求流程 TOKEN请求流程 API测试维度 单接⼝测试 多个接⼝测试 …

基于原生Javascript的放大镜插件的设计和实现

概要 本文主要介绍一个前端放大镜插件的设计和实现,该插件可以将图片的指定区域进行局部放大,插件运行不依赖任何第三方库,可以在Chrome或Edge的常见版本上运行。 基本功能 可以将图片中指定区域扩大1.5倍放大镜可以跟随鼠标进行移动放大镜…

贪心算法(一)

一、概念 贪心算法的核心思想是,在处理一个大问题时,划分为多个局部并在每个局部选择最优解,并且认为在每个局部选择最优解,那么最后全局的问题得到的就是最优解。 贪心算法可以解决一些问题,但是不适用于所有问题&a…

蓝桥杯刷题冲刺 | 倒计时18天

作者:指针不指南吗 专栏:蓝桥杯倒计时冲刺 🐾马上就要蓝桥杯了,最后的这几天尤为重要,不可懈怠哦🐾 文章目录0.知识点1.乳草的入侵今天写 搜索题 0.知识点 DFS 设计步骤 确定该题目的状态(包括边…

MD5加密竟然不安全,应届生表示无法理解?

前言 近日公司的一个应届生问我,他做的一个毕业设计密码是MD5加密存储的,为什么密码我帮他调试的时候,我能猜出来明文是什么? 第六感,是后端研发的第六感! 正文 示例,有个系统,前…

Java每日一练(20230324)

目录 1. 链表插入排序 🌟🌟 2. 最接近的三数之和 🌟🌟 3. 寻找旋转排序数组中的最小值 🌟🌟 🌟 每日一练刷题专栏 🌟 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一…

手写一个Promise

Promise Promise是一个对象,用于解决异步变成的问题,由传统的异步回调为服务端立即调用优化为使用者者掌握回调主动权。 比如传统的JSONP,如下,在请求路由里添加回调函数,由接收请求的一方来调用请求,使用…

maya python 中的maya.cmds 与maya.mel模块的区别笔记

作为一个python的小白,之前多从事mel程序开发,因为python变化太快,不如maya内置mel语言稳定,所以一直懒于python学习,后来偶尔使用中发现,其实python作为maya mel的封装,与内置的mel还是有很大差…

新闻文本分类任务:使用Transformer实现

❤️觉得内容不错的话,欢迎点赞收藏加关注😊😊😊,后续会继续输入更多优质内容❤️👉有问题欢迎大家加关注私戳或者评论(包括但不限于NLP算法相关,linux学习相关,读研读博…

A.机器学习入门算法(六)基于天气数据集的XGBoost分类预测

1.机器学习算法(六)基于天气数据集的XGBoost分类预测 本项目链接:https://www.heywhale.com/home/column/64141d6b1c8c8b518ba97dcc 1.1 XGBoost的介绍与应用 XGBoost是2016年由华盛顿大学陈天奇老师带领开发的一个可扩展机器学习系统。严…

用嘴写代码?继ChatGPT和NewBing之后,微软又开始整活了,Github Copilot X!

用嘴写代码?继ChatGPT和NewBing之后,微软又开始整活了,Github Copilot X! AI盛行的时代来临了,在这段时间,除了爆火的GPT3.5后,OpenAI发布了GPT4版本,同时微软也在Bing上开始加入了A…

第十四届蓝桥杯三月真题刷题训练——第 21 天

目录 第 1 题:灭鼠先锋 问题描述 运行限制 代码: 思路: 第 2 题:小蓝与钥匙 问题描述 答案提交 运行限制 代码: 思路 : 第 3 题:李白打酒加强版 第 4 题:机房 第 1 题&#xff1…

【尝鲜版】ChatGPT插件开发指南

3月23日,OpenAI官方发布了一则公告,宣告ChatGPT已经支持了插件功能,现在处于内测阶段。插件的意义不仅仅在于功能的扩展,它直接让ChatGTP拥有了联网的能力!简直是猛兽出笼、蛟龙出海,要让ChatGPT大杀特杀啊…

无需公网IP,远程连接SQL Server数据库【内网穿透】

文章目录1.前言2.本地安装和设置SQL Server2.1 SQL Server下载2.2 SQL Server本地连接测试2.3 Cpolar内网穿透的下载和安装2.3 Cpolar内网穿透的注册3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置4.公网访问测试5.结语1.前言 数据库的重要性相信大家都有所了解&#xf…

【Unityc#专题篇】之c#基础篇

👨‍💻个人主页:元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 收录于专栏:uni…

ASO优化之应用商店中的A/B测试——改良版

A/B 测试在应用营销中发挥着重要作用,是根据实际用户行为不断改进应用商店列表的最佳方式。 A/B 测试包括将相同的受众分成相同大小的两组实验。第一组用户能够看到测试的A变体和原始资产,第二组用户能够看到带有要测试资产的B变体。运行一段时间后&…

菜鸟刷题Day5

⭐作者:别动我的饭 ⭐专栏:菜鸟刷题 ⭐标语:悟已往之不谏,知来者之可追 一.一维数组的动态和:1480. 一维数组的动态和 - 力扣(LeetCode) 描述 给你一个数组 nums 。数组「动态和」的计算公式…

FPGA打砖块游戏设计(有上板照片)VHDL

这是一款经典打砖块游戏,我们的努力让它更精致更好玩,我们将它取名为打砖块游戏(Flyball),以下是该系统的一些基本功能:  画面简约而经典,色彩绚丽而活泼,动画流畅  玩家顺序挑战3个不同难度的级别,趣味十足  计分功能,卡通字母数字  4条生命值,由生命条显示…

React 入门(超详细)

目录前言:一、React 简介1. 什么是 React2. React 的特点3. React 高效的原因4. React 官网5. React的主要原理6. Facebook为什么要建造React?二、React 的基本使用1. 基础代码2. 效果3. 相关 js 库4. 创建虚拟DOM的两种方式5. 虚拟DOM与真实DOM6. 虚拟DOM与真实DO…
最新文章