RUNTIME VALUE CONVERSIONS

File-processing workflows

Read UTF-8 text, process it with ordinary converted functions, and write the result. Helpers may live in the same document or in explicitly supplied modules.

Select Transpile → Runtime, then click Transpile. File access happens when you compile and run the generated C. Relative paths use that program's working directory. For direct typed C file entry points, see Reading and writing files.

Read, process, write

This self-contained example creates names.txt, cleans its lines, and writes clean-names.txt. Run it in a directory where those example filenames may be created or overwritten.

def load_names(path):
    with open(path, encoding="utf-8") as source:
        return [line.strip() for line in source if line.strip()]

def save_names(path, names):
    with open(path, "w", encoding="utf-8", newline="") as output:
        return output.write("\n".join(sorted(names)) + "\n")

def main():
    with open("names.txt", "w", encoding="utf-8", newline="") as seed:
        seed.write(" pear \n\napple\n café \n")
    names = load_names("names.txt")
    written = save_names("clean-names.txt", names)
    print(f"Saved {len(names)} names in {written} characters.")
    return 0

Expected terminal output: Saved 3 names in 16 characters. The output file contains:

apple
café
pear

The read result remains usable after the input handle closes. Lists, dictionaries, sorting, numeric parsing, lambdas, and formatting work between the read and the write.

File operations

Use open directly in a with statement. Supply encoding="utf-8". The mode is a literal "r" (the default), "w", or "a". Paths are runtime strings, so a function parameter or calculated string can choose the file. Optional errors="strict" makes the decoding policy explicit.

OperationResult and behavior
read(), read(size)Text from the current cursor. A positive size counts Unicode characters, zero returns empty text, and a negative size or None reads to EOF.
readline(), readline(size)One line, including its line ending when present, limited by an optional integer character count. Empty text means EOF unless size is zero.
readlines(), readlines(hint)A list of remaining lines. A positive integer hint stops after the collected character count exceeds the hint; zero, a negative hint, or None reads all lines.
for line in handleRead successive lines. Also works with list/tuple, comprehensions, enumerate, zip, and other admitted iterable consumers.
iter(handle), next(handle, default)The handle is its own iterator. Aliases and helpers share the same cursor. Without a default, EOF reports StopIteration through the context error channel.
write(text)Write a string and return its Unicode character count. No newline is added automatically.
writelines(iterable)Write each string in order; return None. Supply any desired newlines in the strings.
flush()Flush buffered output; return None. A successful flush is not a filesystem durability guarantee.
close(), closedClose early or inspect the Boolean closed state. Repeated close is harmless. The with statement still owns cleanup.

Method arguments are positional. For example, use handle.read(20). Mode "w" creates or truncates; mode "a" creates or appends. Both require a writable destination. A write or close failure may leave a partial output file.

Unicode and newlines

Reads require valid UTF-8. Size arguments and write results count Unicode characters, while resource limits count bytes. Embedded NUL characters are supported in file contents; paths cannot contain them. Unicode filenames are accepted.

Open optionReadingWriting
Omit newline, or newline=NoneRecognize LF, CRLF, and CR, returning each as LF.Translate LF to the host's normal line ending: CRLF on Windows, LF on Linux/macOS.
newline=""Recognize the same line endings and preserve their exact characters.Write the supplied UTF-8 bytes without newline translation.
def main():
    with open("unicode.txt", "w", encoding="utf-8", newline="") as out:
        print(out.write("é🙂\r\nend\r"))
    with open("unicode.txt", encoding="utf-8") as source:
        print(repr(source.read(2)))
        print(source.readlines())
    return 0

Expected output:

8
'é🙂'
['\n', 'end\n']

A complete report across files

Save the three Python blocks with the filenames shown. Create the UTF-8 input file prices.txt in the directory where you will run the program. These rows use a simple comma separator; quoted CSV fields are outside this example's format.

prices.txt — input

pear,7.00
apple,2.50
plum,5.35

main.py — entry point

from text_files import load_rows, save_report
from pricing import make_report

def main():
    rows = load_rows("prices.txt")
    report = make_report(rows)
    count = save_report("report.txt", report)
    print(f"Wrote {count} characters to report.txt")
    return 0

text_files.py — file helpers

def load_rows(path):
    with open(path, encoding="utf-8") as source:
        return [line.strip().split(",") for line in source if line.strip()]

def save_report(path, text):
    with open(path, "w", encoding="utf-8", newline="") as output:
        return output.write(text)

pricing.py — processing and formatting

TITLE = "Fruit prices"

def make_report(rows):
    prices = {name: float(value) for name, value in rows}
    lines = [TITLE]
    for number, name in enumerate(sorted(prices), 1):
        lines.append(f"{number:02d} {name:<8} {prices[name]:.2f}")
    lines.append(f"Total: {sum(prices.values()):.2f}")
    return "\n".join(lines) + "\n"

In the workspace, open all three Python files with module IDs main, text_files, and pricing. Make main.py primary, select Runtime, and transpile. Save the combined C as report.c. The equivalent terminal conversion is:

pycforge convert main.py --runtime-values --module text_files=text_files.py --module pricing=pricing.py --output report.c
gcc -std=c11 -O1 -DPYCF_RUNTIME_MAIN report.c -lm -o report

Run ./report on Linux/macOS or .\report.exe in Windows PowerShell. The terminal prints Wrote 77 characters to report.txt. The output file is:

Fruit prices
01 apple    2.50
02 pear     7.00
03 plum     5.35
Total: 14.85

Run the same Python entry point with python -c "import main; main.main()" to compare the result. Data files are runtime inputs; only the Python source files are supplied to conversion.

Handles and cleanup

File contexts may appear inside branches and loops, nest, or use multiple open calls in one with statement. Each opened handle closes when its context exits normally, through return, break, continue, or a runtime error. Multiple handles close in reverse acquisition order. A failure to open a later handle also closes earlier handles being exited.

Pass an active handle to another converted function when it should share the cursor. That function may read or write it; returning from the helper leaves the caller's handle open. Explicitly closing any alias closes the shared handle. A saved handle or bound method remains a closed value after its owning context exits, and later I/O reports an error. Returned text and collections keep their ordinary context lifetime.

Limits and errors

ResourceDefault limit
Input bytes consumed per open handle1 MiB, cumulative across reads and line iteration; includes newline lookahead.
Retained file-read buffer capacity per C context2 MiB, including capacity reserved for growth and terminators. Read results remain allocated until pv_destroy.
Simultaneously open handles8 per context.
File operations1,024 per context. Each open/read/readline/readlines/write/writelines/flush call or file-iterator step counts once, including an EOF step. Cleanup and close are always permitted.

The Python API's ResourcePolicy configures these bounds before conversion; see File-workflow limits through the API. The generated C embeds the selected bounds. The general memory and work budgets also apply. These bounds do not impose an I/O timeout; a host filesystem operation can block.

Unsupported forms include standalone open calls, binary/update/exclusive modes, seek/tell, other encodings or newline options, descriptor access, custom context managers, and filesystem discovery. Use explicit string paths and direct with-open statements. PYC4620 identifies a rejected open/context form.

Open/read/write/close failures, invalid UTF-8, closed-handle use, and budget failures set PC.error, PC.error_source, and PC.error_line. The first error survives cleanup. A C caller checks the context after every call and destroys it after failure; the optional standalone main reports the error and exits unsuccessfully. Python try/except recovery is unsupported. See Runtime C use.