F12. What ends up on disk - #130
Merged
Merged
Conversation
The last lesson of the front end part. Everything from F01 onwards went one way, text in and a code object out. This goes the other way: the code object becomes bytes, and those bytes become the same code object again without any of the eleven stages running. It compiles a two line file, reads the .pyc back as bytes, decodes the sixteen byte header field by field, walks the marshalled blob with a fifty line reader written in the lesson, and then assembles a .pyc by hand and imports it with no .py anywhere on disk. Two details worth the trip. Only half the magic number is a number, and the other half is a carriage return and a newline put there so a text mode copy corrupts the check rather than the code. And a wrong magic number raises while a wrong timestamp does not, because one means the bytecode is nonsense and the other only means the source moved on. Three new glossary terms: marshal, pyc file, magic number. Six diagrams. Runs end to end in Pyodide.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The last lesson of the front end part, and the twenty sixth overall. F01 to F11 all went one direction: text in, a code object out, eleven stages in between. This one goes the other way, and it is the reason importing a module a second time is fast.
What the lesson does
It compiles a two line file with
py_compile, then reads the.pycback as bytes and takes it apart. Sixteen bytes of header, four fields, four bytes each, and everything after that is one marshalled code object and nothing else.The header cell prints all four fields next to what they are supposed to match, so the staleness rule is visible rather than described: the mtime and the length in the header are the source file's, and if either drifts the file is thrown away.
Then the magic number, which is the part I did not expect. Only two of its four bytes are the number. The other two are a carriage return and a newline, glued on by
PYC_MAGIC_NUMBER_TOKEN. That is a trap on purpose: if something copies a.pycin text mode on a system that translates line endings, those are the bytes that change, so the file fails the magic check loudly instead of loading and doing something strange three hundred bytes later.The middle of the lesson is marshal itself. One byte per object, seven low bits holding an ascii letter naming the type, top bit meaning "put this in a numbered table, something later may point at it". A cell prints the first byte of nine different values so the letters are visible. Then the sharing: a 66 character string said twice costs 63 bytes less than saying it twice, because the second one is a five byte back reference, and a nested code object costs the same five.
Type
cis sixteen fields in a fixed order, and the order is the entire specification. The lesson writes a fifty line reader for the subset a.pycuses and points it at the file from the top, then checks the filename, the bytecode and the line table againstmarshal.loads. Worth noticing where the last two fields are: the line table and the exception table F11 decoded, sitting at the end of every function in the file.It finishes by writing a
.pycby hand and importing it withSourcelessFileLoader, with no.pyanywhere on disk, and then breaking one in each of the two ways. A wrong magic number raises. A wrong timestamp does not raise at all, it just gets compiled again and the file quietly rewritten, and the cell proves that by reading the bytes back afterwards.Checks
Three cells carry
differsorvariesnotes, because the byte counts move between 3.14 and 3.15 and the timestamps are whenever you ran it. Everything that has to be exact, the fourmatcheslines and the twoTrues at the end, says the same on both.One thing that took a fix: the
('abcd', 'abcd')cell was printing a different first byte on each version, becausew_refrefuses to share an object with exactly one reference to it and a bare literal passed straight todumpshas a different refcount in the two runtimes. Binding it to a name first makes it stable, and it also demonstrates the rule the prose describes.Three new glossary terms:
marshal,pyc file,magic number. Six diagrams,.excalidrawand.svg, all under 1100 px. Runs end to end in Pyodide and in Colab. Both READMEs have their rows.just checkandjust versionsare green locally.This closes the twelve front end lessons on M3, issue #22.