勇哥注:
这个系列贴子,勇哥站在C#开发角度来学习python。
我以C#常用知识点来类比python,共5篇。
你看完后,就明白,所谓python简单易学只是表面。这门语言的语法体量很大不输给C#,它其实是很好的把复杂性隐藏了,给人入门简单的印象,其实想精通它困难是比较大的。
C#开发者快速学习python(1):入口函数,运算符,模块与包,py文件 ,基础数据类型,列表
C#开发者快速学习python(2):枚举,装饰器,self,类的三种类型,特殊方法,oop,代码格式,方法 ,方法返回类型,识别函数返回类型
C#开发者快速学习python(3):有无数组?字符串方法,读写文件,序列化,配置文件,异常处理,循环和选择,模块与包,调试手段
C#开发者快速学习python(4):与C#不同点,鸭子类型,多线程,多进程编程,访问控制,事件委托实现,lambda表达式,常用内置库,为啥说python简单易用?
C#开发者快速学习python(5):if的bool判断, 变量的作用域范围, 格式字符串, 弹窗, 列表推导式, 一个点歌小程序
(六)一个点歌小程序:
从这个小程序,可以看到python的图形界面程序开发,还是很方便的。前提条件是有一个强大的AI IDE。
勇哥用的是Trae。它解决了语法提示、方便的格式化代码能力。
如果没有ai ide的这些能力,硬敲这些代码还是不容易的。
界面:
源码:
import os import sys from PySide6.QtWidgets import QApplication, QWidget, QMessageBox from PySide6.QtGui import QIcon from mtvUI import Ui_Form from PySide6.QtWidgets import QStyle class MTVMainWindow(QWidget,Ui_Form): def __init__(self): super().__init__() self.setupUi(self) self.setWindowTitle("MTV点歌") # 使用自定义图标文件 # self.setWindowIcon(QIcon("icon.ico")) # 使用正确的图标常量 self.setWindowIcon(self.style().standardIcon(QStyle.SP_ComputerIcon)) # 其他常用的内置图标常量列表: # QStyle.SP_MessageBoxQuestion - 问号图标 # QStyle.SP_MessageBoxWarning - 警告图标 # QStyle.SP_MessageBoxCritical - 错误图标 # QStyle.SP_ComputerIcon - 计算机图标 # QStyle.SP_DesktopIcon - 桌面图标 # QStyle.SP_DriveFDIcon - 软盘驱动器图标 # QStyle.SP_DriveHDIcon - 硬盘驱动器图标 # QStyle.SP_DriveCDIcon - CD驱动器图标 # QStyle.SP_DriveNetIcon - 网络驱动器图标 # QStyle.SP_TrashIcon - 回收站图标 # QStyle.SP_DirOpenIcon - 打开文件夹图标 # QStyle.SP_DirClosedIcon - 关闭文件夹图标 # QStyle.SP_FileIcon - 文件图标 # QStyle.SP_FileDialogStart - 文件对话框开始图标 # QStyle.SP_FileDialogEnd - 文件对话框结束图标 # QStyle.SP_FileDialogToParent - 向上一级图标 #self.setGeometry(100, 100, 800, 600) # 这行被注释了,所以不会设置特定位置 # 注意:PySide6的窗口默认情况下并不总是在屏幕中央显示 # 窗口显示在屏幕中央可能是由以下原因导致的: # 1. Qt Designer中.ui文件的设置(在mtvUI.ui中可能有相关配置) # 2. 操作系统的窗口管理器行为 # 3. 某些平台上QWidget.show()的默认行为 # 为了确保窗口总是居中,我们可以显式添加以下代码: self.center() def center(self): # 获取屏幕的几何信息 qr = self.frameGeometry() # 获取屏幕中心点 cp = QApplication.primaryScreen().availableGeometry().center() # 将窗口中心点移动到屏幕中心点 qr.moveCenter(cp) # 将窗口左上角移动到计算出的位置 self.move(qr.topLeft()) self.btnAddSelectMusic.clicked.connect(self.on_pushButton_2_clicked) self.btnRefMusic.clicked.connect(self.on_pushButton_3_clicked) self.listAllMusic.itemDoubleClicked.connect(self.on_listAllMusic_itemDoubleClicked) def on_pushButton_3_clicked(self): # 刷新音乐列表 music_files = self.getAllMusicDir() self.setMusicList(music_files) def on_pushButton_2_clicked(self): #self.label.setText("点歌成功!") QMessageBox.information(self, "提示", "点歌成功!") def on_listAllMusic_itemDoubleClicked(self, item): # 双击列表项时,将其添加到选中列表 selected_music = item.text() self.listSelectedMusic.addItem(selected_music) #C:\Program Files (x86)\SPlayer\SPlayer.exe # 构建完整的文件路径 music_path = os.path.join("F:/KuGou", selected_music) # 使用subprocess模块播放音乐,更安全且能更好地处理路径 import subprocess try: # 使用subprocess.Popen可以更好地处理带空格的路径 # 这里使用Windows默认播放器播放音乐 #subprocess.Popen(["start", "", music_path], shell=True) # 如果想要尝试使用特定播放器,可以取消下面的注释并注释掉上面的行 subprocess.Popen(["C:/Program Files (x86)/SPlayer/SPlayer.exe", music_path]) except Exception as e: # 捕获异常并显示错误信息 QMessageBox.warning(self, "错误", f"无法播放音乐: {str(e)}") def getAllMusicDir(self): # 列出所有音乐文件 import os music_dir = "F:/KuGou" if not os.path.exists(music_dir): os.makedirs(music_dir) music_files = [f for f in os.listdir(music_dir) if f.endswith(".mkv")] return music_files def setMusicList(self, music_files): self.listAllMusic.clear() self.listAllMusic.addItems(music_files) if __name__ == "__main__": app = QApplication(sys.argv) window = MTVMainWindow() window.show() sys.exit(app.exec())
效果:
点击“刷新歌曲”按钮,从磁盘目录读取所有格式为"mkv"的歌曲。
双击列表项目,打开Splayer.exe播放器播放歌曲。
(五)列表推导式
def getAllMusicDir(self): # 列出所有音乐文件 import os music_dir = "F:/KuGou" if not os.path.exists(music_dir): os.makedirs(music_dir) music_files = [f for f in os.listdir(music_dir) if f.endswith(".mkv")] return music_files
下面这段代码就是列表推导式:
music_files = [f for f in os.listdir(music_dir) if f.endswith(".mkv")]
Python中的**列表推导式(List Comprehension)**语法,是一种简洁高效的创建列表的方法。
让我详细解释:
music_files = [f for f in os.listdir(music_dir) if f.endswith(".mkv")]
# 语法结构分析:
1. 1.
整体结构 : [表达式 for 变量 in 可迭代对象 if 条件]
- 方括号 [] 表示要创建一个新的列表
- f 是表达式,用于生成列表中的元素
- for f in os.listdir(music_dir) 是遍历部分
- if f.endswith(".mkv") 是条件筛选部分
2. 2.
各部分功能 :
- os.listdir(music_dir) : 获取指定目录下的所有文件和文件夹名称列表
- for f in ... : 遍历这个列表中的每个元素,临时变量 f 代表当前文件名
- if f.endswith(".mkv") : 只保留文件名以".mkv"结尾的文件
- [f for ...] : 将满足条件的文件名收集到一个新的列表中
3. 3.
执行过程 :
- 首先执行 os.listdir(music_dir) 获取目录内容
- 然后对每个文件名 f 进行条件判断
- 只将满足 .endswith(".mkv") 条件的文件名添加到结果列表
- 最终 music_files 变量将包含所有MKV格式的文件名称
#等价的传统写法:
如果不使用列表推导式,相同功能的代码会更冗长:
music_files = [] for f in os.listdir(music_dir): if f.endswith(".mkv"): music_files.append(f)
列表推导式是Python特有的优雅语法,它使代码更简洁、可读性更好,同时执行效率也更高
(四)弹窗,类比C#的MessageBox
看代码:
try: self.db = SongDB() print("数据库连接成功") except Exception as e: QMessageBox.critical(self, "错误", f"数据库连接失败: {str(e)}") print(f"数据库连接失败: {str(e)}") self.db = None
QMessageBox.critical 是 PySide6/Qt 框架中的一个静态方法,用于显示一个严重错误对话框。
在上面代码的中,它被用于当数据库连接失败时向用户显示一个重要的错误信息。
这个方法的主要功能是:
1. 1. 显示一个带有错误图标和"错误"标题的模态对话框
2. 2. 向用户展示具体的错误信息(在本例中是数据库连接失败的原因)
3. 3. 提供一个"确定"按钮让用户关闭对话框
在PySide6/Qt框架中, QMessageBox 提供了多种类型的对话框,用于不同场景下的用户交互。主要包括以下几种类型:
1. 1. QMessageBox.critical :用于显示严重错误消息,通常带有红色错误图标和"错误"标题
2. 2. QMessageBox.warning :用于显示警告消息,通常带有黄色警告图标和"警告"标题
3. 3. QMessageBox.information :用于显示普通信息,通常带有蓝色信息图标和"信息"标题
4. 4. QMessageBox.question :用于显示问题和选项,通常带有问号图标,等待用户确认或取消
这些方法的使用方式类似,都是静态方法,可以直接调用,
式通常为: QMessageBox.类型(父窗口, 标题, 消息内容, 按钮组合) 。
它们的主要区别在于显示的图标、默认标题和适用场景不同,可以根据具体的业务需求选择合适的对话框类型。
(三)格式字符串
看代码:
其中f"数据库连接失败: {str(e)}" 就是一个格式化字符串。
它与C#的格式串差不多,例如这个格式中在C#中为:@"数据库连接失败:{e.ToString()}"
try: self.db = SongDB() print("数据库连接成功") except Exception as e: QMessageBox.critical(self, "错误", f"数据库连接失败: {str(e)}") print(f"数据库连接失败: {str(e)}") self.db = None
在代码中的 f 是 Python 中的格式化字符串(formatted string)标记,也被称为 f-string。
这是 Python 3.6 及以上版本引入的一种字符串格式化方法。
具体来说, f"数据库连接失败: {str(e)}" 中的 f 表示这是一个格式化字符串,它允许在字符串中直接嵌入表达式的值。
花括号 {} 内的内容会被当作 Python 表达式进行计算,并将结果插入到字符串中。
在这行代码中, {str(e)} 会将异常对象 e 转换为字符串,显示异常的具体信息。
这样,最终显示给用户的错误消息就会包含实际的错误原因,而不仅仅是一个静态的"数据库连接失败"文本。
f-string 是 Python 中一种简洁、易读的字符串格式化方式,相比传统的 % 格式化和 .format() 方法更加直观和方便
为了能看懂旧版本代码,这里也介绍一下%和format()两种格式串。
1. %格式化(旧式格式化)
这是Python最早的字符串格式化方法,类似于C语言的printf风格。
语法:
格式字符串 % (值1, 值2, ...)
示例:
name = "张三" age = 25 print("我叫%s,今年%d岁。" % (name, age)) # 输出: 我叫张三,今年25岁。
常用格式说明符:
- %s : 字符串
- %d : 整数
- %f : 浮点数
- %r : 原始表示(使用repr())
2. .format()方法(新式格式化)
Python 2.6+引入的更灵活的格式化方法。
语法:
"格式字符串".format(值1, 值2, ...)
示例:
name = "张三" age = 25 print("我叫{},今年{}岁。".format(name, age)) # 输出: 我叫张三,今年25岁。
高级用法:
- 使用位置参数: "{0} {1}".format("Hello", "World")
- 使用关键字参数: "{name}今年{age}岁".format(name="张三", age=25)
- 使用格式说明: "π≈{:.2f}".format(3.14159) # 保留两位小数
- 访问对象属性: "{person.name}".format(person=person_obj)
与f-string的对比
- f-string(Python 3.6+): f"我叫{name},今年{age}岁" ,最简洁直观
- .format(): 比%格式化更灵活,但代码较长
- %格式化: 旧式语法,功能相对有限,但在一些旧代码中仍可见
在现代Python开发中,f-string通常是首选,因为它更简洁、可读性更好,且执行效率更高。
(二)变量的作用域范围
看下面代码,现在问题是:
为啥self.db在try块中定义,却可以在try的作用域外面使用?
如果换成C#,代码最后那句if self.db是会报错的,因为self.db变量没定义。
def __init__(self): super().__init__() # 设置UI self.setupUi(self) # 设置窗口标题 self.setWindowTitle("MTV音乐播放器") # 初始化数据库连接 try: self.db = SongDB() print("数据库连接成功") except Exception as e: QMessageBox.critical(self, "错误", f"数据库连接失败: {str(e)}") print(f"数据库连接失败: {str(e)}") self.db = None # 连接信号和槽 self.btnUpdateMusic.clicked.connect(self.update_music_list) self.btnAddMusicToSelect.clicked.connect(self.add_music_to_selected) self.txtSearchMusic.textChanged.connect(self.search_music) # 初始化时加载所有歌曲 if self.db: self.update_music_list()
在Python中, self.db 是实例变量,而不是try块的局部变量。
与C#不同,Python没有严格的块级作用域(如try、if、for等块不会创建独立的作用域)。
在Python中,变量的作用域主要分为四种:
1. 1. 局部作用域(函数内)
2. 2. 嵌套作用域(闭包)
3. 3. 全局作用域(模块内)
4. 4. 内置作用域(Python内置函数和变量)
在类的方法中, self.db 作为实例变量,是绑定到实例上的属性,其作用域是整个实例生命周期。
即使在try块中首次赋值,它仍然是实例的属性,可以在try块外部访问。
而在C#中,变量的作用域更为严格,在try块中声明的变量通常只在try块内可见。
这是两种语言在作用域设计上的重要差异。
在示例代码中,即使 self.db = SongDB() 在try块内执行,但它是实例属性的赋值操作,
因此在try块外部(如 if self.db: )仍然可以访问这个属性。
(一)if的bool判断
看下面代码:
这里self.db是一个类的实例,那么它怎么能做为if语句的bool判断呢?
self.db = SongDB() if self.db: self.update_music_list()
在Python中,任何对象都可以直接用于布尔上下文(如if语句的条件判断),这是Python的一种特性。
对于 self.db 能否用于if的布尔值判断,需要理解Python的真值测试规则:
1. 1. 当我们在代码中写 if self.db: 时,Python会自动调用 bool(self.db) 来确定其布尔值。
2. 2. 具体到这个例子:
- 如果数据库连接成功, self.db = SongDB() 会创建一个SongDB类的实例,
此时 bool(self.db) 默认为True(Python中自定义类的实例默认都为True)。
- 如果数据库连接失败,代码会执行 self.db = None ,而 None 在Python中被视为False。
3. 3. 因此, if self.db: 的本质是在检查 self.db 是否为有效的数据库连接实例(不是None)。
这种机制比显式检查 if self.db is not None: 更加简洁,是Python代码中的常见模式。
那是否在SongDB类中要实现bool方法呢?
在Python中,用户定义的类并不是必须实现 __bool__ 方法(即 bool(self.db) 背后调用的方法)。
Python对对象进行布尔值判断时的规则是:
1. 1. 首先检查对象是否实现了 __bool__ 方法,如果有,直接调用该方法的返回值作为布尔值。
2. 2. 如果没有实现 __bool__ 方法,但实现了 __len__ 方法,则调用 __len__ 方法,若返回值为0则布尔值为False,否则为True。
3. 3. 如果两者都没有实现,则默认所有实例对象的布尔值都是True。
SongDB 类其实没有专门实现 __bool__ 或 __len__ 方法,因此它的实例默认被视为True。
这种设计使得大多数情况下我们不需要为自定义类实现布尔判断逻辑,
只有当我们需要特殊的布尔行为时,才需要显式定义 __bool__ 方法。

