| 1 | = Porting to Python 3 = |
| 2 | |
| 3 | This is still a proposal at this stage. |
| 4 | |
| 5 | == Motivation == |
| 6 | |
| 7 | * Make code behave the same on Python 2 and Python 3, insofar as one can, so e.g. `map()` is the same on Python 2 and Python 3 (i.e. lazy). |
| 8 | * Reduce errors by relying on Python 2 behavior and tests as well as manual review. |
| 9 | * Try to reduce grunt work. |
| 10 | |
| 11 | == How to choose a module to port == |
| 12 | |
| 13 | TBD, something involving core abstractions first, then dependency graph topological traversal. |
| 14 | |
| 15 | Assume for now we've picked a module. |
| 16 | |
| 17 | == The porting process, big picture == |
| 18 | |
| 19 | For a module M, there is also a corresponding module T, the unittests for M. |
| 20 | If the tests for M are embedded into a module that tests multiple modules, step one is to split off the tests so there's T that only tests M. |
| 21 | |
| 22 | Then: |
| 23 | |
| 24 | 1. Update T to run on both 2+3 (see below for what that looks like). |
| 25 | 2. Run T's tests on Python 2. They should still pass! If they don’t, something broke. |
| 26 | 3. Port the code module M. |
| 27 | 4. Now run T's tests on Python 3. |
| 28 | 5. Fix any problems caught by the tests. |
| 29 | 6. Add both M and T to `allmydata/util/_python3.py`. |
| 30 | 7. Submit for code review. |
| 31 | |
| 32 | |
| 33 | === Porting a specific Python file === |
| 34 | |
| 35 | **First**, add explicit byte or unicode annotations for strings where needed. |
| 36 | |
| 37 | **Second**, run `futurize --write --both-stages --all-imports path/to/file.py`. |
| 38 | |
| 39 | **Third**, replace the `from builtins import *` variant, if any, with: |
| 40 | |
| 41 | {{{ |
| 42 | #!python |
| 43 | from future.utils import PY2 |
| 44 | if PY2: |
| 45 | from builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, int, list, object, range, str, max, min # noqa: F401 |
| 46 | }}} |
| 47 | |
| 48 | This adds builtins that match Python 3's semantics. The `#noqa: F401` keeps flake8/pyflakes from complaining about unused imports. We do unused imports so that people changing code later don't have to manually check if `map()` is old style or new style. |
| 49 | |
| 50 | **Fourth**, manually review the code. Futureize is nice, but it very definitely doesn't catch everything, or it makes wrong decisions. |
| 51 | |
| 52 | In particular: |
| 53 | |
| 54 | * `map()`, `filter()`, etc. are now lazy. |
| 55 | * `dict.keys()` and friends now return a view of the underlying data, rather than a list with a copy. |
| 56 | |
| 57 | **Fifth**, add a note to the module docstring saying it was ported to Python 3. |