Skip to content

Commit

Permalink
__pycache__: Initial commit (materials)
Browse files Browse the repository at this point in the history
  • Loading branch information
bzaczynski committed Mar 28, 2024
1 parent 11b2f7c commit 4e6fc6e
Show file tree
Hide file tree
Showing 18 changed files with 162 additions and 0 deletions.
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])

0 comments on commit 4e6fc6e

Please sign in to comment.