2

Suppose I have the following directory structure:

lib/
  main.py
  module1/
    __init__.py
    class1.py
    foo.txt

My class1.py file defines a class that has a function that reads the text file. My main.py creates an object from the class defined in class1.py and attempts to use the function to read the text file in the module1 folder. However, when I run the main.py file, it says that it can't find the foo.txt file. How do I give main.py access to foo.txt through class1.py?

Alex Peniz
  • 483
  • 5
  • 14
  • Does this answer your question? [How to read a (static) file from inside a Python package?](https://stackoverflow.com/questions/6028000/how-to-read-a-static-file-from-inside-a-python-package) – Tomerikoo Jan 13 '21 at 11:10

2 Answers2

2

I like to use the __file__ attribute of the module. Since you know where foo.txt is located with respect to class1.py, you can simply use

# inside class1.py
from pathlib import Path

file = Path(__file__).resolve().parent / 'foo.txt'

This will work always, no matter where you call the script, or what script is the entry point.

Reasoning

  • Absolute paths should only be used in quick "one time use scripts".
  • Running a script should not (in most cases) rely on the fact in which folder the script is ran.
  • Another way would be to have the static resources in a separate folder, and then read the file using relative paths, like above (Using pathlib is very handy).

Drawbacks

  • You'll need to find a workaround if you plan to build and executable with pyinstaller or alike, since the __file__ attribute is not anymore pointing to a .py file.
Niko Föhr
  • 28,336
  • 10
  • 93
  • 96
1

When you call class1.py from main.py, your working directory is lib/ (or wherever you called main.py from) and not module1.

This means that if you want to access foo.txt you need to specify the path from the working directory, not from where the class1.py is located.


Example:

Assuming that you run main.py from directory lib/:

# class1.py

class Foo():

    def read1(self):
        try:
            f = open('foo.txt')
            print("Successfully opened 'foo.txt'.")
            print(f.read())
        except OSError:
            print("Error when trying to read 'foo.txt'.")

    def read2(self):
        try:
            f = open('module1/foo.txt')
            print("Successfully opened 'module1/foo.txt'.")
            print(f.read())
        except OSError:
            print("Error when trying to read 'module1/foo.txt'.")

if __name__ == '__main__':

    foo = Foo()
    foo.read1()
    foo.read2()
# __init__.py

from module1.class1 import Foo
# main.py

from module1 import Foo

foo = Foo()
foo.read1()
foo.read2()
  • If you are inside module1 and run class1.py:
Successfully opened 'foo.txt'.
...file contents...

Error when trying to read 'module1/foo.txt'.

As you can see read1 worked and not read2.

  • If you are in lib/ and call main.py, you'll get:
Error when trying to read 'foo.txt'.
Successfully opened 'module1/foo.txt'.
...file contents...

This time read2() worked and not read1().

Notes:

  • if you call class1.py directly from lib/module1 then read_file_1() will work instead of read_file_2().
  • you can alleviate this issue completely by using absolute paths*
Djib2011
  • 6,874
  • 5
  • 36
  • 41
  • But they don't open it explicitly in `main.py`. It is opened through a function in `class1.py` – Tomerikoo Jan 13 '21 at 11:13
  • @Tomerikoo yes but this is imported and *called* from `main.py` . – Djib2011 Jan 13 '21 at 11:15
  • Yes but my point is that the function is in `class1.py` (inside a class) which means that the opening of the file is of the form `open('foo.txt')`. Then, as you indicated correctly, when running `main.py` the working directory is `lib` so the above `open` wouldn't work – Tomerikoo Jan 13 '21 at 11:24
  • Unfortunately this doesn't work, Tomerikoo is correct – Alex Peniz Jan 13 '21 at 11:32
  • @AlexPeniz it does work... I added an example so you can run it by yourself. – Djib2011 Jan 13 '21 at 12:07
  • @Tomerikoo yes, but if you change the path ti will work... – Djib2011 Jan 13 '21 at 12:07
  • Calling a different function according to the main module seems an inconvenient workaround... The way to go here is according to the link I provided above with `resources`. Or, if this is not really a package, then just use an absolute path... – Tomerikoo Jan 13 '21 at 12:15
  • @Tomerikoo I don't think it's a good practice for a script to be able to be called from two possible locations. `class1.py` should be **only** called through the `main.py` module and I think the OP never stated that he wanted otherwise. I'm advocating for changing `'foo.txt'` with `'module1/foo.txt'`. The two functions were just to show how it works... Also absolute imports aren't always possible (e.g. when writing a module). – Djib2011 Jan 13 '21 at 13:40
  • I simplified my problem to just a text file. It's actually a Lark grammar file that uses lark.open, so it may not work the same way. For some reason using this method didn't yield the correct result, but thank you for your answer! – Alex Peniz Jan 13 '21 at 17:32