33. 使用相对路径
路径描述了文件在文件系统中的位置。
当我们将外部数据文件加载到应用程序中时,通常会使用路径来完成这一操作。虽然从理论上讲这很简单,但实际操作中可能会遇到一些问题。随着应用程序规模的扩大,维护这些路径可能会变得有些繁琐,因此值得退一步考虑实施一个更可靠的系统。
相对路径
路径有两种类型——绝对路径和相对路径。绝对路径描述了从文件系统根目录(底部)开始的完整路径,而相对路径则描述了从当前文件系统位置开始的路径(或相对于当前位置的路径)。
这并不明显,但当您仅提供文件名时,例如:hello.jpg
,这实际上是一个相对路径。当文件被加载时,它是相对于当前活动文件夹加载的。令人困惑的是,当前活动文件夹并不一定就是您的脚本所在的文件夹。
在“控件”一章中,我们介绍了一种处理加载图像时出现此问题的简单方法。我们使用内置函数 __file__
获取当前正在运行的脚本(我们的应用程序)的路径,然后使用 os
函数首先获取脚本的目录,再用该目录构建完整路径。
Listing 240. basic/widgets_2b.py
import os
import sys
from PyQt6.QtGui import QPixmap
from PyQt6.QtWidgets import QApplication, QLabel, QMainWindow
basedir = os.path.dirname(__file__)
print("Current working folder:", os.getcwd()) #1
print("Paths are relative to:", basedir) #2
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
widget = QLabel("Hello")
widget.setPixmap(QPixmap(os.path.join(basedir, "otje.jpg")))
self.setCentralWidget(widget)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
对于简单应用程序而言,这种方法效果良好,尤其是当您仅有一个主脚本且需要加载的文件较少时。然而,在加载每个文件时都需要重复计算基目录,并使用 os.path.join
方法在各处构建路径,这很快就会变成维护噩梦。如果您需要重新组织项目中的文件结构,那将是一场噩梦。幸运的是,有一种更简单的方法!
为什么不直接使用绝对路径呢?因为它们只适用于您的文件系统,或者结构完全相同的文件系统。如果我在自己的主目录中开发应用程序,并使用绝对路径来引用文件,例如:
/home/martin/myapp/images/somefile.png
,那么它只会在其他也拥有名为martin
的主目录并将其放置在该目录下的人的系统上生效。这会有点奇怪。
使用 Paths 类
应用程序需要加载的数据文件通常具有一定的结构——加载的文件类型通常较为常见,或者加载这些文件的目的是为了实现常见的功能。通常,您会将相关的文件存储在相关的文件夹中,以便于管理。我们可以利用这种现有的结构,建立一种常规的方法来构建文件的路径。
要实现这一点,我们可以创建一个自定义的 Paths
类,该类通过结合使用属性与方法来分别构建文件夹和文件路径。其核心逻辑与上述使用的 os.path.dirname(__file__)
和 os.path.join()
方法相同,但具有更高的自包含性且易于修改。
现在请您将以下代码添加到项目根目录下的一个名为 paths.py
的文件中。
Listing 241. further/paths.py
import os
class Paths:
base = os.path.dirname(__file__)
ui_files = os.path.join(base, "ui")
images = os.path.join(base, "images")
icons = os.path.join(images, "icons")
data = os.path.join(base, "images")
# 文件加载器.
@classmethod
def ui_file(cls, filename):
return os.path.join(cls.ui_files, filename)
@classmethod
def icon(cls, filename):
return os.path.join(cls.icons, filename)
@classmethod
def image(cls, filename):
return os.path.join(cls.images, filename)
@classmethod
def data(cls, filename):
return os.path.join(cls.data, filename)
要尝试使用 paths 模块,您可以启动一个 Python 解释器,位于您的项目根目录下,并使用
from paths import Paths
现在,在应用程序的任何位置,您都可以导入 Paths 类并直接使用它。属性 base
、ui_files
、icons
、images
和 data
均返回其对应文件夹在 base
文件夹下的路径。请注意 icons
文件夹是如何从 images
路径构建的——将该文件夹嵌套在该路径下。
您可以自由地自定义路径的名称和结构等,以匹配您自己项目中的文件夹结构。
>>> from paths import Paths
>>> Paths.ui_files
'U:\\home\\martin\\books\\create-simple-gui-applications\\code\\further\\ui'
>>> Paths.icons
'U:\\home\\martin\\books\\create-simple-gui-applications\\code\\further\\images\\icons'
我们不会从这个类创建对象实例——我们不会调用
Paths()
——因为我们不需要它。路径是静态且不变的,因此无需通过创建对象来管理内部状态。注意,方法必须使用@classmethod
装饰器才能在类本身访问。
方法 ui_file
、icon
、image
和 data
用于生成包含文件名的路径。在每种情况下,您都需要调用该方法并传入要添加到路径末尾的文件名。这些方法均依赖于上述描述的文件夹属性。例如,如果您想加载特定的图标,可以调用 Paths.icon()
方法,传入图标名称,以获取完整的路径。
>>> Paths.icon('bug.png')
'U:\\home\\martin\\books\\create-simple-gui-applications\\code\\further\\images\\icons\\bug.png'
在您的应用程序代码中,您可以按照以下方式构建路径并加载图标:
QIcon(Paths.icon('bug.png'))
这使得您的代码更加整洁,有助于确保路径正确,并且如果您以后想重新组织文件的存储方式,会变得更加容易。例如,假设您想将图标移动到顶级文件夹:现在您只需修改 paths.py
中的定义,所有图标仍可正常工作。
icons = os.path.join(images, 'icons')
# 要提升到顶层,请让图标从基类继承。
icons = os.path.join(base, 'icons')