#🔒 Os.path.join doesn’t work the same on MacOS vs Windows

286 messages · Page 1 of 1 (latest)

thorny dune
#

Hi, I coded a game in python and I need to load images so I used os.path.join since I was told that was cross-compatible. However, when my friend( who is on windows ) tried to run the game, he was met with my exception_handling message. I have put try except whenever an image is loaded and he was met with my inbuilt error message. We checked everything and he has the correct files, in the correct place and everything should work but it doesn’t. You can check my game’s code here The only thing that I believe is the issue is that I used relative paths but he is running main.py from the same directory that the Assets folder is in, so I think the relative paths should work, but they don’t. I don’t really know how python differs on operating systems so I do need help on this. Thanks!

GitHub

A game where you dodge bullets that are coming down from the top of the screen. Can only be used with Keyboard. - Spacexplorer11/Space_Dodge

onyx bladeBOT
#

@thorny dune

Python help channel opened

Remember to:

  • Ask your Python question, not if you can ask or if there's an expert who can help.
  • Show a code sample as text (rather than a screenshot) and the error message, if you've got one.
  • Explain what you expect to happen and what actually happens.

:warning: Do not pip install anything that isn't related to your question, especially if asked to over DMs.

brisk gull
#

what error specifically?

#

if you want to make sure your paths are resolved to the scripts directory, you should use __file__ variable, that contains the path to the script file that variable is used in

#

typically you have something like:

from pathlib import Path

script_dir = Path(__file__).absolute().parent
``` and `script_dir` is then the absolute path to the scripts directory.
#

or similar in os.path:

script_dir = os.path.dirname(os.path.abspath(__file__))
``` I think
#

haven't used os.path for a while

thorny dune
#

Hi, the error isn’t given in the terminal since my script handles it and from my script I can see it’s a FileNotFound since that’s the only error my script handles

thorny dune
brisk gull
#

is your script handling your errors? or hiding them?
if it's the latter, don't do that. let the script fail and show or log the entire traceback

#

what do you meam, it refused?

#

again no errors?

thorny dune
#

Basically, whenever I need to load an image, I have a try except thing that handles FileNotFound errors, so I don’t see the actual log and it rather displays a message on the screen saying that [this image] wasn’t found

brisk gull
#
pygame.transform.scale(pygame.image.load(os.path.join("Assets", "Mute.png"))
``` this is a relative path.
you need to add the example script_wdir at the beginning.
#

ideally you'd do: os.path.join(script_dir, "Assets", "Mute.png")

#

where script_dir is defined at the top of you Main.py file

thorny dune
#

Hi, I did that, but when I ran it on my Mac, it gave me the error on the screen that I had programmed

brisk gull
#

you need tracebacks. don't hide your errors

thorny dune
#

So you are saying I should remove my try except thing and run the code with script_dir on my Mac and tell you the error?

brisk gull
#

you could use the logging module, to log your exceptions to a file

thorny dune
#

Also, how you do you format the code on discord?

thorny dune
brisk gull
#

to use the logger in the simplest way, in any python file you simply add these two lines:

from logging import getLogger
logger = getLogger(__name__)

and in your Main.py as early as possible you do the above and additionally:

import os
import logging

script_dir = os.path.dirname(os.path.abspath(__file__))

logfile = os.path.join(script_dir, 'mylog.log')
logging.basicConfig(filename=logfile, level=logging.INFO)
onyx bladeBOT
#
Formatting code on Discord

Here's how to format Python code on Discord:

```py
print('Hello world!')
```

These are backticks, not quotes. Check this out if you can't find the backtick key.

For long code samples, you can use our pastebin.

brisk gull
#

then you can do stuff like:

try:
    ...
except SomeException:
    logger.exception('some extra message here')
#

that shoul log the message with the entire exception attached to it

thorny dune
#

Ok, just to check I should put os.path.join(script_dir, “Assets”, “Mute.png”)

#

Right?

brisk gull
#

yes, assuming script_dir is defined as above, in your Main.py

#

the __file__ variable is always the path to the script file, where you use that variable in... so if you define script_dir in a subfolder, you may need to add a .. to get up one directory: os.path.join(script_dir, '..', 'Assets', 'Mute.png')

#

personally I prefer to just have a centralized function, and resolve everything from there.

thorny dune
#

oh, ok

brisk gull
#

I'd basically do: pygame.image.load(ref("Assets/Mute.png"))
and make sure to use forward slashes: / .. as those work on windows as well...
then the ref function might just use the path together with join and __file__ to resolve to the correct location.

thorny dune
thorny dune
brisk gull
#

that actually creates a logger. and you add those two lines to every python file where you need a logger

#

the basicConfig only goes in your Main.py though. that you only do once on startup 🙂

thorny dune
#

ok! I did that and I tried it with my mutesymbol, but I got the error it wasn't associated without a value

brisk gull
#

as s side note: you should probably add .idea/ and .DS_Store to your gitignore and remove them from git (using git rm -rf --cached .idea .DS_Store ) ... those shouldn' really go into git 😄

thorny dune
#

this is my code

brisk gull
#

oh any __pycache__ as well

thorny dune
# thorny dune this is my code

muteSymbol = pygame.transform.scale(pygame.image.load(os.path.join(script_dir, "..", "Assets", "Mute.png")), (70, 50))

brisk gull
#

there is a chance that those pycache files actually make it not work on windows

worthy dune
thorny dune
brisk gull
thorny dune
#

ok

brisk gull
thorny dune
#

I will git.ignore that

thorny dune
brisk gull
#

as Main.py is in the same folder there Assets is

thorny dune
#

it loaded just fine

brisk gull
#

I told you that the extra ".." is only needed, if the python file you define the script_dir in happens to be in a subfolder (relative to the Assets folder)

#

in doubt: Always Be Printing

thorny dune
#

When i used that, a log was generated

#

also, I need to load stuff inside Draw.py, which is inside, "Drawing" so will i need ".."?

brisk gull
#

mylog.log should probably be in gitignore as well 😄

brisk gull
#

you could also like suggested, have a utility function somewhere, that all locations can import.

thorny dune
#

I would love to have that kind of function, I tried, but I couldn't figure out how that'd work

thorny dune
brisk gull
#

we could put it in File_Handling, in Utils.py or something like that:

import os

file_handling_dir = os.path.dirname(os.path.abspath(__file__))
project_dir = os.path.dirnamd(file_handling_dir)

def ref(path):
    path = path.lstrip('/\\')  # remove any leading slashes
    path = path.replace('\\', '/')  # windows backslash to forward slashes, as windows supports both
    return os.path.join(project_dir, path)

(the code above is untested)
then you do:

from File_Handling.Utils import ref

playerR = pygame.transform.scale(pygame.image.load(ref("Assets/Player_R.png"), "PlayerR"),
                                 (PLAYER_WIDTH, PLAYER_HEIGHT))
``` instead of os.path.join.
I wouldn' worry about the slashes too much, as long as you only use forward slashes.
brisk gull
#

it only removed the files from your git history, so you need to commit the removal

thorny dune
#

oh, ok

brisk gull
#

(and add those two entries to .gitignore)

thorny dune
brisk gull
#

it basically just does an os.path.join

#

the strip and replace is not too important

thorny dune
#

btw, Thank you soooooooooo much for helping me so far!

brisk gull
#

you could even remove them

brisk gull
thorny dune
brisk gull
#

dirname

#

typo

thorny dune
#

oh, ok

brisk gull
#

you should be able to fix that youself 😄

thorny dune
#

yeah, obviously

#

😄

#

ok, the ref function works for me, but I need someone else to test it on Windows

#

do I still need script_dir or can I leave that?

brisk gull
#

in that case you don't

#

ref should do all the work

thorny dune
#

ok

brisk gull
#

and it should work from any location, without any extra ".." in there 🙂

#

(because it's resolve from the Utils.py file)

thorny dune
#

You said use "\" right? not "/"

brisk gull
#

forward slashes

#

not backslashes

thorny dune
#

ok

brisk gull
#

backslashes are windows

#

so use / (forward slashes) everywhere

#

windows can use them as well

thorny dune
brisk gull
#

well, \ is a backslash.

#

usually you call / just a slash, unless you want to explicitly separate it from backslashes... so you call it forward slash 🙂

thorny dune
#

I don't need import logging anymore, right?

brisk gull
#

only in your main.py where you use logging.basicConfig

thorny dune
#

ok, but can I replace ```py

logger.exception('some extra message here')withpy
error = "Sound Effects"
running = False
draw_except(error)```

brisk gull
#

yes. or at least add it before

#

it's up to you what you do with the error afterwards, as long as you never hide any error

thorny dune
#

ok, then what is the need ```py
script_dir = os.path.dirname(os.path.abspath(file))

logfile = ref(script_dir, 'mylog.log')
logging.basicConfig(filename=logfile, level=logging.INFO)```

#

of that

brisk gull
#

well, you don't need script_dir there

#

ref does that all for you

#

ref is supposed to replace all your os.path.join and any local "script_dir" variable, as it's all centralized

thorny dune
brisk gull
#

yes

thorny dune
#

ok

#

Thanks, I just need someone to test it for me once I commit the changes

#

would this work?

#
title_screen_music_check = os.path.exists(ref("Sounds/Background_music/Title_screen/Title_screen_music.mp3"))```
brisk gull
#

yes

#

ref just returns a full path to a file. you can always do:

print(ref("Sounds/....music.mp3"))
``` to see what you get (should output a full system path)
thorny dune
#

ok

#

it does indeed!

brisk gull
#

and because it's based in __file__, it should always adjust to the users current system. 🙂

thorny dune
#

wait, how do I have to add a logger to each file or should I just import from main.py

brisk gull
thorny dune
#

ok, I will add it to the files

brisk gull
#

right. and just so you know. logger.exception is only for exceptions. there are other logger functions like logger.debug, .info, .warning, .error

#

in that order. and depending on the loglevel in your basicConfig, only log messages of that configured level or higher will be logged.

thorny dune
#

willl py logging.getLogger() be the same as py from logging import getLogger()

brisk gull
#

no

thorny dune
#

ok

brisk gull
#
from logging import getLogger
logger = getLogger(__name__)
#

that is what you need. Exactly like that.

#

the __name__ is important, as it adds the module name where the logging happened (otherwise you'd search forever)

thorny dune
#

ok

#

Should I add you as a credit in the commit message or not?

brisk gull
#

nah, not needed 🙂

thorny dune
#

ok

brisk gull
#

I'm here for helping, not for credit 🙂

thorny dune
#

Thank you so much! I'll get my friend to test it later!

#

Bye!

brisk gull
thorny dune
#

is there anything I can do to repay you?

#

I really appreciate the time and effort you've given me

brisk gull
#

no, don't worry about stuff like that. 🙂

thorny dune
#

Thank you!

#

!eval print("thank you!")

onyx bladeBOT
thorny dune
#

uhh, I tried to commit and push, I got this error py error: Your local changes to the following files would be overwritten by merge: Drawing/Exception_Handling/__pycache__/draw_exception.cpython-312.pyc Drawing/Pause_Menu/__pycache__/pause_function.cpython-312.pyc Drawing/Title_screen/__pycache__/draw_title_screen.cpython-312.pyc Drawing/Tutorial_and_Information/__pycache__/Keybindings.cpython-312.pyc Drawing/Tutorial_and_Information/__pycache__/Welcome.cpython-312.pyc Drawing/__pycache__/draw.cpython-312.pyc File_Handling/__pycache__/Saving.cpython-312.pyc Pause_Menu/__pycache__/pause_function.cpython-312.pyc Sounds/Background_music/.DS_Store Title_screen/__pycache__/draw_title_screen.cpython-312.pyc Tutorial_and_Information/__pycache__/Information.cpython-312.pyc Merge with strategy ort failed.

brisk gull
#

uh

#

you may not have committed some stuff

#

did you add your stuff to .gitignore?

#

After adding the following to gitignore:

.idea/
__pycache__/
*.py[cod]
.DS_Store

try to run in this order:

git add .gitignore
git rm -rf --cached .DS_Store .idea *.py[cod]
git add .
git commit -m 'your commit message'
thorny dune
#

The commit went through, the push failed

brisk gull
#

pull first?

#

it's like a door. If you can't push, pull instead 😄

worthy dune
brisk gull
#

well yea, depends on the tool that creates it.

chilly basin
#

You should really be using importlib.resources.files

thorny dune
chilly basin
thorny dune
#

I can't pull either, cuz i get this error:

error: Your local changes to the following files would be overwritten by merge:
Drawing/Exception_Handling/pycache/draw_exception.cpython-312.pyc Drawing/Pause_Menu/pycache/pause_function.cpython-312.pyc Drawing/Title_screen/pycache/draw_title_screen.cpython-312.pyc Drawing/Tutorial_and_Information/pycache/Keybindings.cpython-312.pyc Drawing/Tutorial_and_Information/pycache/Welcome.cpython-312.pyc Drawing/pycache/draw.cpython-312.pyc File_Handling/pycache/Saving.cpython-312.pyc Pause_Menu/pycache/pause_function.cpython-312.pyc Sounds/Background_music/.DS_Store Title_screen/pycache/draw_title_screen.cpython-312.pyc Tutorial_and_Information/pycache/Information.cpython-312.pyc
Merge with strategy ort failed.

chilly basin
#

You need to gitignore your pyc files

thorny dune
#

I have:

/File_Handling/highscore.pickle
mylog.log
Drawing/__pycache__/draw.cpython-312.pyc
Drawing/Tutorial_and_Information/__pycache__/Welcome.cpython-312.pyc
Drawing/Tutorial_and_Information/__pycache__/Keybindings.cpython-312.pyc
Drawing/Title_screen/__pycache__/draw_title_screen.cpython-312.pyc
Drawing/Pause_Menu/pause_function.py
Drawing/Pause_Menu/__pycache__/pause_function.cpython-312.pyc```
#

Those are the files in git.ignore

chilly basin
#

Show your .gitignore?

#

What does git status say

chilly basin
thorny dune
# chilly basin What does `git status` say

it says

On branch main
Your branch and 'origin/main' have diverged,
and have 1 and 1 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        renamed:    Exception_Handling/__pycache__/draw_exception.cpython-312.pyc -> Drawing/Exception_Handling/__pycache__/draw_exception.cpython-312.pyc
        new file:   Drawing/Pause_Menu/__pycache__/pause_function.cpython-312.pyc
        new file:   Drawing/Title_screen/__pycache__/draw_title_screen.cpython-312.pyc
        new file:   Drawing/Tutorial_and_Information/__pycache__/Keybindings.cpython-312.pyc
        new file:   Drawing/Tutorial_and_Information/__pycache__/Welcome.cpython-312.pyc
        modified:   Drawing/__pycache__/draw.cpython-312.pyc
        modified:   File_Handling/__pycache__/Saving.cpython-312.pyc
        deleted:    Pause_Menu/__pycache__/pause_function.cpython-312.pyc
        renamed:    .DS_Store -> Sounds/Background_music/.DS_Store
        deleted:    Title_screen/__pycache__/draw_title_screen.cpython-312.pyc
        deleted:    Tutorial_and_Information/__pycache__/Information.cpython-312.pyc

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .DS_Store
        .idea/
        File_Handling/__pycache__/Utility.cpython-312.pyc```
thorny dune
chilly basin
thorny dune
chilly basin
#

Show the full contents of .gitignore

#

!paste

onyx bladeBOT
#
Pasting large amounts of code

If your code is too long to fit in a codeblock in Discord, you can paste your code here:
https://paste.pythondiscord.com/

After pasting your code, save it by clicking the Paste! button in the bottom left, or by pressing CTRL + S. After doing that, you will be navigated to the new paste's page. Copy the URL and post it here so others can see it.

thorny dune
#

that's what's in the git.ignore file

chilly basin
#

Use the official GitHub python gitignore

thorny dune
chilly basin
thorny dune
#

how do i use that?

chilly basin
#

Paste it into your .gitignore

thorny dune
#

the whole file?

chilly basin
#

Yeah

thorny dune
#

ok

chilly basin
#

And use git restore --staged on your pyc files as git status recommended

thorny dune
chilly basin
#

What's this

#

Oh you probably want to combine them both

thorny dune
#

ok

#

ok, done that now

chilly basin
#

Show git status?

#

@thorny dune

thorny dune
#
On branch main
Your branch and 'origin/main' have diverged,
and have 1 and 1 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   Drawing/__pycache__/draw.cpython-312.pyc
        deleted:    Exception_Handling/__pycache__/draw_exception.cpython-312.pyc
        modified:   File_Handling/__pycache__/Saving.cpython-312.pyc
        deleted:    Pause_Menu/__pycache__/pause_function.cpython-312.pyc
        deleted:    Title_screen/__pycache__/draw_title_screen.cpython-312.pyc
        deleted:    Tutorial_and_Information/__pycache__/Information.cpython-312.pyc

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .idea/
        Drawing/Exception_Handling/__pycache__/
        File_Handling/__pycache__/Utility.cpython-312.pyc
        Sounds/Background_music/.DS_Store

no changes added to commit (use "git add" and/or "git commit -a")
#

I can't pull or push, I need help with that

chilly basin
#

You need to clean up your staged files first

#

Use git restore --staged for your modified pyc files

#

It also looks like your .gitignore hasn't been updated?

#

Did you amend a commit?

thorny dune
#

No...

chilly basin
#

Where's your .gitignore?

thorny dune
#

on github?

chilly basin
#

No the one on your disk

#

Paste the content

thorny dune
#

I just pushed all my changes to github

chilly basin
#

!paste

onyx bladeBOT
#
Pasting large amounts of code

If your code is too long to fit in a codeblock in Discord, you can paste your code here:
https://paste.pythondiscord.com/

After pasting your code, save it by clicking the Paste! button in the bottom left, or by pressing CTRL + S. After doing that, you will be navigated to the new paste's page. Copy the URL and post it here so others can see it.

chilly basin
#

And I can see that your .gitignore doesn't have the JetBrains or Python gitignore content

thorny dune
#

I just did push it as a new commit so it should've updated

chilly basin
#

Show what you ran and the output

thorny dune
#

the output of the push?

chilly basin
#

Yes

thorny dune
#
11:50:26.807: [Space Dodge] git -c credential.helper= -c core.quotepath=false -c log.showSignature=false push --progress --porcelain origin refs/heads/main:main
Enumerating objects: 5, done.
Counting objects:  20% (1/5)
Counting objects:  40% (2/5)
Counting objects:  60% (3/5)
Counting objects:  80% (4/5)
Counting objects: 100% (5/5)
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects:  33% (1/3)
Compressing objects:  66% (2/3)
Compressing objects: 100% (3/3)
Compressing objects: 100% (3/3), done.
Writing objects:  33% (1/3)
Writing objects:  66% (2/3)
Writing objects: 100% (3/3)
Writing objects: 100% (3/3), 2.40 KiB | 2.40 MiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas:   0% (0/1)        
remote: Resolving deltas: 100% (1/1)        
remote: Resolving deltas: 100% (1/1), completed with 1 local object.        
remote: This repository moved. Please use the new location:        
remote:   https://github.com/Spacexplorer11/Space_Dodge.git        
To https://github.com/TNTISGOOD/Space_Dodge.git
     refs/heads/main:refs/heads/main    baeb0bb..9a4296c
Done
chilly basin
#

Oh it looks like you got git pull working

thorny dune
#

yeah

#

it did work

#

thanks!

chilly basin
#

Ok now delete all your __pycache__ and pyc and .DS_store files from your repo

thorny dune
#

is there a command for that?

#

or do I do it manually?

chilly basin
#

You could build something with find and xargs

#

But it's probably best to do it manually

thorny dune
#

ok

chilly basin
#

Also it's a good idea to put all your python code into one package

#

Eg move your Main.py to space_doge/__main__.py and move your Assets and File_Handling to space_doge/assets and space_dodge/file_handling/ and run with python -m space_dodge

thorny dune
#

What do you mean, in one package? If possible, please open a pull request, it would help a lot in explaining and make it simpler

chilly basin
thorny dune
#

What are packages?

chilly basin
#

A directory of python code

#

But it's badly named and badly located

#

All the python code and assets for your project should sit inside one main package

thorny dune
#

how is "file_handling" a package, it's a directory?

chilly basin
onyx bladeBOT
#

Main.py line 13

from Drawing.Exception_Handling.draw_exception import draw_except```
chilly basin
#

That shows you're using Drawing and Drawing.Exception_Handling as a package

thorny dune
#

oh

chilly basin
#

It should say from space_dodge.drawing.exception_handling import draw_except instead

#

But you need to move and rename your directories to do that

thorny dune
#

can you not open a pull request, because I don't understand how to do that

chilly basin
#

I'm on my phone so I can't do it

#

mkdir a space_dodge directory

#

And move Main.py into space_dodge/__main__.py

#

Then move all your Assets and other packages into space_dodge renaming to lowercase

#

Then run your code with python -m space_dodge

#

You'll see that you'll need to fix up your imports

thorny dune
#

so, make a directory called "Space_Dodge" inside Space_Dodge

chilly basin
#

No space_dodge

thorny dune
#

name it "space_dodge"

#

lowercase

#

then rename everything to be lowercase

chilly basin
#

Then move Main.py into space_dodge/__main__.py

thorny dune
#

and move it inside that?

#

so I need to create a directory called __main.py__

chilly basin
#

__main__.py is a file

#

You should look more carefully at the position and casing of letters when programming. They're all important

thorny dune
#

name my main.py to __main__.py

chilly basin
#

Your Main.py space_dodge/__main__.py

thorny dune
#

I really don't understand, could you maybe open a pull request later?

chilly basin
#
mkdir space_dodge
git mv Main.py space_dodge/__main__.py
thorny dune
#

done

#

now what?

chilly basin
#

Now move your Assets and other python directories into space_dodge

#

Making them lowercase as you do

#

And Sounds

thorny dune
#

so I need to rename all my files to lowercase and move them inside "space_dodge"

#

but why?

#

I don't need to move my LICENSE and gitignore right?

brisk gull
#

pep-8

chilly basin
#

No they stay at the root

thorny dune
thorny dune
brisk gull
#

it's really just a matter of code style. I wouldn't bother for your current project, but keep it in mind for future projects

#

generally pep8 says: filenames, function names and variable names all use snake_case format. Only class names use CamelCase (without any underscores).
There are also some other things in pep-8, but those are the most prominent ones.

thorny dune
#

ok, I'll rename everything anyway

#

thank you so much!

#

I'm closing this post now

#

Thank you all sooooo much for your help!

brisk gull
thorny dune
#

!close

onyx bladeBOT
#
Python help channel closed

This help channel has been closed and it's no longer possible to send messages here. If your question wasn't answered, feel free to create a new post in #1035199133436354600. To maximize your chances of getting a response, check out this guide on asking good questions.