How to Open a File in Python (Modes, Encoding, Paths)

You open a file in Python with the built-in open() function: pass it the file path, a mode such as "r" or "w", and an encoding. Wrap it in a with block so Python closes the file for you, even when an error happens:

with open("orders.txt", "r", encoding="utf-8") as f:
    text = f.read()

My test machine for this page runs Python 3.12.5 on Windows 11 (locale encoding cp1252), and that last detail comes back in the encoding section.

In this tutorial, we’ll go through what open() returns, every file mode, the + modes, encodings, binary files, relative and absolute paths, and the errors you’ll hit when a file won’t open.

Open a file with open() and the with statement

open() doesn’t read anything by itself. It returns a file object, and you read or write through that object. I’m using a small orders.txt file with three customer orders in it:

with open("orders.txt", encoding="utf-8") as f:
    print("name:    ", f.name)
    print("mode:    ", f.mode)
    print("encoding:", f.encoding)
    print("closed?  ", f.closed)
    for line in f:
        print("  ", line.rstrip())

print("closed after with?", f.closed)

Output:

name:     orders.txt
mode:     r
encoding: utf-8
closed?   False
   1001,Emily Carter,Austin TX,49.99
   1002,James Miller,Denver CO,120.50
   1003,Olivia Brown,Boston MA,15.25
closed after with? True

The file object tells you its name, mode and encoding. Looping over it gives one line at a time, which keeps memory use low on big files.

Once the with block ends, f.closed is True. That’s why I treat with as the default way to open files. How it works under the hood is covered in Python with open as file.

You can still write f = open(...) and call f.close() yourself, but it’s easy to forget on an early return. The ways to do that safely are in closing a file in Python.

The arguments open() takes

The full signature is open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None). In day-to-day code you’ll only set four of them:

  • file – a string path or a pathlib.Path.
  • mode – what you want to do: read, write, append or create, in text or binary.
  • encoding – how text bytes are decoded, almost always "utf-8".
  • newline – set it to "" when you hand the file to the csv module.

Everything else has a sensible default. The CSV case shows up in creating a CSV file in Python, where newline="" stops blank rows on Windows.

Which file mode should you use in open()?

The mode string decides whether Python reads, wipes, adds to or creates the file. Here’s one file going through x, a, r and w in turn:

from pathlib import Path

Path("report.txt").unlink(missing_ok=True)       # start fresh for the demo

with open("report.txt", "x", encoding="utf-8") as f:   # x: create, fail if it exists
    f.write("Q1 sales: $18,420.00\n")

with open("report.txt", "a", encoding="utf-8") as f:   # a: add to the end
    f.write("Q2 sales: $21,075.50\n")

with open("report.txt", "r", encoding="utf-8") as f:   # r: read (the default)
    print("after x + a:\n" + f.read())

with open("report.txt", "w", encoding="utf-8") as f:   # w: wipe, then write
    f.write("Q3 sales: $19,980.25\n")

with open("report.txt", encoding="utf-8") as f:
    print("after w:\n" + f.read())

try:
    open("report.txt", "x")
except FileExistsError as err:
    print("x mode again ->", type(err).__name__, "-", err.strerror)

Output:

after x + a:
Q1 sales: $18,420.00
Q2 sales: $21,075.50

after w:
Q3 sales: $19,980.25

x mode again -> FileExistsError - File exists
Command Prompt output of Python open() creating a file with x mode, appending with a, reading with r and overwriting with w
The a line lands after the x line, w wipes both, and a second x raises FileExistsError.

The surprise for most people is w. It empties the file the moment it opens, before you write a single character. If you only meant to add lines, you’ve lost the old data.

Use x when you must not clobber an existing file, like a daily export. The other patterns for that are in creating a new file in Python, and the deliberate version is overwriting a file in Python.

ModeDoesIf the file existsIf it’s missing
"r"Read (default)Opens at the startFileNotFoundError
"w"WriteEmpties it firstCreates it
"a"AppendWrites go to the endCreates it
"x"CreateFileExistsErrorCreates it
"b"Binary (add to any mode)Bytes in, bytes out–
"t"Text (the default)Strings in, strings out–
"+"Read and write (add to any mode)See next section–

Open a file for reading and writing with r+, w+ and a+

Adding + to a mode lets one file object read and write. The three versions differ in where they start and whether they wipe the file:

with open("orders.txt", "r+", encoding="utf-8") as f:
    first = f.readline()
    print("r+ read: ", first.strip(), "| position", f.tell())
    f.seek(0, 2)                      # jump to the end before writing
    f.write("1004,Ava Davis,Seattle WA,72.00\n")

with open("orders.txt", "a+", encoding="utf-8") as f:
    print("a+ starts at", f.tell(), "->", repr(f.read()))   # at the end: nothing to read
    f.seek(0)
    print("after seek(0):", len(f.readlines()), "lines")

with open("orders.txt", "w+", encoding="utf-8") as f:
    print("w+ emptied it:", repr(f.read()))

Output:

r+ read:  1001,Emily Carter,Austin TX,49.99 | position 35
a+ starts at 139 -> ''
after seek(0): 4 lines
w+ emptied it: ''

r+ starts at the beginning, so I jump to the end with seek(0, 2) before adding order 1004. Without that, the write would land after the first line I’d just read.

a+ starts at the end, so read() returns an empty string until you seek(0). And w+ still empties the file first, just like plain w.

Honestly, I rarely use the + modes. Reading in one block and writing in another is easier to follow. To edit one line in place, see replacing a specific line in a file.

Set the encoding when you open a text file

If you leave out encoding=, Python uses your system’s locale encoding. On a US Windows machine that’s cp1252, not UTF-8, and it quietly garbles anything outside plain ASCII:

import locale

with open("cities.txt", "w", encoding="utf-8") as f:
    f.write("San José, Santa Fe, Coeur d’Alene\n")

print("locale default:", locale.getencoding())

with open("cities.txt") as f:                      # no encoding= : uses the locale
    print("no encoding:   ", f.read().strip())

with open("cities.txt", encoding="utf-8") as f:
    print("utf-8:         ", f.read().strip())

with open("cp1252.txt", "wb") as f:
    f.write("Café $4.50\n".encode("cp1252"))

try:
    with open("cp1252.txt", encoding="utf-8") as f:
        f.read()
except UnicodeDecodeError as err:
    print("UnicodeDecodeError:", err.reason, "at byte", err.start)

with open("cp1252.txt", encoding="utf-8", errors="replace") as f:
    print("errors='replace':", f.read().strip())

Output:

locale default: cp1252
no encoding:    San José, Santa Fe, Coeur d’Alene
utf-8:          San José, Santa Fe, Coeur d’Alene
UnicodeDecodeError: invalid continuation byte at byte 3
errors='replace': Caf� $4.50
Command Prompt output of Python reading a UTF-8 file without encoding, with utf-8, and a cp1252 file raising UnicodeDecodeError
Without encoding=, accented letters come back as mojibake; UTF-8 reads them correctly, and a cp1252 byte breaks a strict UTF-8 read.

Look at the second line: San José came back mangled, and so did the curly apostrophe. Nothing crashed, so this kind of bug can sit in a script for months.

The reverse problem raises UnicodeDecodeError. It shows up when a file saved in an old Windows encoding is read as UTF-8.

If you know the real encoding, pass it. If you just need the text, errors="replace" swaps bad bytes for a question-mark symbol.

Python 3.15 makes UTF-8 the default for files on every platform (PEP 686), so the first read would come out right there. Until all your machines run it, I’d still write encoding="utf-8" every time.

Files that start with a byte order mark

Excel and some Windows tools write a hidden byte order mark (BOM) at the start of UTF-8 files. Read with plain utf-8, it sticks to your first column name:

with open("export.csv", "w", encoding="utf-8-sig") as f:   # what Excel writes
    f.write("customer,total\nEmily,49.99\n")

with open("export.csv", encoding="utf-8") as f:
    print(repr(f.readline()))

with open("export.csv", encoding="utf-8-sig") as f:
    print(repr(f.readline()))

Output:

'\ufeffcustomer,total\n'
'customer,total\n'

The \ufeff on the first line is the BOM. Opening with utf-8-sig strips it, so customer matches when you look up the column. For reading the whole file into a variable, see reading a file as a string in Python.

Open a binary file in rb or wb mode

Images, PDFs, ZIP files and anything else that isn’t text needs "b" in the mode. You get bytes back instead of str, and no decoding happens:

with open("logo.png", "wb") as f:                    # a tiny PNG header for the demo
    f.write(b"\x89PNG\r\n\x1a\n" + bytes(8))

with open("logo.png", "rb") as f:
    header = f.read(8)
    print(type(header).__name__, header)
    print("is PNG:", header == b"\x89PNG\r\n\x1a\n")

try:
    open("logo.png", "rb", encoding="utf-8")
except ValueError as err:
    print("ValueError:", err)

Output:

bytes b'\x89PNG\r\n\x1a\n'
is PNG: True
ValueError: binary mode doesn't take an encoding argument

Checking the first eight bytes is a quick way to confirm a file really is a PNG before you process it. Notice the last line: binary mode doesn’t take an encoding, and Python says so with a ValueError.

Reading whole binary files in chunks is covered in reading a binary file in Python, and the writing side is in writing bytes to a file in Python.

Open a file by relative or absolute path

A bare name like "orders.txt" is a relative path. Python looks for it in the working folder, the folder you ran the command from, not the folder your script lives in:

import os
from pathlib import Path

print("working folder:", Path.cwd())
print("script folder: ", Path(__file__).resolve().parent)

data = Path(__file__).resolve().parent / "data" / "sales.txt"
data.parent.mkdir(exist_ok=True)
data.write_text("Austin,1240.75\n", encoding="utf-8")

with open(data, encoding="utf-8") as f:            # open() accepts Path objects
    print("absolute Path: ", f.read().strip())

with data.open(encoding="utf-8") as f:             # or call Path.open()
    print("Path.open():   ", f.read().strip())

with open(os.path.join("data", "sales.txt"), encoding="utf-8") as f:
    print("relative path: ", f.read().strip())

print("C:\new\temp.txt")      # \n and \t turn into a line break and a tab
print(r"C:\new\temp.txt")     # raw string keeps the backslashes

Output:

working folder: C:\pyguides
script folder:  C:\pyguides
absolute Path:  Austin,1240.75
Path.open():    Austin,1240.75
relative path:  Austin,1240.75
C:
ew	emp.txt
C:\new\temp.txt
Command Prompt output of Python opening a file by absolute pathlib Path, Path.open and a relative os.path.join path, plus raw strings
All three opens read the same data\sales.txt; the broken path at the bottom shows why Windows paths need a raw string.

Here both folders are the same because I ran the script from its own folder. Run it from somewhere else and the relative open fails, while the one built from Path(__file__) keeps working.

So for files that ship with your script, build the path from __file__. There’s more on that in getting the path of the current file in Python and getting the current directory in Python.

The last lines are a Windows trap. In a normal string, \n and \t turn into a line break and a tab, so the path prints split across two lines. Use a raw string (r"C:\new\temp.txt"), forward slashes, or pathlib.

open() takes a Path object directly, and Path.open() takes the same mode and encoding arguments. Joining parts with the os module is covered in os.path.join() in Python.

Handle errors when a file won’t open

Three things usually go wrong: the file isn’t there, you’re not allowed to open it, or the mode string is wrong. Catch the specific exceptions so the message tells you which one it was:

from pathlib import Path

Path("data").mkdir(exist_ok=True)

for target in ["missing.txt", "data"]:
    try:
        with open(target, encoding="utf-8") as f:
            print(f.read())
    except FileNotFoundError:
        print(f"{target}: not found - check the name and the working folder")
    except PermissionError as err:
        print(f"{target}: PermissionError [Errno {err.errno}] - it's a folder or locked")
    except OSError as err:
        print(f"{target}: {type(err).__name__} - {err}")

try:
    open("orders.txt", "rw")
except ValueError as err:
    print("bad mode ->", err)

Output:

missing.txt: not found - check the name and the working folder
data: PermissionError [Errno 13] - it's a folder or locked
bad mode -> must have exactly one of create/read/write/append mode
Command Prompt output of Python open() raising FileNotFoundError for a missing file, PermissionError for a folder and ValueError for a bad mode
A missing file raises FileNotFoundError; on Windows, opening a folder raises PermissionError; and "rw" isn’t a valid mode.

On Windows, trying to open a folder gives PermissionError with errno 13. On Linux and macOS the same mistake raises IsADirectoryError, which is why I keep a general OSError handler last.

Both are subclasses of OSError, so the order of the except blocks matters. If you’d rather check first, see checking if a file exists in Python, and for fixes see handling a file that does not exist.

Open two files at once in one with statement

You can open several files in a single with. Since Python 3.10 you can wrap them in parentheses, one per line, which reads much better than one long line:

with (
    open("orders.txt", encoding="utf-8") as src,
    open("big_orders.txt", "w", encoding="utf-8") as dst,
):
    for line in src:
        order_id, name, city, total = line.strip().split(",")
        if float(total) >= 40:
            dst.write(f"{order_id} {name} ${float(total):,.2f}\n")

with open("big_orders.txt", encoding="utf-8") as f:
    print(f.read().strip())

Output:

1001 Emily Carter $49.99
1002 James Miller $120.50

Both files close when the block ends, in reverse order. I use this pattern for filter-and-copy jobs like pulling orders of $40.00 or more into a new file.

The w mode on the output file means each run starts clean. To build the output from a list instead, see writing a list to a file in Python.

Once a file is open, these guides cover what comes next:

Frequently asked questions

How do I open a file in Python?

Use with open("file.txt", "r", encoding="utf-8") as f: and read or write through f. The file closes when the block ends.

What’s the difference between w and a mode?

w empties the file before writing; a keeps what’s there and adds to the end. Both create the file if it’s missing.

How do I open a file only if it doesn’t already exist?

Use mode "x". It creates the file and raises FileExistsError if the file is already there.

Why do I get UnicodeDecodeError when I open a file?

The bytes aren’t valid in the encoding Python is using. Pass the real encoding, or errors="replace" if you just need the text.

Why can’t Python find my file?

Relative paths start from the working folder, not the script’s folder. Build the path from Path(__file__).parent or use an absolute path.

Where is open() documented?

See open() in the Python docs for every argument and mode.