Lecture du fichier pyc (Python 3.5.2)

Out of respect to the following posts, I'll post this article in English too.

I tried reading pyc content in __pycache__ using the code mentioned above, to understand what the pyc structure looks like in recent days.

However, it was totally unsuccessful due to some mysterious error I don't understand.

$ python --version
Python 3.5.2
$ python show_pyc.py __pycache__/hello.cpython-35.pyc
magic b'160d0d0a'
moddate b'6a393e58' (Wed Nov 30 11:28:58 2016)
source_size: 227
Traceback (most recent call last):
  File "show_pyc.py", line 74, in <module>
    show_file(sys.argv[1])
  File "show_pyc.py", line 70, in show_file
    show_code(marshal.load(f))
ValueError: bad marshal data (unknown type code)

Actually, Ian (in the second article) appropriately mentioned in the comment.

The file format has changed slightly as of Python 3.3+, so the recipe above no longer works. In addition to the two original four-byte fields there is a new four-byte field that encodes the size of the source file as a long.

OK, pyc header after "3.3+" now contains another 4 bytes!

Because of this slight modification, all the documents before Python 3.3 may contain misleading descriptions. For another example, take PEP 3147.

Byte code files contain two 32-bit big-endian numbers followed by the marshaled [2] code object. The 32-bit numbers represent a magic number and a timestamp.

https://www.python.org/dev/peps/pep-3147/

This is not the case anymore. Anyway, the PEP was originally released for Python 3.2, and there was no guarantee pyc format would not change over time.

Here's my modified version of pyc reader.

import binascii
import dis
import marshal
import sys
import time
import types


def get_long(s):
    return s[0] + (s[1] << 8) + (s[2] << 16) + (s[3] << 24)


def show_hex(label, h, indent):
    h = binascii.hexlify(h).decode('ascii')
    if len(h) < 60:
        print('%s%s %s' % (indent, label, h))
    else:
        print('%s%s' % (indent, label))
        for i in range(0, len(h), 60):
            print('%s   %s' % (indent, h[i:i+60]))


def show_code(code, indent=''):
    print('%scode' % indent)
    indent += '   '
    print('%sargcount %d' % (indent, code.co_argcount))
    print('%snlocals %d' % (indent, code.co_nlocals))
    print('%sstacksize %d' % (indent, code.co_stacksize))
    print('%sflags %04x' % (indent, code.co_flags))
    show_hex('code', code.co_code, indent=indent)
    dis.disassemble(code)
    print('%sconsts' % indent)
    for const in code.co_consts:
        if isinstance(const, types.CodeType):
            show_code(const, indent+'   ')
        else:
            print('   %s%r' % (indent, const))
    print('%snames %r' % (indent, code.co_names))
    print('%svarnames %r' % (indent, code.co_varnames))
    print('%sfreevars %r' % (indent, code.co_freevars))
    print('%scellvars %r' % (indent, code.co_cellvars))
    print('%sfilename %r' % (indent, code.co_filename))
    print('%sname %r' % (indent, code.co_name))
    print('%sfirstlineno %d' % (indent, code.co_firstlineno))
    show_hex('lnotab', code.co_lnotab, indent=indent)


def show_file(fname: str) -> None:
    with open(fname, 'rb') as f:
        magic_str = f.read(4)
        mtime_str = f.read(4)
        mtime = get_long(mtime_str)
        modtime = time.asctime(time.localtime(mtime))
        print('magic %s' % binascii.hexlify(magic_str))
        print('moddate %s (%s)' % (binascii.hexlify(mtime_str), modtime))
        if sys.version_info < (3, 3):
            print('source_size: (unknown)')
        else:
            source_size = get_long(f.read(4))
            print('source_size: %s' % source_size)
        show_code(marshal.loads(f.read()))


if __name__ == '__main__':
    show_file(sys.argv[1])

Let the new reader work on the following code.

hello.py


a, b = 1, 0
if a or b:
    print("Hello World")
$ python --version
Python 3.5.2
$ ls -l hello.py
-rwxr-xr-x 1 dmiyakawa 48 Nov 30 12:41 hello.py
$ python -m py_compile hello.py
$ python show_pyc.py __pycache__/hello.cpython-35.pyc
magic b'160d0d0a'
moddate b'574a3e58' (Wed Nov 30 12:41:11 2016)
source_size: 48
code
   argcount 0
   nlocals 0
   stacksize 2
   flags 0040
   code
      6404005c02005a00005a0100650000731800650100722200650200640200
      8301000164030053
  1           0 LOAD_CONST               4 ((1, 0))
              3 UNPACK_SEQUENCE          2
              6 STORE_NAME               0 (a)
              9 STORE_NAME               1 (b)

  2          12 LOAD_NAME                0 (a)
             15 POP_JUMP_IF_TRUE        24
             18 LOAD_NAME                1 (b)
             21 POP_JUMP_IF_FALSE       34

  3     >>   24 LOAD_NAME                2 (print)
             27 LOAD_CONST               2 ('Hello World')
             30 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             33 POP_TOP
        >>   34 LOAD_CONST               3 (None)
             37 RETURN_VALUE
   consts
      1
      0
      'Hello World'
      None
      (1, 0)
   names ('a', 'b', 'print')
   varnames ()
   freevars ()
   cellvars ()
   filename 'hello.py'
   name '<module>'
   firstlineno 1
   lnotab 0c010c01

Note the size of the source file (48) is appropriately embedded in pyc too. That is the new part which is introduced in Python 3.3+ (Sorry I don't know what "+" means here).

This seemed working fine with Python 3.5.2, 3.4.3, 3.3.6, 3.2.6, and 3.6.0b3 on my environment with MacOS Sierra + pyenv. In 3.2.6, obviously, it does not answer source size because it is not embedded in pyc either.

For readers from future: do not rely on the assumption "pyc format won't change", as I did.

Note (2018-02-01)

Python 3.7 or later may have different pyc format, which will be more "deterministic". See the following PEP

https://www.python.org/dev/peps/pep-0552/

Recommended Posts

Lecture du fichier pyc (Python 3.5.2)
[Note] Lecture de fichier ~ Python ~
lecture de fichier externe python
Lecture et écriture de fichiers CSV Python
Fichier python de script
Traitement de fichiers Python
[Python] Opération de fichier / répertoire
Traitement de fichiers en Python
Exécuter automatiquement le fichier python
Lecture du fichier au format pandas
Manipulation de fichiers avec Python
J'ai essayé de lire un fichier CSV en utilisant Python
Lire le fichier csv Python
[Python] Lecture de fichiers CSV
résumé lié à l'opération de fichier python
Dessiner un fichier netCDF avec python
Lire des fichiers .txt avec Python
Tâche AHC (1) Lecture du fichier CSV
Télécharger le fichier csv avec python
Exécuter le script Python à partir du fichier de commandes
Manipulation de chemin de fichier / dossier en Python
Chiffrement facile du contenu des fichiers (Python)
Conseils sur l'entrée / la sortie de fichier Python
Enregistrez le fichier binaire en Python
Téléchargement de fichiers implémenté avec Python + Bottle
Sortie vers un fichier csv avec Python
Créer un fichier binaire en Python
Lire et écrire NetCDF avec Python
Lecture de spécification de taille multiligne avec python
Extraire le fichier targz en utilisant python
Notes pour l'entrée / sortie de fichier Python
Mémo Python ① Opérations sur les dossiers et fichiers
comparaison du module de conversion de fichier exécutable python 2
documentation python lecture HOWTO de programmation de socket
Lire et écrire du CSV avec Python
Fichier CGI Python créé sous Windows
ORC, opérations de fichier Parquet en Python
[Automatisé avec python! ] Partie 2: Fonctionnement des fichiers
Lire et écrire du texte en Python
[Python] Opération de fichier utilisant l'instruction if
Téléchargement de fichiers vers Azure Storage (Python)
[Python] Lecture et écriture de balises d'informations de localisation de photos (GPS Exif du fichier JPG)
Fichier Python CSV Conversion de code de caractère, extraction de nom de fichier, lecture, sortie, opération de fusion
Lisez le fichier avec python et supprimez les sauts de ligne [Notes sur la lecture du fichier]
Erreur due à UnicodeDecodeError lors de la lecture d'un fichier CSV avec Python [Pour les débutants]
[Python] Comment convertir un fichier db en csv
Créer un fichier power simple avec Python
Contrôle exclusif avec fichier de verrouillage en Python
Lire le fichier CSV avec python (Télécharger et analyser le fichier CSV)
Python: lecture de données JSON à partir de l'API Web
Comment convertir Python en fichier exe
Deux règles lors de la lecture de Python (notation de tranche)
[Python] Convertit les délimiteurs de fichier csv en délimiteurs de tabulation
Vérifier l'existence du fichier avec python
"Commerce du système à partir de Python3" lecture du mémo
Convertir un fichier psd en png en Python
Créez rapidement un fichier Excel avec Python #python
Python / numpy> fichier de liste (tableau numpy) sauvegarde / chargement
Copiez le fichier et réécrivez la valeur de la cellule @python