我们将使用Python最流行的GUI库之一——Tkinter来构建界面,并集成tkinter.scrolledtext作为代码编辑区域,为了实现语法高亮,我们将使用一个名为pygments的第三方库。

这个编辑器将具备以下核心功能:
- 代码编辑区:带行号和滚动条。
- 语法高亮:对Python代码进行颜色标记。
- 文件操作:新建、打开、保存文件。
- 运行代码:在编辑器内执行Python脚本并显示输出。
- 简单的错误提示:捕获并显示运行时错误。
第一步:安装必要的库
Tkinter是Python的标准库,通常已经预装,但我们需要安装pygments库来处理语法高亮。
打开你的终端或命令行,运行以下命令:
pip install pygments
第二步:项目结构
我们将所有代码写在一个名为 python_editor.py 的文件中。

# python_editor.py import tkinter as tk from tkinter import ttk, filedialog, messagebox, scrolledtext import subprocess import sys import os from pygments import lex from pygments.lexers import PythonLexer from pygments.styles import get_style_by_name from pygments.token import Token
第三步:创建主应用程序类
我们将使用面向对象的方式,创建一个PythonEditorApp类来管理整个应用程序。
class PythonEditorApp:
def __init__(self, root):
self.root = root
self.root.title("Python 编辑器")
self.root.geometry("800x600")
# --- 核心组件 ---
self.text_area = None
self.line_numbers = None
self.output_area = None
# --- 文件路径 ---
self.file_path = None
# --- 配置语法高亮样式 ---
self.lexer = PythonLexer()
self.style = get_style_by_name('monokai') # 你可以换成 'vim', 'github', 'igor' 等
# --- 创建UI ---
self.create_widgets()
# --- 绑定事件 ---
self.bind_events()
def create_widgets(self):
# 1. 创建菜单栏
self.create_menu()
# 2. 创建主框架 (用于放置编辑区和输出区)
main_frame = ttk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# 3. 创建上半部分:编辑区
editor_frame = ttk.Frame(main_frame)
editor_frame.pack(fill=tk.BOTH, expand=True)
# 3.1 行号区域
self.line_numbers = tk.Text(editor_frame, width=4, padx=3, takefocus=0, border=0,
background='#2b2b2b', foreground='#999999', font=('Consolas', 10))
self.line_numbers.pack(side=tk.LEFT, fill=tk.Y)
# 3.2 代码编辑区域
self.text_area = scrolledtext.ScrolledText(editor_frame, wrap=tk.NONE, font=('Consolas', 10))
self.text_area.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# 初始化行号
self.update_line_numbers()
# 4. 创建下半部分:输出区
output_frame = ttk.LabelFrame(main_frame, text="输出")
output_frame.pack(fill=tk.BOTH, expand=True, pady=(5, 0))
self.output_area = scrolledtext.ScrolledText(output_frame, height=10, font=('Consolas', 10))
self.output_area.pack(fill=tk.BOTH, expand=True)
def create_menu(self):
menubar = tk.Menu(self.root)
self.root.config(menu=menubar)
# 文件菜单
file_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="文件", menu=file_menu)
file_menu.add_command(label="新建", command=self.new_file)
file_menu.add_command(label="打开...", command=self.open_file)
file_menu.add_command(label="保存", command=self.save_file)
file_menu.add_separator()
file_menu.add_command(label="退出", command=self.root.quit)
# 运行菜单
run_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="运行", menu=run_menu)
run_menu.add_command(label="运行脚本", command=self.run_script)
def bind_events(self):
# 绑定文本修改事件,用于更新行号和语法高亮
self.text_area.bind('<KeyRelease>', self.on_text_change)
self.text_area.bind('<Button-1>', self.on_text_change)
self.text_area.bind('<MouseWheel>', self.on_text_change) # Windows
self.text_area.bind('<Button-4>', self.on_text_change) # Linux, scroll up
self.text_area.bind('<Button-5>', self.on_text_change) # Linux, scroll down
def on_text_change(self, event=None):
"""当文本内容改变时,更新行号和语法高亮"""
self.update_line_numbers()
self.highlight_syntax()
def update_line_numbers(self):
"""更新行号"""
final_line = self.text_area.index('end-1c').split('.')[0]
line_numbers_content = '\n'.join(str(i) for i in range(1, int(final_line) + 1))
self.line_numbers.config(state='normal')
self.line_numbers.delete('1.0', tk.END)
self.line_numbers.insert('1.0', line_numbers_content)
self.line_numbers.config(state='disabled')
# 同步滚动条
self.line_numbers.yview_moveto(self.text_area.yview()[0])
self.line_numbers.config(yscrollcommand=self.text_area.yview)
self.text_area.config(yscrollcommand=self.line_numbers.yview)
def highlight_syntax(self):
"""使用pygments进行语法高亮"""
content = self.text_area.get('1.0', tk.END)
self.text_area.tag_remove("Token", '1.0', tk.END)
for token, value in lex(content, self.lexer):
# 为每种token类型设置前景色
fg_color = self.style.styles_for_token(token).get('color', None)
if fg_color:
# Tkinter的颜色格式是 #RRGGBB
tag_name = f"fg_{fg_color}"
self.text_area.tag_config(tag_name, foreground=f'#{fg_color}')
self.text_area.insert(tk.END, value, tag_name)
else:
self.text_area.insert(tk.END, value)
# 重新定位光标,防止闪烁
cursor_pos = self.text_area.index("insert")
self.text_area.mark_set("insert", cursor_pos)
self.text_area.see("insert")
# --- 文件操作方法 ---
def new_file(self):
"""新建文件"""
self.root.title("未命名 - Python 编辑器")
self.text_area.delete('1.0', tk.END)
self.file_path = None
self.update_line_numbers()
def open_file(self):
"""打开文件"""
file_path = filedialog.askopenfilename(
filetypes=[("Python Files", "*.py"), ("All Files", "*.*")]
)
if file_path:
self.file_path = file_path
self.root.title(f"{os.path.basename(file_path)} - Python 编辑器")
with open(file_path, 'r', encoding='utf-8') as f:
self.text_area.delete('1.0', tk.END)
self.text_area.insert('1.0', f.read())
self.update_line_numbers()
def save_file(self):
"""保存文件"""
if not self.file_path:
self.save_as()
else:
with open(self.file_path, 'w', encoding='utf-8') as f:
f.write(self.text_area.get('1.0', tk.END))
messagebox.showinfo("保存成功", "文件已保存。")
def save_as(self):
"""另存为"""
file_path = filedialog.asksaveasfilename(
defaultextension=".py",
filetypes=[("Python Files", "*.py"), ("All Files", "*.*")]
)
if file_path:
self.file_path = file_path
self.root.title(f"{os.path.basename(file_path)} - Python 编辑器")
with open(file_path, 'w', encoding='utf-8') as f:
f.write(self.text_area.get('1.0', tk.END))
messagebox.showinfo("保存成功", "文件已保存。")
# --- 运行代码方法 ---
def run_script(self):
"""运行脚本并显示输出"""
# 清空之前的输出
self.output_area.delete('1.0', tk.END)
self.output_area.insert(tk.END, "正在运行...\n")
# 如果文件未保存,先保存
if not self.file_path:
self.save_as()
if not self.file_path: # 用户取消了保存
return
# 获取代码内容
code = self.text_area.get('1.0', tk.END)
try:
# 使用subprocess来运行脚本,这样可以捕获stdout和stderr
# 使用临时文件来保存代码,避免在命令行中传递复杂代码可能带来的问题
with open('temp_script.py', 'w', encoding='utf-8') as f:
f.write(code)
result = subprocess.run(
[sys.executable, 'temp_script.py'],
capture_output=True,
text=True,
check=True # 如果返回码不为0,则抛出CalledProcessError
)
# 打印标准输出
self.output_area.insert(tk.END, result.stdout)
except subprocess.CalledProcessError as e:
# 打印标准错误
self.output_area.insert(tk.END, "运行出错:\n", "error")
self.output_area.insert(tk.END, e.stderr, "error")
except Exception as e:
# 捕获其他可能的异常
self.output_area.insert(tk.END, f"发生未知错误: {e}\n", "error")
finally:
# 删除临时文件
if os.path.exists('temp_script.py'):
os.remove('temp_script.py')
if __name__ == "__main__":
root = tk.Tk()
app = PythonEditorApp(root)
root.mainloop()
第四步:代码解释
-
__init__(self, root):- 初始化主窗口,设置标题和大小。
- 定义核心组件(
text_area,line_numbers,output_area)和文件路径变量。 - 配置
pygments的词法分析器(PythonLexer)和样式(monokai)。 - 调用
create_widgets和bind_events来构建界面和绑定事件。
-
create_widgets(self):- 菜单栏 (
create_menu): 创建了“文件”和“运行”两个菜单,并关联了相应的命令函数。 - 主框架 (
main_frame): 使用ttk.Frame作为容器,方便布局。 - 编辑区 (
editor_frame):line_numbers: 一个只读的Text组件,用于显示行号,背景色设置为深色,以配合代码编辑区的主题。text_area:scrolledtext.ScrolledText,这是核心的代码输入区,支持滚动。
- 输出区 (
output_frame): 另一个scrolledtext.ScrolledText,用于显示脚本的运行结果或错误信息。
- 菜单栏 (
-
bind_events(self):
(图片来源网络,侵删)- 绑定了
text_area的<KeyRelease>(按键释放)、<Button-1>(鼠标点击)和滚动事件到on_text_change方法,这意味着每当用户输入、点击或滚动时,都会触发这个方法。
- 绑定了
-
on_text_change(self, event=None):- 这是实现动态效果的关键,它被触发时,会调用
update_line_numbers和highlight_syntax,确保行号和语法高亮始终与代码内容同步。
- 这是实现动态效果的关键,它被触发时,会调用
-
update_line_numbers(self):- 获取
text_area的总行数。 - 生成从1到总行数的数字字符串。
- 更新
line_numbers,并同步两个组件的滚动条。
- 获取
-
highlight_syntax(self):- 这是实现语法高亮的核心。
- 从
text_area获取所有代码。 - 使用
pygments.lex()函数将代码分解成一个个的token(Token.Keyword,Token.String,Token.Comment等)。 - 根据
pygments的样式(monokai),为每种token设置一个tag(标签),并指定其前景色。 - 将代码和对应的
tag重新插入到text_area中。tkinter会根据tag来应用颜色。
-
文件操作方法 (
new_file,open_file,save_file,save_as):- 这些方法使用
tkinter.filedialog来与用户交互,实现文件的创建、打开和保存逻辑,它们会更新窗口标题和self.file_path变量。
- 这些方法使用
-
run_script(self):- 这是运行代码的功能。
- 它会检查文件是否已保存,如果没有则调用
save_as。 - 它将
text_area中的代码写入一个临时文件temp_script.py,这样做是为了避免在命令行中直接执行可能包含特殊字符的代码,更加安全。 - 使用
subprocess.run()来调用系统的Python解释器(sys.executable)执行这个临时文件。 capture_output=True和text=True参数用于捕获标准输出和标准错误,并以文本形式返回。check=True参数会在脚本执行失败(返回非零退出码)时抛出CalledProcessError异常。- 将捕获到的
stdout或stderr输出到output_area中。 - 删除临时文件。
第五步:运行和改进
-
运行: 将以上代码保存为
python_editor.py,然后在终端中运行:python python_editor.py
你应该能看到一个简单的Python编辑器界面。
-
可以进一步改进的方向:
- 自动补全: 这是编辑器的一大难点,可以使用
jedi或parso库来分析代码,并提供补全建议。 - 错误检查: 在用户输入时(或通过一个“检查”按钮),使用
pyflakes或flake8等静态分析工具来检查代码中的语法错误和潜在问题,并用波浪线标出。 - 主题切换: 允许用户在不同的
pygments主题之间切换。 - 更多编辑功能: 添加查找/替换、转到行号、代码折叠等功能。
- 更好的错误提示: 在编辑器内直接用红色下划线标记出错误的位置,而不是只在输出区显示。
- 自动补全: 这是编辑器的一大难点,可以使用
这个项目为你提供了一个坚实的基础,你可以根据自己的兴趣和需求,不断地为其添加新功能,打造一个属于你自己的强大编辑器。
