"Where is my script?" sounds like a question with one answer. It has four. __file__ is the script's own path. os.getcwd() is the user's shell directory. sys.argv[0] is whatever the user typed on the command line. sys.executable is the Python interpreter (or the PyInstaller bootloader for frozen apps). They give different answers in different contexts, and using the wrong one is the source of countless "FileNotFoundError when I run from a different directory" bug reports. This entry covers the canonical 2026 answer (Path(__file__).resolve().parent), why os.getcwd() is the wrong tool for this job, how PyInstaller-frozen apps change the picture, and the modern importlib.resources pattern for packaged data files. Part of our Stdlib Essentials guide.
Key Takeaways
Canonical idiom:HERE = Path(__file__).resolve().parent. Put it at the top of every script.
Don't use os.getcwd() for script-relative paths. It returns the user's shell directory, not yours.
Always call .resolve() to convert relative paths to absolute and follow symlinks.
For PyInstaller frozen apps, use sys._MEIPASS when sys.frozen is set.
For data files inside an installed package, prefer importlib.resources over __file__-based paths.
Same scenario, four method calls, four different answers. The bottom row is the only one you should use for script-relative paths.
The Canonical 2026 Answer
One idiom, put at the top of every script:
from pathlib import Path
HERE = Path(__file__).resolve().parent
From there, locate files that ship with your script using path arithmetic:
config = HERE / "config" / "default.json"
template = HERE / "templates" / "report.html"
data = HERE.parent / "data" / "input.csv" # sibling to HERE
The HERE name is a convention; some codebases use SCRIPT_DIR, BASE_DIR (Django), or PROJECT_ROOT. The pattern is the same: compute once at module load, then use throughout.
What Each Method Returns
Four methods, four different jobs. Knowing what each one actually represents prevents the wrong-tool bugs.
os.getcwd(): the user's shell directory
os.getcwd() returns the current working directory at the moment of the call. That's wherever the user's shell was when they ran your script. If they were in /tmp and ran python /home/jesse/myapp/main.py, os.getcwd() returns /tmp.
This is correct for "files the user gave me by relative path on the command line" (an input file argument, for example). It is WRONG for "files that ship with my script". Treating cwd as the script's location is the most common bug in this area.
sys.argv[0]: the command-line invocation
sys.argv[0] is the first argument the shell handed to Python: the script name as the user typed it. The value depends on the invocation:
It might be relative, it might be absolute, it might be a name that doesn't exist (if the script was deleted between invocation and execution). Use it for help messages or logging the command line, not for locating the script.
__file__: the module's own path
__file__ is the module-level constant Python sets to the path of the .py file being executed. It's the right answer to "where is this code", with two caveats:
It may be a relative path ('../scripts/main.py') depending on how the script was invoked. Wrap with .resolve() to make it absolute.
It's NOT defined in some contexts: the REPL, code passed to exec(), code passed via python -c. In those cases, accessing it raises NameError.
For normal script and module execution, __file__ always exists and points to your file.
sys.executable: the Python interpreter
sys.executable is the path to the Python interpreter that's running. Useful for spawning subprocesses that need the same Python (covered in the subprocess guide). It's NOT your script; it's the Python binary. For PyInstaller frozen apps, it points to the frozen bootloader.
Why .resolve() Matters
Without .resolve(), you can end up with a path that looks fine on Python's side but breaks when joined with cwd later:
# User ran: python ../scripts/main.py
# __file__ is '../scripts/main.py' (RELATIVE)
Path(__file__).parent
# PosixPath('../scripts') - useless without context
Path(__file__).resolve().parent
# PosixPath('/home/jesse/scripts') - absolute, usable anywhere
.resolve() does two things: convert relative to absolute (using the cwd at the time of the call, which is one of the rare correct uses of cwd), and follow symbolic links to the real target. The second matters when the script is symlinked from /usr/local/bin to /opt/myapp/main.py; .resolve() gives you the real location, not the symlink's directory.
Use .absolute() instead of .resolve() if you specifically want to keep symlinks unresolved (rare; usually you want resolve).
PyInstaller Frozen Applications
When PyInstaller bundles your code into a single executable, __file__ points to a temporary extraction directory, not where the user's executable lives. The runtime info docs at pyinstaller.org describe the full picture; the relevant patterns are:
import sys
from pathlib import Path
def script_dir() -> Path:
if getattr(sys, "frozen", False):
# Running inside a PyInstaller bundle
return Path(sys._MEIPASS)
return Path(__file__).resolve().parent
HERE = script_dir()
The PyInstaller bootloader sets sys.frozen=True and stores the bundle's extraction directory in sys._MEIPASS. If you only ever run from source, you don't need this. If you ship binaries, it's mandatory.
Separately, if you need to know where the user's actual executable is (for log files next to it, for example), use sys.executable, which points to the bootloader rather than the temp directory.
The Modern Alternative: importlib.resources
For data files (templates, schemas, default configs) that ship INSIDE a Python package, the modern approach isn't __file__-based paths at all. It's importlib.resources, which works across normal installs, editable installs, zipped wheels, and PyInstaller bundles uniformly.
from importlib import resources
# Read a file shipped inside the package
text = resources.files("mypackage.data").joinpath("config.json").read_text()
# Get a Path-like object for files outside read-text use
with resources.as_file(resources.files("mypackage.data") / "logo.png") as path:
process_image(path)
The files() function returns a Traversable object that behaves like a Path, regardless of whether the package is on disk, in a zip, or extracted by PyInstaller. Use this for any "data file that ships with my code" need. Reserve __file__-based paths for standalone scripts that aren't part of an installable package.
Real-World Patterns
Loading a config file next to the script
from pathlib import Path
import json
HERE = Path(__file__).resolve().parent
config = json.loads((HERE / "config.json").read_text())
Simple, works from anywhere, immune to cwd changes.
Locating a sibling data directory
# Project layout:
# project/
# src/main.py
# data/input.csv
HERE = Path(__file__).resolve().parent
DATA = HERE.parent / "data"
input_file = DATA / "input.csv"
Walking up with .parent is the canonical way to express "sibling directory".
Setting Django's BASE_DIR
# settings.py
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
Django generates this in new projects. Two .parents because settings.py lives one level inside the project root.
Reading a package resource (modern way)
from importlib import resources
import json
# config.json is inside mypackage/data/
config = json.loads(resources.files("mypackage.data").joinpath("config.json").read_text())
No __file__ needed; works inside zips and bundles.
Jupyter Notebooks: __file__ Doesn't Work
Notebooks are a special case. By default, __file__ isn't defined in a Jupyter notebook because the notebook isn't a single file from Python's perspective; each cell runs through exec(). Trying Path(__file__).resolve().parent inside a notebook cell raises NameError.
The workarounds:
Use cwd intentionally:Path.cwd() works in notebooks because Jupyter sets cwd to the notebook's directory by default.
Hardcode the project root at the top of the notebook: PROJECT = Path('/home/user/myproject').resolve().
Use the IPython magic%pwd or %cd for interactive directory management.
For scripts converted to notebooks or vice versa, abstracting the path lookup into a small function makes the codebase portable:
def project_root() -> Path:
try:
return Path(__file__).resolve().parent.parent
except NameError:
# Running in notebook or REPL
return Path.cwd()
Python 3.9+: __file__ is Now Absolute by Default
A subtle but useful change in Python 3.9: __file__ is now guaranteed to be an absolute path when the script is executed normally. Before 3.9, it could be relative depending on how the script was invoked, which is why the .resolve() habit became standard. The change makes .resolve() partly redundant for direct script execution on modern Python, but it still matters for symlink resolution and for code that might be imported through unusual paths. The recommendation stays the same: keep .resolve() in your standard idiom. It's microseconds of overhead, it handles symlinks correctly, and it makes the code portable to older interpreters.
Common Mistakes
1. Using os.getcwd() to find the script
The most common bug. os.getcwd() returns the user's shell directory, which is rarely where your script lives. Use Path(__file__).resolve().parent.
2. Skipping .resolve()
HERE = Path(__file__).parent # might be relative
config = HERE / "config.json" # later code joins with cwd; breaks
Always include .resolve(). The overhead is microseconds.
3. Using sys.argv[0] as the script path
It's whatever the user typed, including possibly empty or wrong. Use __file__.
4. Forgetting PyInstaller frozen-app handling
Code that works from source breaks when packaged. Wrap with the sys.frozen check if you ever ship binaries.
5. Using __file__ for resources inside an installed package
It works in development but fails when the package is installed inside a zip (some deployment tools do this). Use importlib.resources for package data.
Frequently Asked Questions
How do I find the directory a Python script is in?
Use Path(__file__).resolve().parent. __file__ is the module-level constant Python sets to the script's path, .resolve() turns it into an absolute path with all symlinks resolved, and .parent extracts the directory. The full canonical idiom is: from pathlib import Path; HERE = Path(__file__).resolve().parent. Place this near the top of your module and use HERE / 'data' / 'config.json' to locate sibling files. The older os.path.dirname(os.path.abspath(__file__)) is equivalent and still works.
What is the difference between __file__ and os.getcwd() in Python?
__file__ is the path to the script that contains the code; it's constant for any given module and points to where the .py file lives on disk. os.getcwd() returns the current working directory, which is wherever the user's shell was when they ran the script. They can be completely different: a user in /tmp running python /home/jesse/myapp/main.py gets cwd='/tmp' and __file__='/home/jesse/myapp/main.py'. For locating files that ship with your script (data files, templates, config defaults), use __file__. For files the user supplied via a relative path argument, use cwd.
Does __file__ work in PyInstaller frozen executables?
Partially. In a PyInstaller one-file frozen app, __file__ points to a temporary extraction directory under sys._MEIPASS, not where the user's executable is. PyInstaller sets sys.frozen=True and sys._MEIPASS to the bundle's temporary directory. The portable pattern is: if getattr(sys, 'frozen', False): HERE = Path(sys._MEIPASS) else: HERE = Path(__file__).resolve().parent. For locating the executable the user actually launched (separate from bundled resources), use sys.executable. The PyInstaller runtime-information docs cover the full set of platform paths.
When is __file__ not defined in Python?
__file__ is not defined in three main contexts: the interactive REPL (you typed at the python prompt, there's no file), code passed via exec() of a string (no associated file path), and code passed via the python -c 'code' command-line flag (no file). In those contexts, accessing __file__ raises NameError. If your code might run in such a context, guard with try/except NameError or check globals().get('__file__') before using it. In normal script and module execution, __file__ is always present.
Should I use Path(__file__).parent or Path(__file__).resolve().parent?
Use Path(__file__).resolve().parent for production code. The .resolve() call does two important things: it converts a possibly-relative __file__ value to an absolute path, and it follows symlinks to the real location. Without .resolve(), if the script was invoked via python ../scripts/main.py, __file__ is the relative '../scripts/main.py' and joining it with cwd later can produce wrong paths. .resolve() also handles symlinks correctly: if the script is invoked via a symlink, Path(__file__).parent gives the symlink's parent while Path(__file__).resolve().parent gives the real script's parent. The latter is almost always what you want.
The Bottom Line: HERE = Path(__file__).resolve().parent
One idiom handles 95% of the "where is my script" cases. Put it at the top of every script that loads sibling files; use HERE / 'data' / 'file' for script-relative paths. Reserve os.getcwd() for user-supplied arguments. Add the sys.frozen branch when you ship PyInstaller bundles. Reach for importlib.resources when data files ship inside an installable package. Next: parsing strings to int and float safely. Or browse the full Stdlib Essentials guide.
Lock In Path Reflexes Through Practice
CodeGym's Python track turns path patterns into reflex across 62 levels: HERE conventions, importlib.resources for packaged data, PyInstaller-safe code, cwd-vs-script awareness. AI validators catch the os.getcwd() anti-pattern and missing .resolve() calls; AI mentors explain why your test fails when run from a different directory. First level free; full plan on the pricing page.
GO TO FULL VERSION