본문 바로가기
PyQt5_

Working with Relative Paths

by 자동매매 2023. 3. 13.

33. Working with Relative Paths

Paths describe the location of files in your filesystem.

When we load external data files into our applications we typically do this using paths. While straightforward in principle, there are a couple of ways this can trip you up. As your applications grow in size, maintaining the paths can get a bit unwieldly and it’s worth taking a step back to implement a more reliable system.

Relative paths

There are two types of path — absolute and relative. An absolute path describes the path entirely from the root (bottom) of the filesystem, while a relative path describes the path from (or relative to) the current location in the filesystem.

It is not immediately obvious, but when you provide just a filename for a file, e.g. hello.jpg, that is a relative path. When the file is loaded, it is loaded relative to the current active folder. Confusingly, the current active folder is not necessarily the same folder your script is in.

In the Widgets chapter we introduced a simple approach for dealing with this problem when loading an image. We used the file built-in to get the path of the currently running script (our application) and then used os functions to first get the directory of our script and then use that to build the full path.

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()) ①

print ("Paths are relative to:", basedir) ②

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 ()

This works well for simple applications where you have a single main script and load relatively few files. But having to duplicate the basedir calculation in every file you load from and use os.path.join to construct the paths everywhere quickly turns into a maintenance headache. If you ever need to restructure the files in your project, it’s not going to be fun. Thankfully there is a simpler way!

ë

Why not just use absolute paths? Because they will only work on

your own filesystem, or a filesystem with exactly the same

structure. If I develop an application in my own home folder

and use absolute paths to refer to files, e.g.

/home/martin/myapp/images/somefile.png, it will only work for

other people who also have a home folder named martin and put

the folder there. That would be a bit strange.

Using a Paths class

The data files your application needs to load are usually fairly structured — there are common types of file to load, or you are loading them for common purposes. Typically you will store related files in related folders to make managing them easier. We can make use of this existing structure to build a regular way to construct paths for our files.

To do this we can create a custom Paths class which uses a combination of attributes and methods to build folder and file paths respectively. The core of this is the same os.path.dirname(file) and os.path.join() approach used above, with the added benefit of being self-contained and easily modifiable.

Take the following code and add it to the root of your project, in a file named 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")

# File loaders.

@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)

ë

To experiment with the paths module you can start up a Python

interpreter in your project root and use from paths import Paths

Now, anywhere in your application you can import the Paths class an use it directly. The attributes base, ui_files, icons, images, and data all return the paths to their respective folders under the base folder. Notice how the icons folder is constructed from the images path — nesting this folder under that one.

ë

Feel free to customize the names and structure of the paths, etc.

to match the folder structure in your own project.

>>> 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'

Z

We don’t create an object instance instance from this class — we

don’t call Paths() — because we don’t need one. The paths are

static and unchanging, so there is no internal state to manage by

creating an object. Notice that the methods must be decorated as

@classmethod to be accessible on the class itself.

The methods ui_file, icon, image and data are used to generate paths including filenames. In each case you call the method passing in the filename to add to the end of the path. These methods all depend on the folder attributes described above. For example, if you want to load a specific icon you can call the Paths.icon() method, passing in the name, to get the full path back.

>>> Paths.icon('bug.png')

'U:\\home\\martin\\books\\create-simple-gui-applications\\code

\\further\\images\\icons\\bug.png'

In your application code you could use this as follows to construct the path and load the icon.

QIcon(Paths.icon('bug.png'))

This keeps your code much tidier, helps ensure the paths are correct and makes

it much easier if you ever want to restructure how your files are stored. For example, say you want to move icons up to the top level folder: now you only need to change the paths.py definition and all icons will work as before.

icons = os.path.join(images, 'icons')

# to move to top level, make icons derive from base instead

icons = os.path.join(base, 'icons')

'PyQt5_' 카테고리의 다른 글

Enums & the Qt Namespace  (0) 2023.03.13
System tray & macOS menus  (0) 2023.03.13
Extending Signals  (0) 2023.03.13
Extending Signals  (0) 2023.03.13
Timers  (0) 2023.03.13

댓글