Python 基础入门课:面向 Agent 开发的第一课
这节课将教会你什么?
- 基本变量与函数
- 类(class)、模块(module)、包(package)
- 虚拟环境 venv 和依赖安装工具 pip
- 文件读写与 pathlib
- dataclass
- typing
- Enum
- 异常处理 try/except
- 日志系统 logging
- JSON 处理
- 命令行参数
- 一个最小的项目结构
最后我们会做一个小练习:写一个“最小 Agent 配置与日志程序”。
1. 先建立正确目标
你现在学 Python,不是为了刷算法题,而是为了后面能写:
- agent 主循环
- tool(工具)封装
- 配置系统
- 日志系统
- 状态存储
所以你要优先掌握的是“工程里常用的 Python”。
这里先解释两个词:
- Agent
可以简单理解成“会思考、会调用工具、会执行任务的程序” - Tool
指 Agent 可以调用的外部能力,比如搜索、读文件、执行代码、打开网页
2. 变量、基本类型、函数
2.1 变量
变量可以理解成“给一段数据起名字”。
name = "research-agent"
max_steps = 10
temperature = 0.2
enabled = True
上面这些变量对应了几种最常见的 Python 类型:
- str:字符串(string),比如 "hello"
- int:整数(integer),比如 10
- float:小数,比如 0.2
- bool:布尔值,只有 True(真)和 False(假)
- list:列表,可以放多个值
- dict:字典,用“键:值”的方式存数据
例如:
tools = ["search", "read_file", "write_file"]
config = {
"model": "gpt-5",
"max_steps": 10,
"debug": True,
}
这里:
- tools 是一个列表
- config 是一个字典
- 字典非常适合存配置数据
2.2 函数
函数就是“把一段会重复使用的逻辑包装起来”。
def greet(name: str) –> str:
return f"Hello, {name}"
先解释这段代码:
- def:定义函数
- greet:函数名
- name: str:参数 name,并标注它应该是字符串
- -> str:表示这个函数返回字符串
- return:把结果返回出去
调用:
message = greet("agent")
print(message)
2.3 默认参数
默认参数表示:如果调用函数时没有传这个值,就自动使用默认值。
def run_task(task: str, max_steps: int = 5) –> str:
return f"Running task={task}, max_steps={max_steps}"
这里 max_steps 默认是 5。
2.4 练习
def build_prompt(task: str, role: str = "assistant") –> str:
return f"Role: {role}\\nTask: {task}"
你可以自己试着改成:
- role="researcher"
- task="summarize a paper"
3. 条件、循环
3.1 条件判断
条件判断让程序根据不同情况走不同分支。
def should_continue(step: int, max_steps: int) –> bool:
if step < max_steps:
return True
return False
解释:
- if:如果
- step < max_steps:判断当前步数是否还没到上限
- 返回 True 表示继续
- 返回 False 表示停止
3.2 for 循环
循环就是重复做某件事。
for i in range(3):
print("step", i)
解释:
- for:遍历
- range(3):生成 0, 1, 2
- 所以这段会打印三次
再看一个更常见的例子:
tools = ["search", "browser", "python"]
for tool in tools:
print(tool)
这里表示:把列表里的每个工具一个一个取出来。
3.3 while 循环
step = 0
while step < 3:
print("running", step)
step += 1
解释:
- while:只要条件成立就一直循环
- step += 1:每轮把 step 加 1
- 如果忘了加 1,循环可能永远不会结束
这个结构以后就是 agent loop(Agent 主循环)的雏形。
4. 类、模块、包
4.1 类是什么
类(class)适合表示“有状态的对象”,比如:
- Agent
- Tool
- Task
class SimpleAgent:
def __init__(self, name: str, max_steps: int):
self.name = name
self.max_steps = max_steps
def run(self, task: str) –> None:
print(f"[{self.name}] start task: {task}")
for step in range(self.max_steps):
print(f"step {step + 1}: thinking…")
解释:
- class SimpleAgent:定义一个类
- __init__:初始化方法,创建对象时自动调用
- self:表示“当前这个对象自己”
- self.name = name:把传进来的值保存到对象里
- run(…):这个类的一个方法
使用:
agent = SimpleAgent("demo-agent", 3)
agent.run("summarize a file")
4.2 模块是什么
模块(module)就是一个 .py 文件。
例如文件 utils.py:
def normalize_text(text: str) –> str:
return text.strip().lower()
另一个文件中导入:
from utils import normalize_text
这里的意思是:从 utils.py 中导入这个函数。
4.3 包是什么
包(package)可以理解成“装多个模块的文件夹”。
例如:
my_agent/
__init__.py
agent.py
tools.py
config.py
- agent.py 可以放 Agent 逻辑
- tools.py 可以放工具逻辑
- config.py 可以放配置逻辑
导入时可以写:
from my_agent.agent import SimpleAgent
5. venv / pip
这部分是 Python 工程的基本功。
5.1 什么是虚拟环境 venv
虚拟环境(virtual environment) 是一个独立的小 Python 环境。
它能让每个项目使用自己的依赖,不和别的项目冲突。
创建虚拟环境:
python -m venv .venv
解释:
- python -m venv .venv
- 表示用 Python 内置的 venv 模块,在当前目录创建一个叫 .venv 的虚拟环境
5.2 激活虚拟环境
Windows PowerShell:
.venv\\Scripts\\Activate.ps1
激活后,你安装的依赖就会进入这个项目自己的环境里。
5.3 什么是 pip
pip 是 Python 的依赖安装工具。
它相当于“Python 的包管理器”。
安装依赖:
pip install requests
这里的 requests 是一个常见的 HTTP 请求库。
5.4 导出依赖
pip freeze > requirements.txt
解释:
- pip freeze:列出当前环境安装的所有依赖
- >:把结果写入文件
- requirements.txt:依赖清单文件
以后你做 Agent 项目时,每个项目都应该有独立虚拟环境。
6. 文件读写与 pathlib
Agent 很多时候都要读文件、写文件、处理路径。
6.1 传统写法
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("hello agent")
解释:
- open(…):打开文件
- "w":写入模式(write)
- encoding="utf-8":按 UTF-8 编码写入文本,避免中文乱码
- with … as f:一种安全的写法,代码块结束后会自动关闭文件
读取:
with open("notes.txt", "r", encoding="utf-8") as f:
content = f.read()
print(content)
- "r":读取模式(read)
- f.read():读取整个文件内容
6.2 更推荐:pathlib
pathlib 是 Python 标准库里的路径工具。
它比手动拼字符串路径更清晰、更稳。
from pathlib import Path
path = Path("notes.txt")
path.write_text("hello pathlib", encoding="utf-8")
content = path.read_text(encoding="utf-8")
print(content)
解释:
- Path("notes.txt"):表示一个路径对象
- write_text(…):往文件里写文本
- read_text(…):读文件文本
6.3 常见路径操作
from pathlib import Path
project_root = Path.cwd()
data_dir = project_root / "data"
log_file = data_dir / "app.log"
print(project_root)
print(data_dir)
print(log_file)
解释:
- Path.cwd():当前工作目录(current working directory)
- /:在 pathlib 里用来拼接路径,不是真的做除法
- data_dir / "app.log":表示 data 目录下的 app.log
创建目录:
data_dir.mkdir(parents=True, exist_ok=True)
解释:
- mkdir:创建目录
- parents=True:如果上级目录不存在,也一起创建
- exist_ok=True:如果目录已经存在,不报错
这在做日志目录、缓存目录时很常见。
7. dataclass
如果一个对象主要是“装数据”,优先考虑 dataclass。
7.1 普通类写法
class ToolCall:
def __init__(self, name: str, input_text: str):
self.name = name
self.input_text = input_text
这没问题,但写起来有点啰嗦。
7.2 dataclass 写法
from dataclasses import dataclass
@dataclass
class ToolCall:
name: str
input_text: str
解释:
- @dataclass:告诉 Python,这个类主要是存数据
- Python 会自动帮你生成初始化方法等常用内容
使用:
call = ToolCall(name="search", input_text="latest AI agent papers")
print(call)
7.3 更适合 Agent 的例子
from dataclasses import dataclass, field
@dataclass
class AgentConfig:
model: str
max_steps: int = 5
tools: list[str] = field(default_factory=list)
解释:
- field(default_factory=list) 的意思是:
每次创建对象时,都生成一个新的空列表 - 不建议直接写 tools: list[str] = []
- 因为那样可能导致多个对象共享同一个列表
config = AgentConfig(model="gpt-5", tools=["search", "browser"])
print(config)
8. typing
类型标注(typing) 不会让 Python 变成强类型语言,但会让代码更清晰,也更容易维护。
8.1 基本类型标注
def add(a: int, b: int) –> int:
return a + b
表示:
- a 是整数
- b 是整数
- 返回值也是整数
8.2 容器类型
def pick_first(tools: list[str]) –> str:
return tools[0]
表示:
- tools 是字符串列表
- 返回一个字符串
def get_config() –> dict[str, str]:
return {"model": "gpt-5"}
表示:
- 返回一个字典
- 键和值都是字符串
8.3 Optional
from typing import Optional
def find_tool(name: str) –> Optional[str]:
tools = ["search", "browser"]
if name in tools:
return name
return None
解释:
- Optional[str] 的意思是:
这个函数可能返回字符串,也可能返回 None
8.4 为什么要学 typing
因为你后面会写很多这种结构:
- tool 输入
- tool 输出
- config
- task state
- memory record
类型标注会大幅减少后期混乱。
9. Enum
当一个字段只能取固定值时,用 Enum 很合适。
Enum(枚举) 表示“一组固定可选值”。
from enum import Enum
class TaskStatus(Enum):
PENDING = "pending"
RUNNING = "running"
DONE = "done"
FAILED = "failed"
使用:
status = TaskStatus.RUNNING
if status == TaskStatus.RUNNING:
print("task is running")
在 Agent 项目里,常见枚举有:
- 任务状态
- 工具类型
- 日志级别
- 执行模式
为什么不用普通字符串?
因为枚举更规范:
- 不容易拼错
- 更适合后期维护
- 代码可读性更好
10. try/except 异常处理
异常(exception) 可以理解成“程序运行时发生了错误”。
Agent 系统里,异常处理很重要,因为:
- 文件可能不存在
- API 可能失败
- JSON 可能不合法
- 工具可能超时
10.1 基本写法
try:
value = int("123")
print(value)
except ValueError:
print("invalid number")
解释:
- try:先尝试执行
- except:如果出错,就进这里
- ValueError:一种常见异常,表示值不合法
10.2 文件读取异常
from pathlib import Path
def read_file(path: str) –> str:
try:
return Path(path).read_text(encoding="utf-8")
except FileNotFoundError:
return "file not found"
这里表示:
- 如果文件存在,就读取内容
- 如果文件不存在,就返回提示
10.3 主动抛出异常
def safe_divide(a: float, b: float) –> float:
if b == 0:
raise ValueError("b cannot be zero")
return a / b
解释:
- raise:主动抛出异常
- 当输入不合理时,让调用者明确知道出了问题
记住一个原则:
不要把异常全吞掉。
能处理的就处理,不能处理的就抛出去,并记录日志。
11. logging
日志(logging) 不是可选项,是后面做 Agent 的必备能力。
和 print 不同,日志更适合记录程序运行过程。
11.1 最简单的日志
import logging
logging.basicConfig(level=logging.INFO)
logging.info("agent started")
logging.warning("tool response is slow")
logging.error("task failed")
解释:
- INFO:普通运行信息
- WARNING:警告
- ERROR:错误
11.2 更常见写法
import logging
logger = logging.getLogger(__name__)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s"
)
logger.info("starting task")
解释:
- getLogger(__name__):创建一个和当前模块关联的 logger
- format=…:设置日志显示格式
- %(asctime)s:时间
- %(levelname)s:日志级别
- %(name)s:logger 名称
- %(message)s:日志正文
11.3 写入文件
import logging
from pathlib import Path
log_dir = Path("logs")
log_dir.mkdir(exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(message)s",
handlers=[
logging.FileHandler(log_dir / "app.log", encoding="utf-8"),
logging.StreamHandler()
],
)
解释:
- FileHandler:把日志写入文件
- StreamHandler:把日志输出到终端
- handlers=[…]:表示同时使用多个输出方式
以后你做 agent 时,至少要记录:
- 收到什么任务
- 调了什么工具
- 工具输入输出
- 失败原因
- 总耗时
12. JSON 处理
JSON 是 JavaScript Object Notation 的缩写,可以理解成一种“用文本表示结构化数据”的格式。
它很像 Python 里的字典和列表,所以非常适合:
- 存配置文件
- 存任务结果
- 作为 API 返回值
- 作为模型的结构化输入输出
例如:
{
"model": "gpt-5",
"max_steps": 3,
"tools": ["search", "browser"]
}
它和 Python 字典很像,但注意两点:
- JSON 必须用双引号
- JSON 的布尔值是 true / false,不是 True / False
12.1 Python 对象转 JSON
import json
data = {
"task": "summarize this file",
"max_steps": 3,
"tools": ["search", "browser"]
}
json_text = json.dumps(data, ensure_ascii=False, indent=2)
print(json_text)
解释:
- json.dumps(…):把 Python 对象转换成 JSON 字符串
- ensure_ascii=False:保证中文正常显示
- indent=2:输出更整齐,带缩进
12.2 JSON 转 Python 对象
import json
text = '{"model": "gpt-5", "debug": true}'
obj = json.loads(text)
print(obj["model"])
解释:
- json.loads(…):把 JSON 字符串解析成 Python 对象
12.3 写入 JSON 文件
from pathlib import Path
import json
config = {
"model": "gpt-5",
"max_steps": 5,
"tools": ["search", "read_file"]
}
Path("config.json").write_text(
json.dumps(config, ensure_ascii=False, indent=2),
encoding="utf-8"
)
12.4 读取 JSON 文件
from pathlib import Path
import json
config = json.loads(Path("config.json").read_text(encoding="utf-8"))
print(config)
执行过程可以拆成两步:
13. 命令行参数
很多后端脚本、Agent 任务入口都会从命令行接收参数。
13.1 最简单:sys.argv
import sys
print(sys.argv)
sys.argv 会拿到你运行程序时传入的参数列表。
但更推荐 argparse。
13.2 argparse
argparse 是 Python 标准库里的命令行参数解析工具。
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("–task", required=True)
parser.add_argument("–max-steps", type=int, default=3)
args = parser.parse_args()
print("task:", args.task)
print("max_steps:", args.max_steps)
解释:
- ArgumentParser():创建一个参数解析器
- add_argument(…):定义支持哪些参数
- required=True:这个参数必填
- type=int:这个参数要被解析成整数
- default=3:如果不传,就默认是 3
- parse_args():真正读取命令行参数
运行:
python app.py –task "summarize paper" –max-steps 5
这在后面做:
- 批处理
- 调试脚本
- 评测脚本
- tool runner
时都很常见。
14. 项目目录结构
一个最小 Python 工程可以这样组织:
agent_course/
app.py
requirements.txt
config.json
logs/
data/
my_agent/
__init__.py
config.py
agent.py
tools.py
utils.py
更清晰一点的版本:
agent_course/
pyproject.toml
requirements.txt
.env
app.py
logs/
data/
src/
my_agent/
__init__.py
agent.py
config.py
tools.py
models.py
logger.py
先解释几个词:
- requirements.txt
Python 项目的依赖清单 - .env
用来放环境变量,比如 API Key - src/
常见的源码目录 - logs/
放日志 - data/
放数据或任务结果
你现在先不用追求最复杂结构,但要养成一个习惯:
- 配置单独放
- 工具单独放
- 核心逻辑单独放
- 日志目录固定
- 数据目录固定
15. 小项目:最小 Agent 配置与日志程序
现在把前面的知识串起来。
15.1 文件:app.py
下面这份代码是“教学注释版”。
你不仅要看它做了什么,还要看“为什么这样写”。
import argparse
import json
import logging
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
class TaskStatus(Enum):
"""
Enum(枚举)用来表示“固定的几个可选状态”。
这里任务状态只有四种:
– pending:等待中
– running:运行中
– done:已完成
– failed:失败
"""
PENDING = "pending"
RUNNING = "running"
DONE = "done"
FAILED = "failed"
@dataclass
class AgentConfig:
"""
dataclass 适合用来保存“数据对象”。
这个类表示 Agent 的配置:
– model:要使用的模型名
– max_steps:最多执行多少步
– tools:允许使用哪些工具
"""
model: str
max_steps: int = 3
tools: list[str] = field(default_factory=list)
# default_factory=list 的意思是:
# 每次创建新对象时,都生成一个新的空列表。
# 这样可以避免多个对象共享同一个默认列表。
@dataclass
class TaskRecord:
"""
这个类表示“一次任务执行的结果记录”。
它会保存:
– task:任务内容
– status:任务状态
– steps_run:实际跑了多少步
"""
task: str
status: TaskStatus
steps_run: int = 0
def setup_logger() –> logging.Logger:
"""
创建并返回一个 Logger(日志记录器)。
Logger 的作用:
– 把程序运行过程打印到终端
– 同时写入日志文件
– 便于后续调试和排查问题
"""
log_dir = Path("logs")
# mkdir 表示“创建目录”
# exist_ok=True 表示如果目录已经存在,不要报错
log_dir.mkdir(exist_ok=True)
logging.basicConfig(
level=logging.INFO,
# level 表示日志级别
# INFO 适合记录正常运行过程
format="%(asctime)s | %(levelname)s | %(message)s",
# format 表示每条日志的显示格式:
# 时间 | 级别 | 内容
handlers=[
logging.FileHandler(log_dir / "app.log", encoding="utf-8"),
# FileHandler 表示把日志写入文件
logging.StreamHandler(),
# StreamHandler 表示把日志输出到终端
],
)
return logging.getLogger("agent-course")
# getLogger("agent-course") 返回一个具名 logger
# 后面就可以通过 logger.info(…) 这种方式记录日志
def load_config(path: str) –> AgentConfig:
"""
从 JSON 配置文件中读取内容,并转换成 AgentConfig 对象。
参数 path 是配置文件路径,例如:
– "config.json"
"""
data = json.loads(Path(path).read_text(encoding="utf-8"))
# 这里分成两层理解:
# 1. Path(path).read_text(…) 先把文件读成字符串
# 2. json.loads(…) 再把字符串解析成 Python 字典
return AgentConfig(
model=data["model"],
# data["model"] 表示这个字段必须存在,不然会报错
max_steps=data.get("max_steps", 3),
# dict.get("max_steps", 3) 表示:
# 如果配置里没有 max_steps,就用默认值 3
tools=data.get("tools", []),
)
def save_task_record(record: TaskRecord) –> None:
"""
把任务结果写入 data/last_task.json。
这是最简单的“状态持久化”示例。
持久化的意思是:程序结束后,结果仍然保存在文件里。
"""
data_dir = Path("data")
data_dir.mkdir(exist_ok=True)
output = {
"task": record.task,
"status": record.status.value,
# 注意这里要用 record.status.value
# 因为 record.status 是一个 Enum 对象
# 而 JSON 只能直接保存字符串、数字、列表、字典等基础类型
"steps_run": record.steps_run,
}
Path(data_dir / "last_task.json").write_text(
json.dumps(output, ensure_ascii=False, indent=2),
encoding="utf-8",
)
def run_task(task: str, config: AgentConfig, logger: logging.Logger) –> TaskRecord:
"""
运行一个任务,并返回任务记录。
这个函数现在还是一个“最小模拟版本”:
它不会真的调用模型,只是用循环模拟“执行了若干步”。
参数解释:
– task:任务内容
– config:Agent 配置
– logger:日志记录器
"""
record = TaskRecord(task=task, status=TaskStatus.RUNNING)
# 一开始先把任务状态标记为 RUNNING(运行中)
logger.info("Task started: %s", task)
logger.info("Model: %s", config.model)
logger.info("Tools: %s", config.tools)
try:
# try/except 的作用是:
# 如果运行过程中出错,不要让程序直接崩掉,
# 而是进入 except,记录失败状态和错误日志。
for step in range(config.max_steps):
logger.info("Step %s/%s", step + 1, config.max_steps)
record.steps_run += 1
record.status = TaskStatus.DONE
logger.info("Task completed")
except Exception as e:
# Exception 是大多数异常的基类
# 这里先用通用写法,后面课程可以再细分异常类型
record.status = TaskStatus.FAILED
logger.exception("Task failed: %s", e)
# logger.exception(…) 除了记录错误信息,
# 还会把异常堆栈(traceback)一起写到日志里,
# 这比 logger.error(…) 更适合调试异常
return record
def main() –> None:
"""
main() 是程序入口函数。
一般来说,实际运行逻辑都会集中放在 main() 中,
这样结构更清晰,也更方便测试。
"""
parser = argparse.ArgumentParser()
# argparse 是 Python 标准库里的命令行参数工具
parser.add_argument("–task", required=True)
# –task 表示命令行参数名
# required=True 表示这是必填参数
parser.add_argument("–config", default="config.json")
# –config 是可选参数
# 如果用户不传,就默认读取 config.json
args = parser.parse_args()
# parse_args() 的作用是:
# 把命令行参数解析成一个对象
# 例如 args.task, args.config
logger = setup_logger()
try:
config = load_config(args.config)
except FileNotFoundError:
logger.error("Config file not found: %s", args.config)
return
except json.JSONDecodeError:
logger.error("Invalid JSON config: %s", args.config)
return
record = run_task(args.task, config, logger)
save_task_record(record)
print("Final status:", record.status.value)
print("Steps run:", record.steps_run)
# print 更适合给用户做最终结果展示
# logger 更适合记录程序运行过程
if __name__ == "__main__":
"""
__name__ 是 Python 的一个内置变量。
当你“直接运行当前文件”时,__name__ 的值会是 "__main__"
当这个文件被别的文件 import 时,__name__ 就不是 "__main__"
所以这句的意思是:
只有在直接运行这个文件时,才执行 main()
"""
main()
15.2 文件:config.json
{
"model": "gpt-5",
"max_steps": 3,
"tools": ["search", "read_file", "write_file"]
}
这里:
- model:模型名
- max_steps:任务最多执行多少步
- tools:当前允许使用的工具列表
15.3 运行方式
python app.py –task "read and summarize notes"
这句话的意思是:
- 运行 app.py
- 传入一个命令行参数 –task
- 参数值是 "read and summarize notes"
15.4 这个小项目里你已经练到了什么
- 函数
- 类
- dataclass
- Enum
- JSON 读写
- pathlib
- 日志
- 异常处理
- 命令行参数
- 项目目录
- 最小任务执行流程
它其实已经是一个很小的“agent 外壳”了。
16. 建议你怎么练
不要只看,建议你按下面顺序自己打一遍代码。
第一次练习
原样抄一遍,跑通。
第二次练习
自己改这些地方:
- max_steps 改成从命令行传入
- 在日志里加 task id
- 让 tools 为空时给出 warning
- 把输出文件名改成带时间戳
先解释两个词:
- task id
用来唯一标识一次任务,方便查日志 - timestamp(时间戳)
表示当前时间,常用来生成不重复的文件名
第三次练习
继续加一个最简单的工具系统:
def call_tool(name: str, input_text: str) –> str:
if name == "echo":
return input_text
raise ValueError(f"Unknown tool: {name}")
解释:
- call_tool(…):根据工具名调用工具
- 如果工具名是 echo,就把输入原样返回
- 否则抛出异常,表示工具不存在
然后把它放进 run_task() 里,模拟 Agent 真正调用工具的过程。
17. 这节课你真正应该记住的东西
- Python 基础不是零碎语法,而是后面工程能力的地基。
- dataclass 很适合配置、任务记录、工具输入输出。
- pathlib 比直接拼字符串路径更稳。
- logging 从第一天就该加。
- JSON 是 agent 世界里的基础交换格式。
- argparse 让你的程序从“脚本”开始变成“工具”。
- 好的目录结构会让你后面扩展 agent 更轻松。
18. 下一节课预告
学完这一课,第二课我们将深入学习异步编程:
这是 agent 很关键的一层。
你要掌握这些概念:
- 同步 vs 异步
- async / await
- coroutine
- event loop
- asyncio.run
- create_task
- gather
- timeout
- cancellation
- 并发和串行的区别
- race condition 基本概念
为什么它重要:
- 模型调用是 IO
- 搜索、数据库、浏览器、文件、网络都是 IO
- agent 经常需要并发调多个工具
你至少要能理解:
- 一个 agent 为什么不能全靠阻塞式写法
- 什么时候应该并发,什么时候必须串行
好了,感谢您的学习,学习是提升自己最快花费最少,但是提升最快的方式,不要忘了一直学习哦!!!


