[python] 基于Gradio可视化部署机器学习应用
Gradio是一个开源的Python库,用于构建机器学习和数据科学演示应用。有了Gradio,你可以围绕你的机器学习模型或数据科学工作流程快速创建一个简单漂亮的用户界面。Gradio适用于以下情况:
为客户/合作者/用户/学生演示你的机器学习模型。
通过自动共享链接快速部署你的模型,并获得对模型性能的反馈。
在开发过程中使用内置的操作和解释工具交互式地调试你的模型。
Gradio官方仓库为:Gradio官方仓库。Gradio官方文档见:Gradio官方文档。如果在使用中遇到问题,多查查官方文档。参考Gradio-demo可以获得不同的Gradio应用示例。
Gradio需要Python3.7及以上版本才能运行,安装指令如下:
pip install gradio
文章目录
1 基础使用
Gradio只需要几行代码就可以建立一个展示应用,一些基础应用搭建示例将在本章进行介绍。
1.1 快速入门
1.1.1 Interface构建应用
通过Interface类可以快速构建应用。
简单应用
下面的示例建立了对输入文本进行简单处理的应用。
import gradio as gr
# 输入文本处理程序
def greet(name):
return "Hello " + name + "!"
# 接口创建函数
# fn设置处理函数,inputs设置输入接口组件,outputs设置输出接口组件
# fn,inputs,outputs都是必填函数
demo = gr.Interface(fn=greet, inputs="text", outputs="text")
demo.launch()
运行程序后,打开http://localhost:7860即可看到网页效果。左边是文本输入框,右边是结果展示框。Clear按钮用于重置网页状态,Submit按钮用于执行处理程序,Flag按钮用于保存结果到本地。
在上面的示例中,我们看到了一个简单的基于文本的函数,但该函数可以是任何函数,从音乐生成器到税务计算器,再到机器学习模型的预测函数。核心的Interface类被初始化,有三个必要的参数。
- fn: 围绕用户界面的函数
- 输入:用于输入的组件(例如:“text”、"image)。
- 输出:用于输出的组件(例如:“text”、"image)。
自定义输入组件
以下代码展示了如何自定义输入组件。
import gradio as gr
def greet(name):
return "Hello " + name + "!"
demo = gr.Interface(
fn=greet,
# 自定义输入框
# 具体设置方法查看官方文档
inputs=gr.Textbox(lines=3, placeholder="Name Here...",label="my input"),
outputs="text",
)
demo.launch()
多输入和多输出组件设置
对于复杂程序,输入列表中的每个组件按顺序对应于函数的一个参数。输出列表中的每个组件按顺序排列对应于函数返回的一个值。
import gradio as gr
# 该函数有3个输入参数和2个输出参数
def greet(name, is_morning, temperature):
salutation = "Good morning" if is_morning else "Good evening"
greeting = f"{salutation} {name}. It is {temperature} degrees today"
celsius = (temperature - 32) * 5 / 9
return greeting, round(celsius, 2)
demo = gr.Interface(
fn=greet,
# 按照处理程序设置输入组件
inputs=["text", "checkbox", gr.Slider(0, 100)],
# 按照处理程序设置输出组件
outputs=["text", "number"],
)
demo.launch()
代码运行处理结果如下图所示:
图像组件
Gradio支持许多类型的组件,如image、dataframe、video。使用示例如下:
import numpy as np
import gradio as gr
def sepia(input_img):
# 处理图像
sepia_filter = np.array([
[0.393, 0.769, 0.189],
[0.349, 0.686, 0.168],
[0.272, 0.534, 0.131]
])
sepia_img = input_img.dot(sepia_filter.T)
sepia_img /= sepia_img.max()
return sepia_img
# shape设置输入图像大小
demo = gr.Interface(sepia, gr.Image(shape=(200, 200)), "image")
demo.launch()
当使用Image组件作为输入时,函数将收到一个维度为(w,h,3)的numpy数组,按照RGB的通道顺序排列。要注意的是,我们的输入图像组件带有一个编辑按钮,可以对图像进行裁剪和放大。以这种方式处理图像可以帮助揭示机器学习模型中的偏差或隐藏的缺陷。此外对于输入组件有个shape参数,指的设置输入图像大小。但是处理方式是保持长宽比的情况下,将图像最短边缩放为指定长度,然后按照中心裁剪方式裁剪最长边到指定长度。当图像不大的情况,一种更好的方式是不设置shape,这样直接传入原图。输入组件Image也可以设置输入类型type,比如type=filepath设置传入处理图像的路径。具体可以查看官方文档,文档写的很清楚。
代码运行处理结果如下图所示:
1.1.2 Blocks构建应用
相比Interface,Blocks提供了一个低级别的API,用于设计具有更灵活布局和数据流的网络应用。Blocks允许控制组件在页面上出现的位置,处理复杂的数据流(例如,输出可以作为其他函数的输入),并根据用户交互更新组件的属性可见性。
简单应用
import gradio as gr
def greet(name):
return "Hello " + name + "!"
with gr.Blocks() as demo:
# 设置输入组件
name = gr.Textbox(label="Name")
# 设置输出组件
output = gr.Textbox(label="Output Box")
# 设置按钮
greet_btn = gr.Button("Greet")
# 设置按钮点击事件
greet_btn.click(fn=greet, inputs=name, outputs=output)
demo.launch()
Blocks方式需要with语句添加组件,如果不设置布局方式,那么组件将按照创建的顺序垂直出现在应用程序中,运行界面如下图示:
多模块应用
该例子介绍了一个多模块应用,其中各个模块的使用可以查看官方文档。
import numpy as np
import gradio as gr
def flip_text(x):
return x[::-1]
def flip_image(x):
return np.fliplr(x)
with gr.Blocks() as demo:
# 用markdown语法编辑输出一段话
gr.Markdown("Flip text or image files using this demo.")
# 设置tab选项卡
with gr.Tab("Flip Text"):
# Blocks特有组件,设置所有子组件按垂直排列
# 垂直排列是默认情况,不加也没关系
with gr.Column():
text_input = gr.Textbox()
text_output = gr.Textbox()
text_button = gr.Button("Flip")
with gr.Tab("Flip Image"):
# Blocks特有组件,设置所有子组件按水平排列
with gr.Row():
image_input = gr.Image()
image_output = gr.Image()
image_button = gr.Button("Flip")
# 设置折叠内容
with gr.Accordion("Open for More!"):
gr.Markdown("Look at me...")
text_button.click(flip_text, inputs=text_input, outputs=text_output)
image_button.click(flip_image, inputs=image_input, outputs=image_output)
demo.launch()
1.2 关键特性
示例输入
下面的代码提供了创建一个简单计算器的模板。该程序运行后,点击flag按钮会在运行目录下指定flagged/logs.csv保存输入记录。保存目录的修改和相关信息设置查看官方文档flagging项。
import gradio as gr
# 一个简单计算器
def calculator(num1, operation, num2):
if operation == "add":
return num1 + num2
elif operation == "subtract":
return num1 - num2
elif operation == "multiply":
return num1 * num2
elif operation == "divide":
if num2 == 0:
# 设置报错弹窗
raise gr.Error("Cannot divide by zero!")
return num1 / num2
demo = gr.Interface(
calculator,
# 设置输入
[
"number",
gr.Radio(["add", "subtract", "multiply", "divide"]),
"number"
],
# 设置输出
"number",
# 设置输入参数示例
examples=[
[5, "add", 3],
[4, "divide", 2],
[-4, "multiply", 2.5],
[0, "subtract", 1.2],
],
# 设置网页标题
title="Toy Calculator",
# 左上角的描述文字
description="Here's a sample toy calculator. Enjoy!",
# 左下角的文字
article = "Check out the examples",
)
demo.launch()
样式和排队
在Gradio官方文档,搜索不同的组件加.style(如image.style),可以获取该组件的样式参数设置样例。例如image组件的设置如下:
img = gr.Image("lion.jpg").style(height='24', rounded=False)
如果函数推理时间较长,比如目标检测;或者应用程序处理流量过大,则需要使用queue方法进行排队。queue方法使用websockets,可以防止网络超时。使用方式如下:
demo = gr.Interface(...).queue()
demo.launch()
# 或
with gr.Blocks() as demo:
#...
demo.queue()
demo.launch()
生成器
在某些情况下,你可能想显示一连串的输出,而不是单一的输出。例如,你可能有一个图像生成模型,如果你想显示在每个步骤中生成的图像,从而得到最终的图像。在这种情况下,你可以向Gradio提供一个生成器函数,而不是一个常规函数。下面是一个生成器的例子,每隔1秒返回1张图片。
import gradio as gr
import numpy as np
import time
# 生成steps张图片,每隔1秒钟返回
def fake_diffusion(steps):
for _ in range(steps):
time.sleep(1)
image = np.random.randint(255, size=(300, 600, 3))
yield image
demo = gr.Interface(fake_diffusion,
# 设置滑窗,最小值为1,最大值为10,初始值为3,每次改动增减1位
inputs=gr.Slider(1, 10, value=3, step=1),
outputs="image")
# 生成器必须要queue函数
demo.queue()
demo.launch()
1.3 应用分享
互联网分享
如果运行环境能够连接互联网,在launch函数中设置share参数为True,那么运行程序后。Gradio的服务器会提供XXXXX.gradio.app地址。通过其他设备,比如手机或者笔记本电脑,都可以访问该应用。这种方式下该链接只是本地服务器的代理,不会存储通过本地应用程序发送的任何数据。这个链接在有效期内是免费的,好处就是不需要自己搭建服务器,坏处就是太慢了,毕竟数据经过别人的服务器。
demo.launch(share=True)
Hugging Face托管
如果想长期免费分享应用,可以在Hugging Face中托管应用或者其他的一些方式,详细使用见分享Gradio应用。Hugging Face提供了多个基于Gradio搭建应用的demo,详情使用见Using Hugging Face Integrations。
局域网分享
通过设置server_name=‘0.0.0.0’(表示使用本机ip),server_port(可不改,默认值是7860)。那么可以通过本机ip:端口号在局域网内分享应用。
# show_error为True表示在控制台显示错误信息。
demo.launch(server_name='0.0.0.0', server_port=8080, show_error=True)
密码验证
在首次打开网页前,可以设置账户密码。比如auth参数为(账户,密码)的元组数据。这种模式下不能够使用queue函数。
demo.launch(auth=("admin", "pass1234"))
如果想设置更为复杂的账户密码和密码提示,可以通过函数设置校验规则。
# 账户和密码相同就可以通过
def same_auth(username, password):
return username == password
demo.launch(auth=same_auth,auth_message="username and password must be the same")
2 进阶使用
2.1 interface进阶使用
2.1.1 interface状态
全局变量
全局变量的好处就是在调用函数后仍然能够保存,例如在机器学习中通过全局变量从外部加载一个大型模型,并在函数内部使用它,以便每次函数调用都不需要重新加载模型。下面就展示了全局变量使用的好处。
import gradio as gr
scores = []
def track_score(score):
scores.append(score)
# 返回分数top3
top_scores = sorted(scores, reverse=True)[:3]
return top_scores
demo = gr.Interface(
track_score,
gr.Number(label="Score"),
gr.JSON(label="Top Scores")
)
demo.launch()
会话状态
Gradio支持的另一种数据持久性是会话状态,数据在一个页面会话中的多次提交中持久存在。然而,数据不会在你模型的不同用户之间共享。会话状态的典型例子就是聊天机器人,你想访问用户之前提交的信息,但你不能将聊天记录存储在一个全局变量中,因为那样的话,聊天记录会在不同的用户之间乱成一团。注意该状态会在每个页面内的提交中持续存在,但如果您在另一个标签页中加载该演示(或刷新页面),该演示将不会共享聊天历史。
要在会话状态下存储数据,你需要做三件事。
- 在你的函数中传入一个额外的参数,它代表界面的状态。
- 在函数的最后,将状态的更新值作为一个额外的返回值返回。
- 在添加输入和输出时添加state组件。
import random
import gradio as gr
def chat(message, history):
history = history or []
message = message.lower()
if message.startswith("how many"):
response = random.randint(1, 10)
elif message.startswith("how"):
response = random.choice(["Great", "Good", "Okay", "Bad"])
elif message.startswith("where"):
response = random.choice(["Here", "There", "Somewhere"])
else:
response = "I don't know"
history.append((message, response))
return history, history
# 设置一个对话窗
chatbot = gr.Chatbot().style(color_map=("green", "pink"))
demo = gr.Interface(
chat,
# 添加state组件
["text", "state"],
[chatbot, "state"],
# 设置没有保存数据的按钮
allow_flagging="never",
)
demo.launch()
2.1.2 interface交互
实时变化
在Interface中设置live=True,则输出会跟随输入实时变化。这个时候界面不会有submit按钮,因为不需要手动提交输入。
import gradio as gr
def calculator(num1, operation, num2):
if operation == "add":
return num1 + num2
elif operation == "subtract":
return num1 - num2
elif operation == "multiply":
return num1 * num2
elif operation == "divide":
return num1 / num2
demo = gr.Interface(
calculator,
[
"number",
gr.Radio(["add", "subtract", "multiply", "divide"]),
"number"
],
"number",
live=True,
)
demo.launch()
流模式
在许多情形下,我们的输入是实时视频流或者音频流,那么意味这数据不停地发送到后端,这是可以采用streaming模式处理数据。
import gradio as gr
import numpy as np
def flip(im):
return np.flipud(im)
demo = gr.Interface(
flip,
gr.Image(source="webcam", streaming=True),
"image",
live=True
)
demo.launch()
2.2 Blocks进阶使用
2.2.1 Blocks事件
可交互设置
任何输入的组件内容都是可编辑的,而输出组件默认是不能编辑的。如果想要使得输出组件内容可编辑,设置interactive=True即可。
import gradio as gr
def greet(name):
return "Hello " + name + "!"
with gr.Blocks() as demo:
name = gr.Textbox(label="Name")
# 不可交互
# output = gr.Textbox(label="Output Box")
# 可交互
output = gr.Textbox(label="Output", interactive=True)
greet_btn = gr.Button("Greet")
greet_btn.click(fn=greet, inputs=name, outputs=output)
demo.launch()
事件设置
我们可以为不同的组件设置不同事件,如为输入组件添加change事件。可以进一步查看官方文档,看看组件还有哪些事件。
import gradio as gr
def welcome(name):
return f"Welcome to Gradio, {name}!"
with gr.Blocks() as demo:
gr.Markdown(
"""
# Hello World!
Start typing below to see the output.
""")
inp = gr.Textbox(placeholder="What is your name?")
out = gr.Textbox()
# 设置change事件
inp.change(fn = welcome, inputs = inp, outputs = out)
demo.launch()
多个数据流
如果想处理多个数据流,只要设置相应的输入输出组件即可。
import gradio as gr
def increase(num):
return num + 1
with gr.Blocks() as demo:
a = gr.Number(label="a")
b = gr.Number(label="b")
# 要想b>a,则使得b = a+1
atob = gr.Button("b > a")
atob.click(increase, a, b)
# 要想a>b,则使得a = b+1
btoa = gr.Button("a > b")
btoa.click(increase, b, a)
demo.launch()
多输出值处理
下面的例子展示了输出多个值时,以列表形式表现的处理方式。
import gradio as gr
with gr.Blocks() as demo:
food_box = gr.Number(value=10, label="Food Count")
status_box = gr.Textbox()
def eat(food):
if food > 0:
return food - 1, "full"
else:
return 0, "hungry"
gr.Button("EAT").click(
fn=eat,
inputs=food_box,
# 根据返回值改变输入组件和输出组件
outputs=[food_box, status_box]
)
demo.launch()
下面的例子展示了输出多个值时,以字典形式表现的处理方式。
import gradio as gr
with gr.Blocks() as demo:
food_box = gr.Number(value=10, label="Food Count")
status_box = gr.Textbox()
def eat(food):
if food > 0:
return {food_box: food - 1, status_box: "full"}
else:
return {status_box: "hungry"}
gr.Button("EAT").click(
fn=eat,
inputs=food_box,
outputs=[food_box, status_box]
)
demo.launch()
组件配置修改
事件监听器函数的返回值通常是相应的输出组件的更新值。有时我们也想更新组件的配置,比如说可见性。在这种情况下,我们可以通过返回update函数更新组件的配置。
import gradio as gr
def change_textbox(choice):
# 根据不同输入对输出控件进行更新
if choice == "short":
return gr.update(lines=2, visible=True, value="Short story: ")
elif choice == "long":
return gr.update(lines=8, visible=True, value="Long story...")
else:
return gr.update(visible=False)
with gr.Blocks() as demo:
radio = gr.Radio(
["short", "long", "none"], label="Essay Length to Write?"
)
text = gr.Textbox(lines=2, interactive=True)
radio.change(fn=change_textbox, inputs=radio, outputs=text)
demo.launch()
2.2.2 Blocks布局
Blocks应用的是html中的flexbox模型布局,默认情况下组件垂直排列。
组件水平排列
使用Row函数会将组件按照水平排列,但是在Row函数块里面的组件都会保持同等高度。
import gradio as gr
with gr.Blocks() as demo:
with gr.Row():
img1 = gr.Image()
text1 = gr.Text()
btn1 = gr.Button("button")
demo.launch()
组件垂直排列与嵌套
组件通常是垂直排列,我们可以通过Row函数和Column函数生成不同复杂的布局。
import gradio as gr
with gr.Blocks() as demo:
with gr.Row():
text1 = gr.Textbox(label="t1")
slider2 = gr.Textbox(label="s2")
drop3 = gr.Dropdown(["a", "b", "c"], label="d3")
with gr.Row():
# scale与相邻列相比的相对宽度。例如,如果列A的比例为2,列B的比例为1,则A的宽度将是B的两倍。
# min_width设置最小宽度,防止列太窄
with gr.Column(scale=2, min_width=600):
text1 = gr.Textbox(label="prompt 1")
text2 = gr.Textbox(label="prompt 2")
inbtw = gr.Button("Between")
text4 = gr.Textbox(label="prompt 1")
text5 = gr.Textbox(label="prompt 2")
with gr.Column(scale=1, min_width=600):
img1 = gr.Image("test.jpg")
btn = gr.Button("Go")
demo.launch()
组件可视化
如下所示,我们可以通过visible和update函数构建更为复杂的应用。
import gradio as gr
with gr.Blocks() as demo:
# 出错提示框
error_box = gr.Textbox(label="Error", visible=False)
# 输入框
name_box = gr.Textbox(label="Name")
age_box = gr.Number(label="Age")
symptoms_box = gr.CheckboxGroup(["Cough", "Fever", "Runny Nose"])
submit_btn = gr.Button("Submit")
# 输出不可见
with gr.Column(visible=False) as output_col:
diagnosis_box = gr.Textbox(label="Diagnosis")
patient_summary_box = gr.Textbox(label="Patient Summary")
def submit(name, age, symptoms):
if len(name) == 0:
return {error_box: gr.update(value="Enter name", visible=True)}
if age < 0 or age > 200:
return {error_box: gr.update(value="Enter valid age", visible=True)}
return {
output_col: gr.update(visible=True),
diagnosis_box: "covid" if "Cough" in symptoms else "flu",
patient_summary_box: f"{name}, {age} y/o"
}
submit_btn.click(
submit,
[name_box, age_box, symptoms_box],
[error_box, diagnosis_box, patient_summary_box, output_col],
)
demo.launch()
组件渲染
在某些情况下,您可能希望在实际在UI中呈现组件之前定义组件。例如,您可能希望在相应的gr.Textbox输入上方显示使用gr.examples的示例部分。由于gr.Examples需要输入组件对象作为参数,因此您需要先定义输入组件,然后在定义gr.Exmples对象后再进行渲染。解决方法是在gr.Blocks()范围外定义gr.Textbox,并在UI中希望放置的任何位置使用组件的.render()方法。
import gradio as gr
input_textbox = gr.Textbox()
with gr.Blocks() as demo:
# 提供示例输入给input_textbox,示例输入以嵌套列表形式设置
gr.Examples(["hello", "bonjour", "merhaba"], input_textbox)
# render函数渲染input_textbox
input_textbox.render()
demo.launch()
2.2.3 样式修改
自定义css
要获得额外的样式功能,您可以设置行内css属性将任何样式给应用程序。如下所示。
import gradio as gr
# 修改blocks的背景颜色
with gr.Blocks(css=".gradio-container {background-color: red}") as demo:
box1 = gr.Textbox(value="Good Job")
box2 = gr.Textbox(value="Failure")
demo.launch()
元素选择
您可以向任何组件添加HTML元素。通过elem_id选择对应的css元素。
import gradio as gr
# 这里用的是id属性设置
with gr.Blocks(css="#warning {background-color: red}") as demo:
box1 = gr.Textbox(value="Good Job", elem_id="warning")
box2 = gr.Textbox(value="Failure")
box3 = gr.Textbox(value="None", elem_id="warning")
demo.launch()
3 参考
[python] 基于Gradio可视化部署机器学习应用的更多相关文章
- 【转帖】Python在大数据分析及机器学习中的兵器谱
Flask:Python系的轻量级Web框架. 1. 网页爬虫工具集 Scrapy 推荐大牛pluskid早年的一篇文章:<Scrapy 轻松定制网络爬虫> Beautiful Soup ...
- 【python3】基于scrapyd + scrapydweb 的可视化部署
一.部署组件概览 该部署方式适用于 scrapy项目.scrapy-redis的分布式爬虫项目 需要安装的组件有: 1.scrapyd 服务端 [运行打包后的爬虫代码](所有的爬虫机器都要安 ...
- 使用pmml实现跨平台部署机器学习模型
一.概述 对于由Python训练的机器学习模型,通常有pickle和pmml两种部署方式,pickle方式用于在python环境中的部署,pmml方式用于跨平台(如Java环境)的部署,本文叙述的 ...
- 使用pmml跨平台部署机器学习模型Demo——房价预测
基于房价数据,在python中训练得到一个线性回归的模型,在JavaWeb中加载模型完成房价预测的功能. 一. 训练.保存模型 工具:PyCharm-2017.Python-39.sklearn2 ...
- Python基于共现提取《釜山行》人物关系
Python基于共现提取<釜山行>人物关系 一.课程介绍 1. 内容简介 <釜山行>是一部丧尸灾难片,其人物少.关系简单,非常适合我们学习文本处理.这个项目将介绍共现在关系中的 ...
- 基于Docker的TensorFlow机器学习框架搭建和实例源码解读
概述:基于Docker的TensorFlow机器学习框架搭建和实例源码解读,TensorFlow作为最火热的机器学习框架之一,Docker是的容器,可以很好的结合起来,为机器学习或者科研人员提供便捷的 ...
- Python - matplotlib 数据可视化
在许多实际问题中,经常要对给出的数据进行可视化,便于观察. 今天专门针对Python中的数据可视化模块--matplotlib这块内容系统的整理,方便查找使用. 本文来自于对<利用python进 ...
- 利用Python,四步掌握机器学习
为了理解和应用机器学习技术,你需要学习 Python 或者 R.这两者都是与 C.Java.PHP 相类似的编程语言.但是,因为 Python 与 R 都比较年轻,而且更加“远离”CPU,所以它们显得 ...
- 记基于docker+gunicorn部署sanic项目遇到的很多很多坑
前言: 最近有个项目需要上线,是python中sanic网络异步框架写的,并且要求使用docker+nginx来部署项目实现负载均衡,于是乎百度了sanic项目部署,基本上都是基于docker+gun ...
随机推荐
- [C#]SourceGenerator实战: 对任意对象使用await吧!!!
[C#]SourceGenerator实战: 对任意对象使用await吧!!! 前言 本文记录一次简单的 SourceGenerator 实战,最终实现可以在代码中 await 任意类型对象,仅供娱乐 ...
- 齐博x1客服系统显示客户在哪个页面
如下图所示,要想实现下面的效果,即显示客户给你发消息时,当时处于哪个商品页面.这样方便跟客户针对此商品进行交流. 你的模板如果使用了碎片的话,就可以添加下面的代码index_style/default ...
- 齐博x1背景图如何设置标签
背景图非常特殊,由于不能点击,所以他不能直接添加标签,需要添加一个辅助标签,比如类似下面的代码 {qb:hy name="xxa001" type="image" ...
- JAVA语言学习-面向对象(1)
类与对象 类 类是JAVA语言中重要的复合型数据类型().类的实现包括两个部分:成员变量和成员方法("方法"可以看作是C语言中的函数) 类的声明 要使用类,首先得对其声明.声明一个 ...
- .NET性能优化-是时候换个序列化协议了
计算机单机性能一直受到摩尔定律的约束,随着移动互联网的兴趣,单机性能不足的瓶颈越来越明显,制约着整个行业的发展.不过我们虽然不能无止境的纵向扩容系统,但是我们可以分布式.横向的扩容系统,这听起来非常的 ...
- Git配置和使用?Git你真的会用么?Git与SVN的主要区别
1.Git环境配置 在学习Git之前,首先要知道什么是版本控制 1.1 版本控制:版本迭代.新的版本!版本管理器 版本控制是开发过程中用于管理我们的文件.目录或工程内容的修改内容,查看修改历史记 ...
- JavaScript常用工具函数
检测数据是不是除了symbol外的原始数据 function isStatic(value) { return ( typeof value === 'string' || typeof value ...
- 详解从浏览器地址栏输入URL到页面显示的步骤
版本1(基础版本) 步骤1:浏览器根据请求的 URL 交给 DNS 域名解析,找到真实 IP ,向服务器发起请求: 步骤2:服务器交给后台处理完成后返回数据,浏览器接收⽂件( HTML.JS.CSS ...
- 本地GoLand编辑与调试远端服务器上的代码
Goland是专为Go开发人员构建的跨平台IDE,功能非常强大,拥有强大的代码洞察力,帮助所有Go开发人员即时错误检测和修复建议,快速和安全的重构,一步撤销,智能代码完成,死代码检测和文档提示,让您创 ...
- 【云原生 · Kubernetes】部署博客系统
[云原生 · Kubernetes]Kubernetes运维 接着上次的内容,后续来了! (1)配置NFS服务 master节点安装NFS与RPC服务: # yum install -y nfs-ut ...