别再说Python没有枚举类型了,大家好好的看!

本文详细介绍了Python中枚举类型的实现方法,包括使用字典、类以及标准库enum的高级用法,如Enum、IntEnum和unique装饰器,探讨了枚举类型的不可变性和单例特性。

枚举类型可以看作是一种标签或是一系列常量的集合,通常用于表示某些特定的有限集合,例如星期、月份、状态等。

Python 的原生类型(Built-in types)里并没有专门的枚举类型,但是我们可以通过很多方法来实现它,例如字典、类等:

MiracleLove = {'MON': '林志玲', 'TUS': '陈意涵', 'WEN': '张柏芝', 'THU': '辛芷蕾', 'FRI': '周冬雨'} class MiracleLove: MON = '林志玲' TUS = '陈意涵' WEN = '张柏芝' THU = '辛芷蕾' FRI = '周冬雨'


上面两种方法可以看做是简单的枚举类型的实现。

如果只在局部范围内用到了这样的枚举变量是没有问题的。

但问题在于它们都是可变的(mutable),也就是说可以在其它地方被修改从而影响其正常使用:

MiracleLove['MON'] = MiracleLove['FRI'] print(MiracleLove)


通过类定义的枚举甚至可以实例化,变得不伦不类:

ml = MiracleLove() print(ml.MON) MiracleLove.MON = 2 print(ml.MON)


当然也可以使用不可变类型(immutable),例如元组,但是这样就失去了枚举类型的本意,将标签退化为无意义的变量:

MiracleLove = ('R', 'G', 'B') print(MiracleLove[0], MiracleLove[1], MiracleLove[2])


为了提供更好的解决方案,Python 通过 PEP 435 在 3.4 版本中添加了 enum 标准库,3.4 之前的版本也可以通过 pip install enum 下载兼容支持的库。

enum 提供了 Enum/IntEnum/unique 三个工具,用法也非常简单,可以通过继承 Enum/IntEnum 定义枚举类型,其中 IntEnum 限定枚举成员必须为(或可以转化为)整数类型,而 unique 方法可以作为修饰器限定枚举成员的值不可重复:

from enum import Enum, IntEnum, unique try: @unique class MiracleLove(Enum): MON = '林志玲' TUS = '陈意涵' WEN = '张柏芝' THU = '辛芷蕾' FRI = '周冬雨' except ValueError as e: print(e) # duplicate values found in <enum 'MiracleLove'>: FRI -> MON


try: class MiracleLove(IntEnum): MON = 1 TUS = 2 WEN = 3 THU = 4 FRI = '周冬雨' except ValueError as e: print(e) # invalid literal for int() with base 10: '周冬雨'


更有趣的是 Enum 的成员均为单例(Singleton),并且不可实例化,不可更改:

class MiracleLove(Enum): MON = '林志玲' TUS = '陈意涵' WEN = '张柏芝' THU = '辛芷蕾' FRI = '周冬雨' try: MiracleLove.MON = 2 except AttributeError as e: print(e) # Cannot reassign members.


虽然不可实例化,但可以将枚举成员赋值给变量:

mon = MiracleLove(0) tus = MiracleLove(1) wen = MiracleLove(2) print(mon, tus, wen) # MiracleLove.MON # MiracleLove.TUS # MiracleLove.WEN


也可以进行比较判断:

print(mon is MiracleLove.MON) print(mon == MiracleLove.MON) print(mon is tus) print(wen != MiracleLove.TUS) print(mon == 0) # 不等于任何非本枚举类的值 # True # True # False # True # False


最后一点,由于枚举成员本身也是枚举类型,因此也可以通过枚举成员找到其它成员:

print(mon.TUS) print(mon.TUS.WEN.MON) # MiracleLove.TUS # MiracleLove.MON


但是要谨慎使用这一特性,因为可能与成员原有的命名空间中的名称相冲突:

print(mon.name, ':', mon.value) class Attr(Enum): name = 'NAME' value = 'VALUE' print(Attr.name.value, Attr.value.name) # R : 0 # NAME value


总结
 

  1. enum 模块的用法很简单,功能也很明确,但是其实现方式却非常值得学习。如果你想更深入了解更多 Python 中关于 Class 和 Metaclass 的黑魔法,又不知道如何入手,那么不妨阅读一下 enum 的源码。
  2. 给大家一个学习Python的小组织720790193,这就不用我多说了吧
import sys import os import json import datetime import time import threading import random import requests import speech_recognition as sr import pyttsx3 import pythoncom import win32gui import win32con import win32api from PIL import Image, ImageDraw import pystray from pystray import MenuItem as item import atexit import traceback import ctypes from ctypes import wintypes import tempfile # Live2DViewerEX 控制类 class Live2DViewerEXController: def __init__(self): self.window_name = "Live2DViewerEX" self.hwnd = None self.find_window() def find_window(self): """查找 Live2DViewerEX 窗口句柄""" try: # 使用更可靠的方法查找窗口 def enum_windows_proc(hwnd, lParam): if win32gui.IsWindowVisible(hwnd): window_text = win32gui.GetWindowText(hwnd) if self.window_name.lower() in window_text.lower(): self.hwnd = hwnd return False # 停止枚举 return True # 继续枚举 # 使用ctypes定义回调函数类型 enum_windows_func = ctypes.WINFUNCTYPE( wintypes.BOOL, wintypes.HWND, wintypes.LPARAM ) # 创建回调函数 callback = enum_windows_func(enum_windows_proc) # 调用EnumWindows ctypes.windll.user32.EnumWindows(callback, 0) if self.hwnd is None: print("未找到Live2DViewerEX窗口,请确保程序已运行") else: print(f"找到Live2DViewerEX窗口,句柄: {self.hwnd}") return self.hwnd is not None except Exception as e: print(f"查找窗口时出错: {e}") traceback.print_exc() return False def send_keystroke(self, key): """向 Live2DViewerEX 发送按键模拟""" try: if self.hwnd: # 确保窗口处于前台 win32gui.ShowWindow(self.hwnd, win32con.SW_RESTORE) win32gui.SetForegroundWindow(self.hwnd) # 发送按键 win32api.keybd_event(key, 0, 0, 0) time.sleep(0.05) win32api.keybd_event(key, 0, win32con.KEYEVENTF_KEYUP, 0) except Exception as e: print(f"发送按键时出错: {e}") def trigger_expression(self, expression_id): """触发特定表情""" try: if 1 <= expression_id <= 9: self.send_keystroke(0x30 + expression_id) except Exception as e: print(f"触发表情时出错: {e}") def trigger_motion(self, motion_id): """触发特定动作""" try: if 1 <= motion_id <= 9: self.send_keystroke(win32con.VK_F1 + motion_id - 1) except Exception as e: print(f"触发动作时出错: {e}") # 配置管理 class ConfigManager: def __init__(self, config_file=None): if config_file is None: config_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.json") self.config_file = config_file self.default_config = { "user_name": "主人", "volume": 70, "speech_rate": 150, "auto_start": False, "enable_startup_greeting": True, "enable_shutdown_greeting": True, "reminder_times": ["09:00", "12:00", "15:00", "18:00", "21:00"], "live2d_window_title": "Live2DViewerEX" } self.config = self.load_config() def load_config(self): try: if os.path.exists(self.config_file): with open(self.config_file, 'r', encoding='utf-8') as f: return {**self.default_config, **json.load(f)} return self.default_config.copy() except Exception as e: print(f"加载配置时出错: {e}") return self.default_config.copy() def save_config(self): try: # 确保配置目录存在 os.makedirs(os.path.dirname(self.config_file), exist_ok=True) with open(self.config_file, 'w', encoding='utf-8') as f: json.dump(self.config, f, ensure_ascii=False, indent=4) except Exception as e: print(f"保存配置时出错: {e}") def get(self, key, default=None): return self.config.get(key, default) def set(self, key, value): self.config[key] = value self.save_config() # 桌面助手主类 class DesktopAssistant: def __init__(self): self.config_manager = ConfigManager() self.live2d_controller = Live2DViewerEXController() self.recognizer = sr.Recognizer() self.microphone = None self.is_listening = False self.last_announce_time = None self.scheduler_running = True # 人性化问候语库 self.greetings = self.init_greetings() self.init_tts() self.init_microphone() self.init_scheduler() # 注册退出时的处理函数 atexit.register(self.shutdown_greeting) # 显示启动问候 if self.config_manager.get("enable_startup_greeting", True): self.show_welcome() def init_greetings(self): return { "dawn": [ "{name},已经{hour}点{minute}分了,您还在工作吗?要注意休息哦~", "夜深了,{name},现在是{hour}点{minute}分,早点休息对身体好", "{name},凌晨{hour}点{minute}分了,您真是辛苦呢" ], "morning": [ "早上好,{name}!新的一天开始啦,现在是{hour}点{minute}分,记得吃早餐哦~", "早安,{name}!阳光正好,现在是{hour}点{minute}分,愿你有个美好的一天!", "{name},早上好!现在是{hour}点{minute}分,今天也要元气满满哦!" ], "late_morning": [ "上午好,{name}!现在是{hour}点{minute}分,工作学习要加油哦~", "{name},上午好!已经{hour}点{minute}分了,保持专注,但也别忘了休息一下", "上午{hour}点{minute}分,{name}今天的目标完成得怎么样啦?" ], "noon": [ "中午好,{name}!现在是{hour}点{minute}分,午饭时间到啦,记得好好吃饭~", "午安,{name}!已经{hour}点{minute}分了,休息一下,补充能量吧!", "{name},中午了哦~ {hour}点{minute}分,吃饱了才有力气继续战斗呢!" ], "afternoon": [ "下午好,{name}!现在是{hour}点{minute}分,下午茶时间到啦~", "下午好呀,{name}!已经{hour}点{minute}分了,工作学习累了记得休息一下哦", "{name},下午好!{hour}点{minute}分,保持专注,但也别忘了站起来活动活动~" ], "evening": [ "晚上好,{name}!现在是{hour}点{minute}分,今天过得怎么样呀?", "傍晚好,{name}!{hour}点{minute}分了,晚餐吃了吗?", "{name},晚上好~ 已经{hour}点{minute}分了,忙碌一天辛苦了!" ], "night": [ "晚上好,{name}!现在是{hour}点{minute}分,今天早点休息哦~", "夜深了,{name}!{hour}点{minute}分了,不要熬夜太晚呀", "{name},晚上{hour}点{minute}分了,明天还要继续努力,记得早点睡哦" ], "startup": [ "您好,{name}!我已经准备好为您服务了~", "{name},欢迎回来!今天有什么需要帮忙的吗?", "很高兴再次见到您,{name}!我已经启动完毕,随时待命~" ], "shutdown": [ "{name},我要休息啦,下次再见哦~", "再见,{name}!期待与您的下一次相遇~", "关机中...{name},别忘了保存重要文件哦,明天见!" ] } def init_tts(self): try: self.tts_engine = pyttsx3.init() voices = self.tts_engine.getProperty('voices') if voices: for voice in voices: if "Microsoft" in voice.name and "Xiaoxiao" in voice.name: self.tts_engine.setProperty('voice', voice.id) break else: self.tts_engine.setProperty('voice', voices[0].id) self.tts_engine.setProperty('rate', self.config_manager.get("speech_rate", 150)) self.tts_engine.setProperty('volume', self.config_manager.get("volume", 70) / 100.0) except Exception as e: print(f"初始化TTS引擎失败: {e}") self.tts_engine = None def init_microphone(self): """初始化麦克风""" try: self.microphone = sr.Microphone() print("麦克风初始化成功") except Exception as e: print(f"麦克风初始化失败: {e}") self.microphone = None def init_scheduler(self): self.schedule_thread = threading.Thread(target=self.run_scheduler) self.schedule_thread.daemon = True self.schedule_thread.start() def run_scheduler(self): while self.scheduler_running: try: now = datetime.datetime.now() current_time = now.strftime("%H:%M") reminder_times = self.config_manager.get("reminder_times", []) if current_time in reminder_times: if self.last_announce_time is None or (now - self.last_announce_time).total_seconds() > 300: self.announce_time() self.last_announce_time = now if now.minute == 0 and (self.last_announce_time is None or (now - self.last_announce_time).total_seconds() > 300): self.announce_time() self.last_announce_time = now time.sleep(30) except Exception as e: print(f"调度器错误: {e}") time.sleep(30) def get_time_period(self, hour): if 0 <= hour < 5: return "dawn" elif 5 <= hour < 9: return "morning" elif 9 <= hour < 12: return "late_morning" elif 12 <= hour < 14: return "noon" elif 14 <= hour < 17: return "afternoon" elif 17 <= hour < 19: return "evening" else: return "night" def announce_time(self, test=False): try: now = datetime.datetime.now() hour = now.hour minute = now.minute if test: hour = now.hour minute = now.minute else: hour = now.hour minute = 0 time_period = self.get_time_period(hour) if time_period in self.greetings: greeting = random.choice(self.greetings[time_period]) message = greeting.format( name=self.config_manager.get("user_name"), hour=hour, minute=minute ) else: message = f"现在是{hour}点{minute}分" if time_period == "morning": self.live2d_controller.trigger_expression(1) elif time_period == "noon": self.live2d_controller.trigger_expression(2) elif time_period in ["afternoon", "evening"]: self.live2d_controller.trigger_expression(3) else: self.live2d_controller.trigger_expression(4) self.speak(message) except Exception as e: print(f"报时功能出错: {e}") def show_welcome(self): try: now = datetime.datetime.now() hour = now.hour if 5 <= hour < 12: welcome = f"早上好,{self.config_manager.get('user_name')}!很高兴为您服务~" expression = 1 elif 12 <= hour < 18: welcome = f"下午好,{self.config_manager.get('user_name')}!今天过得怎么样?" expression = 3 else: welcome = f"晚上好,{self.config_manager.get('user_name')}!今天辛苦啦~" expression = 4 # 添加启动问候语 startup_greeting = random.choice(self.greetings["startup"]) welcome = f"{welcome} {startup_greeting.format(name=self.config_manager.get('user_name'))}" self.live2d_controller.trigger_expression(expression) self.speak(welcome, priority=True) except Exception as e: print(f"欢迎语功能出错: {e}") def shutdown_greeting(self): """关机问候语""" if not self.config_manager.get("enable_shutdown_greeting", True): return try: self.scheduler_running = False shutdown_message = random.choice(self.greetings["shutdown"]) formatted_message = shutdown_message.format(name=self.config_manager.get("user_name")) self.live2d_controller.trigger_expression(5) # 使用一个温和的表情 self.speak(formatted_message, priority=True) # 给语音足够的时间播放 time.sleep(3) except Exception as e: print(f"关机问候出错: {e}") def start_listening(self): if self.is_listening: return self.is_listening = True threading.Thread(target=self.voice_listen_thread).start() def voice_listen_thread(self): try: if self.microphone is None: self.speak("麦克风未初始化,无法进行语音识别", priority=True) return self.speak("我在听呢,请说吧~", priority=True) with self.microphone as source: self.recognizer.adjust_for_ambient_noise(source) audio = self.recognizer.listen(source, timeout=5, phrase_time_limit=5) text = self.recognizer.recognize_google(audio, language='zh-CN') self.process_user_input(text) except sr.WaitTimeoutError: self.speak("好像没有说话呢~", priority=True) except sr.UnknownValueError: self.speak("抱歉,我没有听清楚,能再说一次吗?", priority=True) except Exception as e: print(f"语音识别错误: {e}") self.speak("出了点小问题,请再试一次~", priority=True) finally: self.is_listening = False def process_user_input(self, text): if "几点" in text or "时间" in text: self.announce_time() elif "你好" in text or "嗨" in text: self.speak(f"{self.config_manager.get('user_name')},你好呀!") self.live2d_controller.trigger_expression(1) elif "谢谢" in text: self.speak("不客气,很高兴能帮到你~") self.live2d_controller.trigger_expression(3) else: self.speak("让我想想...", priority=True) response = self.get_chat_response(text) self.speak(response) def get_chat_response(self, message): try: api_url = "https://api.ownthink.com/bot" data = {"spoken": message} response = requests.post(api_url, data=data, timeout=5) response.encoding = 'utf-8' result = response.json() return result['data']['info']['text'] except Exception as e: print(f"聊天API错误: {e}") return "抱歉,我现在有点困惑,请稍后再试。" def speak(self, text, priority=False): if self.tts_engine is None: print(f"TTS引擎未初始化,无法朗读: {text}") return try: self.tts_engine.say(text) self.tts_engine.runAndWait() except Exception as e: print(f"语音输出错误: {e}") # 系统托盘图标类 class SystemTrayIcon: def __init__(self, assistant): self.assistant = assistant self.icon = None self.create_tray_icon() def create_tray_icon(self): try: # 创建一个更明显的图标 image = Image.new('RGBA', (64, 64), (255, 255, 255, 0)) draw = ImageDraw.Draw(image) # 绘制一个更显眼的图标 draw.rectangle((16, 16, 48, 48), fill=(255, 105, 180, 255)) # 热粉色背景 draw.ellipse((24, 24, 40, 40), fill=(255, 255, 255, 255)) # 白色圆形 draw.ellipse((28, 28, 36, 36), fill=(0, 0, 0, 255)) # 黑色中心 menu = ( item('语音聊天', self.voice_chat), item('测试报时', self.test_announce), item('设置', self.open_settings), item('退出', self.quit) ) self.icon = pystray.Icon("live2d_assistant", image, "Live2D桌面助手", menu) except Exception as e: print(f"创建系统托盘图标时出错: {e}") traceback.print_exc() # 创建备用图标 try: image = Image.new('RGB', (16, 16), color='red') self.icon = pystray.Icon("live2d_assistant", image, "Live2D桌面助手", menu) except Exception as e2: print(f"创建备用图标也失败: {e2}") def voice_chat(self, icon, item): self.assistant.start_listening() def test_announce(self, icon, item): self.assistant.announce_time(test=True) def open_settings(self, icon, item): # 这里可以添加打开设置窗口的功能 print("打开设置功能待实现") self.assistant.speak("设置功能尚未实现") def quit(self, icon, item): # 退出前执行关机问候 self.assistant.shutdown_greeting() self.icon.stop() os._exit(0) # 确保程序完全退出 def run(self): try: if self.icon: self.icon.run() else: print("图标创建失败,程序将继续在后台运行") # 程序继续运行但没有图标 while True: time.sleep(10) except Exception as e: print(f"运行系统托盘时出错: {e}") traceback.print_exc() def check_single_instance(): """确保只有一个程序实例在运行""" try: # 创建互斥体 mutex = win32api.CreateMutex(None, False, "Live2DDesktopAssistantMutex") if win32api.GetLastError() == win32con.ERROR_ALREADY_EXISTS: mutex = None print("程序已经在运行中") # 显示提示信息 ctypes.windll.user32.MessageBoxW(0, "Live2D桌面助手已经在运行中,请检查系统托盘区域", "提示", 0x40) return False return True except Exception as e: print(f"检查单实例时出错: {e}") return True def main(): try: # 检查是否已有实例运行 if not check_single_instance(): return print("正在初始化COM...") pythoncom.CoInitialize() print("COM初始化完成") print("正在创建DesktopAssistant实例...") assistant = DesktopAssistant() print("DesktopAssistant实例创建完成") print("正在创建系统托盘图标...") tray_icon = SystemTrayIcon(assistant) print("系统托盘图标创建完成") print("程序已启动,请在系统托盘中查看图标") # 添加启动通知 try: if tray_icon.icon: tray_icon.icon.visible = True # 显示通知 tray_icon.icon.notify("Live2D桌面助手已启动", "点击托盘图标与助手互动") except Exception as e: print(f"显示通知时出错: {e}") tray_icon.run() except Exception as e: print(f"程序运行出错: {e}") traceback.print_exc() # 显示错误消息 ctypes.windll.user32.MessageBoxW(0, f"程序启动失败: {str(e)}", "错误", 0x10) finally: try: pythoncom.CoUninitialize() except: pass if __name__ == "__main__": # 如果是直接运行,而不是被导入 if hasattr(sys, 'frozen'): # 如果是打包后的可执行文件 # 改变当前工作目录到可执行文件所在目录 os.chdir(os.path.dirname(sys.executable)) main()为什么在系统托盘没看见图标
最新发布
09-12
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值