Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions python-pycache/Calculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public class Calculator {
public static void main(String[] args) {
add(3, 4);
}

private static int add(int a, int b) {
return a + b;
}
}
81 changes: 81 additions & 0 deletions python-pycache/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# What Is the __pycache__ Folder in Python?

Source code, shell scripts, and example projects for the [What Is the `__pycache__` Folder in Python?](https://realpython.com/python-pycache/) tutorial on Real Python.

## Setup

You don't need to create a virtual environments because you won't be installing anything.

## Cleanup Scripts

To recursively remove the `__pycache__` folders on macOS and Linux:

```shell
$ ./pyclean.sh
```

To do the same on Windows in PowerShell:

```shell
PS> .\pyclean.ps1
```

## X-Ray of `.pyc` Files

Compile sample bytecode into timestamp-based `.pyc` files:

```shell
$ ./pyclean.sh
$ python -m compileall --invalidation-mode timestamp example-2/
$ python xray.py example-2/__pycache__/arithmetic*.pyc
{'magic_number': b'\xcb\r\r\n',
'magic_int': 3531,
'python_version': '3.12',
'bit_field': 0,
'pyc_type': <PycInvalidationMode.TIMESTAMP: 1>,
'timestamp': datetime.datetime(2024, 3, 28, 17, 8, 22, tzinfo=datetime.timezone.utc),
'file_size': 32}
```

Compile sample bytecode into unchecked hash-based `.pyc` files:

```shell
$ ./pyclean.sh
$ python -m compileall --invalidation-mode unchecked-hash example-2/
$ python xray.py example-2/__pycache__/arithmetic*.pyc
{'magic_number': b'\xcb\r\r\n',
'magic_int': 3531,
'python_version': '3.12',
'bit_field': 1,
'pyc_type': <PycInvalidationMode.UNCHECKED_HASH: 3>,
'hash_value': b'\xf3\xdd\x87j\x8d>\x0e)'}
```

Compile sample bytecode into checked hash-based `.pyc` files:

```shell
$ ./pyclean.sh
$ python -m compileall --invalidation-mode checked-hash example-2/
$ python xray.py example-2/__pycache__/arithmetic*.pyc
{'magic_number': b'\xcb\r\r\n',
'magic_int': 3531,
'python_version': '3.12',
'bit_field': 3,
'pyc_type': <PycInvalidationMode.CHECKED_HASH: 2>,
'hash_value': b'\xf3\xdd\x87j\x8d>\x0e)'}
```

## Java Bytecode Compiler

Compile the source code upfront and run the resulting class file:

```shell
$ javac Calculator.java
$ time java Calculator
```

Let the `java` command handle the compilation:

```shell
$ time java Calculator.java
```
1 change: 1 addition & 0 deletions python-pycache/example-1/project/calculator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import mathematics.geometry # noqa
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def add(a, b):
return a + b
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def sub(a, b):
return a - b
Empty file.
Empty file.
2 changes: 2 additions & 0 deletions python-pycache/example-2/arithmetic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def add(a, b):
return a + b
3 changes: 3 additions & 0 deletions python-pycache/example-2/calculator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from arithmetic import add

add(3, 4)
Empty file.
2 changes: 2 additions & 0 deletions python-pycache/example-3/project/arithmetic/add.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def add_function(a, b):
return a + b
2 changes: 2 additions & 0 deletions python-pycache/example-3/project/arithmetic/sub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def sub_function(a, b):
return a - b
3 changes: 3 additions & 0 deletions python-pycache/example-3/project/calculator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import arithmetic.add # noqa
from arithmetic import add # noqa
from arithmetic.add import add_function # noqa
1 change: 1 addition & 0 deletions python-pycache/pyclean.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Get-ChildItem -Path . -Filter __pycache__ -Recurse -Directory | Remove-Item -Recurse -Force
3 changes: 3 additions & 0 deletions python-pycache/pyclean.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash

find . -type d -name __pycache__ -exec rm -rf {} +
51 changes: 51 additions & 0 deletions python-pycache/xray.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import marshal
from datetime import datetime, timezone
from importlib.util import MAGIC_NUMBER
from pathlib import Path
from pprint import pp
from py_compile import PycInvalidationMode
from sys import argv
from types import SimpleNamespace


def main(path):
metadata, code = load_pyc(path)
pp(vars(metadata))
if metadata.magic_number == MAGIC_NUMBER:
exec(code, globals())
else:
print("Bytecode incompatible with this interpreter")


def load_pyc(path):
with Path(path).open(mode="rb") as file:
return (
parse_header(file.read(16)),
marshal.loads(file.read()),
)


def parse_header(header):
metadata = SimpleNamespace()
metadata.magic_number = header[0:4]
metadata.magic_int = int.from_bytes(header[0:4][:2], "little")
metadata.python_version = f"3.{(metadata.magic_int - 2900) // 50}"
metadata.bit_field = int.from_bytes(header[4:8], "little")
metadata.pyc_type = {
0: PycInvalidationMode.TIMESTAMP,
1: PycInvalidationMode.UNCHECKED_HASH,
3: PycInvalidationMode.CHECKED_HASH,
}.get(metadata.bit_field)
if metadata.pyc_type is PycInvalidationMode.TIMESTAMP:
metadata.timestamp = datetime.fromtimestamp(
int.from_bytes(header[8:12], "little"),
timezone.utc,
)
metadata.file_size = int.from_bytes(header[12:16], "little")
else:
metadata.hash_value = header[8:16]
return metadata


if __name__ == "__main__":
main(argv[1])