Godot(4.X): 外接Python处理Excel数据: Python部分
前言: 本文是对 Godot(4.X): 外接Python处理Excel数据: 账号管理系统实现-CSDN博客 中Python部分代码的完整实现,本文将继续解释Python的子脚本部分,其中主脚本部分在主文章中已虽已完整给出,这里 仍然会加入文本开头 方便阅读
这里给出Python文件结构( 主管理器 Json管理器 Excel管理器 ),主要使用 Openpyxl 库

主管理器只负责对接Godot,负责返回对应处理结果,这里重新说明:
规定: "0" : 验证通过,"1" : 校验信息问题(比如账号不存在,注册密码长度不够),"2" : 账号密码匹配问题(密码错误,已被注册)
主管理器代码
#=================openpyxl库================#
# 本文用来处理Excel的python的库 #
#===========================================#
from openpyxl.worksheet.worksheet import Worksheet
# 以下为子Python脚本引入的处理函数(这里简要解释)
from ExcelProcessing import read_from_excel # 返回指定的Excel工作表
from ExcelProcessing import write_new_account # 向Excel写入新账号信息,返回 tuple[bool, int] 元组
from JsonProcessing import convert_excel_to_account # 将Excel中读取的数据转化为固定字典格式 {"ID": [password, name]}
from JsonProcessing import detect_login_information # 校验登录信息,返回 tuple[bool, int] 元组
# Optional 为类型注解标记,可以用于规定函数传参传限定类型内的参数如 Optional[int | str] 指参数可以传入 int,str,None类型的
from typing import Optional
# 这个是Python用来接收系统传参的类,其中 sys.argv 为收到的参数
import sys
import os
#=============默认存档名============#
default_save_exc_name = "GameDataExcel.xlsx" # 手动调用脚本时的Excel路径
default_exc_data_player = "PlayerData" # 默认取打开Excel中的工作表的名字为 PlayerData
default_json_out = "PlayerGameData.json" # 存档转化出的Json位置
#=============默认存档名============#
#=============指定参数==============# # 这里所有的位置都是Godot中传来参数中对应参数的索引
default_order_index: int = 2 # 这里是默认从godot获得的参数中取第2位为指令参数
default_excel_path_index: int = 1 # 默认Excel位置
default_order_name_index: int = 3 # 默认名字位置
default_order_pwd_index: int = 4 # 默认密码位置
default_godot_argv_len: int = 5 # 默认传参长度
#=============指定参数==============#
#=============指定命令==============#
godot_login: str = "0" # 指定Godot登录命令为 "0"
godot_register: str = "1" # 指定Godot注册命令为 "1"
#=============指定命令==============#
"""
以下所有函数面向Godot返回
此时Python中print的内容就是Godot接收到的返回值+-
全部使用print
"""
"""
返回值说明:
- 元组第1位:是否校验通过(True=通过,False=不通过)
- 元组第2位:状态码
- 0 : 账号验证通过
- 1 : 账户信息不存在
- 2 : 账号信息验证错误(信息不全/密码错误)
"""
# 登录验证器
def login_verify(input_login_info: Optional[list[str]]) -> None:
if input_login_info is None:
return None
try:
# 得到账户表格
read_sheet = read_from_excel(default_save_exc_name, default_exc_data_player)
stored_info_dict = convert_excel_to_account(read_sheet)
# 验证登录信息
verify_result: tuple = detect_login_information(stored_info_dict, input_login_info)
# 通过验证
print(str(verify_result[1]))
except Exception as e:
# 登录程序运行失败
print("-1")
"""
账户注册处理
返回-1: 程序运行出错
返回0 : 注册成功
返回1 : 账户注册出现问题(非法字符/账户密码问题)
返回2 : 账户已被注册
"""
# 注册验证器
def register_verify(input_register_info: Optional[list[str]]) -> None:
if input_register_info is None:
return None
try:
# 验证注册信息并写入
verify_result: tuple = write_new_account(default_exc_data_player,
default_save_exc_name,
input_register_info)
print(str(verify_result[1]))
except Exception as e:
# 注册程序运行失败
print("-1")
"""
返回 -1 为程序运行出错
返回 -2 为程序输入变量不足
"""
# 主函数进入
if __name__ == "__main__":
if len(sys.argv) >= default_godot_argv_len:
# 将Godot传参转至本地
receive_params: list[str] = sys.argv
godot_order = receive_params[default_order_index]
# 格式化传参内容
default_save_exc_name = receive_params[default_excel_path_index] # 获得收到的Excel地址
input_name = receive_params[default_order_name_index] # 获得收到的账号ID
input_pwd = receive_params[default_order_pwd_index] # 获得收到的密码
input_info: list[str] = [input_name, input_pwd]
# 登录注册验证请求
if (godot_order == godot_login):
login_verify(input_info)
if (godot_order == godot_register):
register_verify(input_info)
# 参数不够传 -2
else:
print("-2")
1. 对子管理器的简述
在主文章的主管理器代码中,使用了子管理器的函数
# 以下为子Python脚本引入的处理函数(这里简要解释)
from ExcelProcessing import read_from_excel # 返回指定的Excel工作表
from ExcelProcessing import write_new_account # 向Excel写入新账号信息,返回 tuple[bool, int] 元组
from JsonProcessing import convert_excel_to_account # 将Excel中读取的数据转化为固定字典格式 {"ID": [password, name]}
from JsonProcessing import detect_login_information # 校验登录信息,返回 tuple[bool, int] 元组
虽然并非所有子管理器的代码均被使用,但是文章仍然会解释说明
子管理器要分析获得的Godot参数,排除错误的账号信息并返回给主管理器,由主管理器返回给Godot
子管理器处理Excel账号数据流程:
1. 首先要规定Excel存入的账号的格式
2. 然后Python要打开Excel,将Excel格式转化为特定格式,这里需要的的格式为
{ "ID": [password, name ] }
3. Python寻找到对应ID,检查密码,返回规定的检查结果
2. 子管理器实现
这里先给出Excel的格式

1. Excel管理器 (ExcelProcessing)
主要用于处理Excel数据,以下为代码部分
#====引入的 Openpyxl 库中的工作簿,工作表,以及加载函数====#
from openpyxl.worksheet.worksheet import Worksheet # 工作表类
from openpyxl.workbook.workbook import Workbook # 工作簿类
from openpyxl import load_workbook # 加载Excel函数
from typing import Optional # 多类型注释,用于函数返回多类型
#====允许调试信息(防止 print() 输出到Godot捕获参数列表)====#
DEBUG = False
# 调试信息输出
def log(msg):
"""只有 DEBUG=True 时才打印,否则不输出任何东西"""
if DEBUG:
print(msg)
#====允许调试信息(防止 print() 输出到Godot捕获参数列表)====#
"""
Excel全局Excel文件管理器
可以替换所有函数内打开Excel的操作
减少频繁打开关闭Excel的性能消耗
这里可以忽略,本文并未使用
"""
#===============全局文件管理器Excel===============#
"""
统一管理全局被打开的Excel
防止手动释放出现问题
"""
class Excel_Manager:
#==默认空表未打开Excel==#
def __init__(self):
self.inner_excel: Workbook = None
#==尝试打开Excel==#
def open_excel(self, file_path: str) -> bool:
#==检查本对象是否已经有Excel指向==#
if self.inner_excel:
log(f"打开对象已存在: {self.inner_excel}: 请手动释放")
return False
#==尝试读取Excel==#
try:
#==获取Excel==#
read_excel: Workbook = load_workbook(file_path, read_only=True)
self.inner_excel = read_excel
return True
#==异常处理==#
except Exception as e:
log(f"打开Excel发生异常: {e}")
return False
#==获得类中Excel==#
def get_excel(self) -> Optional[Workbook]:
#==检查本对象Excel是否已经指向==#
if self.inner_excel:
log(f"成功返回Excel: {self.inner_excel}")
return self.inner_excel
#=不存在=#
log("返回excel失败,excel并未读取")
return None
#==获得Excel中对应表单==#
def get_sheet(self, sheet_name: str) -> Optional[Worksheet]:
#==检查Excel指向==#
if not self.inner_excel:
log("不存在原表")
return None
#==返回特定表==#
try:
if sheet_name not in self.inner_excel.sheetnames:
log(f"工作表'{sheet_name}' 不存在")
return None
#==返回表==#
return self.inner_excel[sheet_name]
#==返回异常==#
except Exception as e:
log(f"表单返回发生异常: {e}")
return None
#==关闭Excel表格以及停止指向==#
def close_workbook(self) -> bool:
#==检查Excel指向==#
if not self.inner_excel:
log("不存在原表,已经关闭")
return True
try:
self.inner_excel.close()
return True
#==表单关闭失败==#
except Exception as e:
log(f"关闭excel出现异常: {e}")
return False
"""
以下为当前文章使用的原版表格函数
因为没有Excel统一管理类
所以需要注意表格的状态
开启或者关闭
"""
#============从excel中以固定格式读取工作表============#
#===========返回值为 Worksheet 或 None ===============#
#==参数(Excel路径,工作表名字,是否只读(默认不只读))===#
def read_from_excel(file_read_path: str,
read_data_name: str,
read_only_out: bool = False) -> Optional[Worksheet]:
read_excel: Workbook
#==返回指定工作表==#
try:
#==获取Excel==#
read_excel = load_workbook(file_read_path, read_only=read_only_out)
#==Excel中不存在指定工作表==#
if read_data_name not in read_excel.sheetnames:
log(f"工作表'{read_data_name}' 不存在")
read_excel.close()
return None
#==读取返回指定工作表==#
read_excel_sheet = read_excel[read_data_name]
read_excel.close()
return read_excel_sheet
# 读取失败
except Exception as e:
log(f"read failed{e}")
# 打开了就关闭Excel
if read_excel:
read_excel.close()
return None
"""
账户注册处理
返回-1: 程序运行出错
返回0 : 注册成功
返回1 : 账户注册出现问题(非法字符/账户密码问题)
返回2 : 账户已被注册
"""
#=============写入新玩家数据==============#
#===返回值为元组 tuple[bool, int] ========#
#====第一个为是否成功,第二个为结果参数===#
#参数(Excel工作表名,Excel路径,新的账户信息)#
def write_new_account(read_data_name: str,
file_read_path: str,
new_account_information: Optional[list[str]]) -> tuple[bool, int]:
#===传入空列表处理===#
if new_account_information is None:
return False, -1
#=======这里调用了下面的验证账户输入合法性质(检验非法字符)检查函数=========#
#==================返回同样为元组 tuple[bool, str] =======================#
#==================== 第一个为是否合法,第二个为调试信息===================#
get_account_verify: tuple[bool, str] = verify_account_data(new_account_information)
#===非法返回 1===#
if not get_account_verify[0]:
log("账号登录或注册出现问题: " + get_account_verify[1])
return False, 1
read_excel: Workbook
#=====合法尝试修改表格=====#
try:
#=====这里调用了下面验证Excel中玩家是否存在的函数=====#
#==================返回值为bool类型==================#
#=== 参数 (Excel工作表名字,Excel路径,查找账户ID) ===#
if detect_id_excel_exist(read_data_name, file_read_path, new_account_information[0]):
log(f"玩家ID已经存在: {new_account_information[0]}")
return False, 2
#===修改Excel表格,则Excel读取不能启用 read_only=True ===#
read_excel = load_workbook(file_read_path, read_only=False)
# 检查工作表存在
if read_data_name not in read_excel.sheetnames:
log(f"工作表'{read_data_name}' 不存在")
read_excel.close()
return False, -1
# 选中工作表,获得新数据行号
read_excel_sheet = read_excel[read_data_name]
new_row_index = read_excel_sheet.max_row + 1 # 获得最大行的下一行,就是插入行
col_num = read_excel_sheet.max_column # 获得Excel当前工作表最大列数
#====输入数据不全全部补"None"=====#
need_add_num = col_num - len(new_account_information) # 要补充的 "None" 的数量
if need_add_num > 0:
new_account_information += [None] * need_add_num
#=======遍历新行所有单元格写入=======#
# col_index 从 start 开始,每增加一列增加 1,用来计数
# .cell()的第三个参数用来设置Excel单个格子的值
for col_index, input_value in enumerate(new_account_information, start=1):
read_excel_sheet.cell(row=new_row_index,
column=col_index,
value=input_value)
#====保存修改数据====#
read_excel.save(file_read_path)
read_excel.close()
log(f"数据写入成功: {new_account_information} -> {file_read_path}")
return True, 0
# 写入失败处理
except Exception as e:
log(f"write failed: {e}")
if read_excel:
read_excel.close()
return False, -1
#=====验证Excel中玩家是否存在=====#
#=========返回值为bool类型========#
#=== 参数 (Excel工作表名字,Excel路径,查找账户ID) ===#
def detect_id_excel_exist(read_data_name: str, file_read_path: str, detected_id: str) -> bool:
read_excel: Workbook
#====尝试寻找ID====#
try:
# read_only=False,这样才可以使用列迭代器,否则报错
read_excel = load_workbook(file_read_path, read_only=False)
# 检查工作表是否存在于当前 Excel 表格
if read_data_name not in read_excel.sheetnames:
log(f"工作表'{read_data_name}' 不存在")
read_excel.close()
return False
#====选中工作表,获取最大行数====#
read_excel_sheet = read_excel[read_data_name]
read_sheet_first = read_excel_sheet["A"]
row_max_len = read_excel_sheet.max_row
#====遍历确认玩家ID不存在====#
exist_dir: dict[str, int] = {}
for current_row, cell in enumerate(read_sheet_first, start=1):
# 略过第一个属性名
if current_row == 1:
continue
# 将所有玩家ID放入字典,用于后续检测
if cell.value:
exist_dir[str(cell.value)] = 1
# 对比检测是否存在
if detected_id in exist_dir:
read_excel.close()
return True
read_excel.close()
return False
#=====检测出现异常,用于调试======#
except Exception as e:
log(f"查找Excel出现问题: {e}")
if read_excel:
read_excel.close()
return False
#=======验证账户输入合法性质(检验非法字符)检查函数=========#
#===========返回为元组 tuple[bool, str] ==================#
#========== 第一个为是否合法,第二个为调试信息=============#
def verify_account_data(acc_info: list[str]) -> tuple[bool, str]:
"""
账号合法性质校验、
校验至少[账号ID, 密码, ......]
"""
if not acc_info or len(acc_info) < 2:
return False, "字段不足: 至少需要[账号ID, 密码]"
acc_id = acc_info[0]
acc_password = acc_info[1]
#=====账号基本信息校验=====#
# 返回 bool 类型
if not acc_id:
return False, "账号ID不能为空或全空格"
if len(acc_id) < 3:
return False, "账号长度不能小于3"
# 这里右边 per_char 会遍历组成一个表格,只有非法字符会成为元素
# any() 只要其中检测的元素不为空则返回true,也就是有非法字符返回true
if any(per_char in r'\/:*?"<>| ' for per_char in acc_id):
return False, "账号ID包含非法特殊字符"
#====密码校验====#
if not acc_password:
return False, "密码不能为空"
if len(acc_password) < 4:
return False, "密码长度不能小于4"
return True, "校验通过"
2. Json管理器 (JsonProcessing)
Json子管理器主要用于转化Excel玩家存档数据等为Json文件提供Godot读取,这里也提供输入总账户字典,根据Godot输入信息,验证登录信息
以下为代码部分
from openpyxl.worksheet.worksheet import Worksheet
from typing import Optional
import json # 引入 Json 库
#====允许调试信息(防止 print() 输出到Godot捕获参数列表)====#
DEBUG = False
# 调试信息输出
def log(msg):
"""只有 DEBUG=True 时才打印,否则不输出任何东西"""
if DEBUG:
print(msg)
#====允许调试信息(防止 print() 输出到Godot捕获参数列表)====#
# 转换工作表为字典(默认字典格式,所有标题散装)
# 这里是转化玩家存档数据的不是账户数据的
# 输入为 Excel工作表
# 输出为字典类型 { "属性": 值 }
def convert_excel_to_dir(out_excel_worksheet: Optional[Worksheet]) -> dict:
#===Excel工作表传值判断===#
if out_excel_worksheet is None:
log("默认字典转换: 传入的是空表")
return {}
try:
"""
这里的列表转化循环为
str(cell.value) if cell.value is not None 满足添加单元格的值
else "None" 不满足补齐 "None"
cell 来源于工作表的第一列,可以用常量换,表示名字列或者数据列
"""
# 转换属性列表
excel_title_name: list[str | None] = [str(cell.value) if cell.value is not None
else "None"
for cell in out_excel_worksheet[1]]
# 转化属性值列表
excel_value: list[str | int | None] = [cell.value if cell.value is not None
else "None"
for cell in out_excel_worksheet[2]]
# 用 zip 将两组数据对应拼成字元组
excel_zip_result = zip(excel_title_name, excel_value)
# 元组转换字典
excel_zip_to_dir = dict(excel_zip_result)
# 返回玩家存档信息字典
return excel_zip_to_dir
# 转换失败
except Exception as e:
log(f"Excel转换字典失败: {str(e)}")
return {}
# ===============================================================================#
# 转换工作表为账号格式 { "名字": ["密码", "昵称"] }
# 适配 Godot本地登录,查字典完成账号校验
# 传参 (Excel工作表),返回上述字典
# ===============================================================================#
def convert_excel_to_account(out_excel_worksheet: Optional[Worksheet]) -> dict:
#===Excel工作表传值判断===#
if out_excel_worksheet is None:
log("账号字典转换: 传入的是空表")
return {}
try:
# 转换账户信息ID,需要的list容器
excel_id_list: list[str | None] = []
excel_password_list: list[str | None] = []
excel_name_list: list[str | None] = []
excel_account_dir: dict[str , list] = {}
# 添加数据内置的用于转化特定列的数据到存储列表的函数
# 思路同上转化玩家存档数据
def append_value(cell: tuple, excel_list: list):
# 遍历得到的当前列
for cell_value in cell:
append_case: str = ""
# 没有数据添加 "None"
if cell_value.value is None:
append_case = "None"
else:
append_case = str(cell_value.value)
# 添加玩家信息
excel_list.append(append_case)
#==注意传进来的工作表不要 read_only=True 否则无法使用列迭代器 ==#
# 这里遍历 1 到 3 列 ,同样可以换成常数,分别指 ID PASSWORD NAME
for col_num, cell in enumerate(out_excel_worksheet.iter_cols(min_col=1,
max_col=3,
min_row=2,
max_row=None),
start=1):
# 添加玩家信息到对应列表
# 用match匹配当前列
match col_num:
# 玩家账户ID信息
case 1:
append_value(cell, excel_id_list)
# 玩家密码信息
case 2:
append_value(cell, excel_password_list)
# 玩家名字信息
case 3:
append_value(cell, excel_name_list)
# 获得当前最小的数据列表长度
min_valid_length = min(len(excel_password_list), len(excel_name_list), len(excel_id_list))
# 截断列表, 保证每一个数据所存的数据长度一致
# 方便后续 zip 转化
excel_id_list = excel_id_list[:min_valid_length]
excel_name_list = excel_name_list[:min_valid_length]
excel_password_list = excel_password_list[:min_valid_length]
# 这里的思路是先将后面的所有数据整合成列表
# 然后再和 ID 匹配转化成字典
# 中间数组
result_info_list: list[list[str]] = []
# 整合结果字典 [[password, name]]
result_info_list = [
[password, name]
for password, name in zip(excel_password_list,
excel_name_list)
]
# 转换字典
excel_account_dir = dict(zip(excel_id_list, result_info_list))
return excel_account_dir
# 账户字典转换失败
except Exception as e:
log(f"账户字典创建失败: {str(e)}")
return {}
#===============================================================================#
# 字典创建JSON文件
# 这里是将字典的数据转化为Json的
# 传参为 (字典,Json输出路径)
def convert_dir_to_json(out_dir: dict, json_out_path: str) -> None:
# 尝试创建JSON文件
try:
convert_dir: str = json.dumps(
out_dir,
ensure_ascii=False,
indent=4,
sort_keys=False
)
# 写入JSON
with open(json_out_path, "w", encoding="utf-8") as file:
file.write(convert_dir)
log(f"JSON文件创建成功: {json_out_path}")
# 创建失败
except Exception as e:
log(f"字典创建JSON失败: {str(e)}")
"""
校验玩家登录信息(账号 + 密码)
返回值说明:
- 元组第1位:是否校验通过(True=通过,False=不通过)
- 元组第2位:状态码
- 0 : 账号验证通过
- 1 : 账户信息不存在
- 2 : 账号信息验证错误(信息不全/密码错误)
"""
#=====使用玩家信息字典校验登录信息=====#
# 传值为 (玩家信息字典,登录信息)
# 返回 tuple[bool, int] 元组
# 第一个bool为校验是否通过,int 为校验得到的结果码
def detect_login_information(input_info_dict: dict,
login_info: Optional[list[str]]
) -> tuple[bool, int]:
# 空信息校验
if login_info is None:
log("登录验证: 传入的是空信息")
return False, 1
# 校验输入长度
if len(login_info) < 2:
log("登录信息不全")
return False, 2
# 校验玩家ID存在
if not (login_info[0] in input_info_dict):
log("账号不存在!")
return False, 1
# 校验密码
input_password = login_info[1]
stored_password = input_info_dict[login_info[0]][0]
if input_password != stored_password:
log("账号密码错误!")
return False, 2
# 校验通过
log("校验通过")
return True, 0
3. 补充
本代码演示部分在主文章中已经演示,这里不再做演示,代码开源地址同样在主文章中给出。这里返回主文章
更多推荐
所有评论(0)