Is there a way to know by which Python version the .pyc file was compiled?
Question:
Is there any way to know by which Python version the .pyc
file was compiled?
Answers:
The first two bytes of the .pyc
file are the magic number that tells the version of the bytecodes. The word is stored in little-endian format, and the known values are:
Python version
Decimal
Hexadecimal
Comment
Python 1.5
20121
0x994e
Python 1.5.1
20121
0x994e
Python 1.5.2
20121
0x994e
Python 1.6
50428
0x4cc4
Python 2.0
50823
0x87c6
Python 2.0.1
50823
0x87c6
Python 2.1
60202
0x2aeb
Python 2.1.1
60202
0x2aeb
Python 2.1.2
60202
0x2aeb
Python 2.2
60717
0x2ded
Python 2.3a0
62011
0x3bf2
Python 2.3a0
62021
0x45f2
Python 2.3a0
62011
0x3bf2
!
Python 2.4a0
62041
0x59f2
Python 2.4a3
62051
0x63f2
Python 2.4b1
62061
0x6df2
Python 2.5a0
62071
0x77f2
Python 2.5a0
62081
0x81f2
ast-branch
Python 2.5a0
62091
0x8bf2
with
Python 2.5a0
62092
0x8cf2
changed WITH_CLEANUP
opcode
Python 2.5b3
62101
0x95f2
fix wrong code: for x, in ...
Python 2.5b3
62111
0x9ff2
fix wrong code: x += yield
Python 2.5c1
62121
0xa9f2
fix wrong lnotab with for loops and storing constants that should have been removed
Python 2.5c2
62131
0xb3f2
fix wrong code: for x, in ...
in listcomp/genexp
Python 2.6a0
62151
0xc7f2
peephole optimizations and STORE_MAP
opcode
Python 2.6a1
62161
0xd1f2
WITH_CLEANUP
optimization
Python 2.7a0
62171
0xdbf2
optimize list comprehensions/change LIST_APPEND
Python 2.7a0
62181
0xe5f2
optimize conditional branches: introduce POP_JUMP_IF_FALSE
and POP_JUMP_IF_TRUE
Python 2.7a0
62191
0xeff2
introduce SETUP_WITH
Python 2.7a0
62201
0xf9f2
introduce BUILD_SET
Python 2.7a0
62211
0x03f3
introduce MAP_ADD
and SET_ADD
Python 3000
3000
0xb80b
3010
0xc20b
removed UNARY_CONVERT
3020
0xcc0b
added BUILD_SET
3030
0xd60b
added keyword-only parameters
3040
0xe00b
added signature annotations
3050
0xea0b
print
becomes a function
3060
0xf40b
PEP 3115 metaclass syntax
3061
0xf50b
string literals become unicode
3071
0xff0b
PEP 3109 raise changes
3081
0x090c
PEP 3137 make __file__
and __name__
unicode
3091
0x130c
kill str8 interning
3101
0x1d0c
merge from 2.6a0, see 62151
3103
0x1f0c
__file__
points to source file
Python 3.0a4
3111
0x270c
WITH_CLEANUP
optimization
Python 3.0a5
3131
0x3b0c
lexical exception stacking, including POP_EXCEPT
Python 3.1a0
3141
0x450c
optimize list, set and dict comprehensions: change LIST_APPEND
and SET_ADD
, add MAP_ADD
Python 3.1a0
3151
0x4f0c
optimize conditional branches: introduce POP_JUMP_IF_FALSE
and POP_JUMP_IF_TRUE
Python 3.2a0
3160
0x580c
add SETUP_WITH
, tag: cpython-32
Python 3.2a1
3170
0x620c
add DUP_TOP_TWO
, remove DUP_TOPX
and ROT_FOUR
, tag: cpython-32
Python 3.2a2
3180
0x6c0c
add DELETE_DEREF
Sources:
- Python/import.c – merged by aix from Python 2.7.2 and Python 3.2.2
- Little endian hex values for comparison first two bytes of Igor Popov’s method added by jimbob
You can get the magic number of your Python as follows:
$ python -V
Python 2.6.2
# python
>>> import imp
>>> imp.get_magic().encode('hex')
'd1f20d0a'
To get the magic number for a pyc file you can do the following:
>>> f = open('test25.pyc')
>>> magic = f.read(4)
>>> magic.encode('hex')
'b3f20d0a'
>>> f = open('test26.pyc')
>>> magic = f.read(4)
>>> magic.encode('hex')
'd1f20d0a'
By comparing the magic numbers you’ll know the python version that generated the pyc file.
Take a look at my script in Python that detects and returns the version of Python by which the file (*.pyc or *.pyo) was compiled.
It detects versions of Python from Python 1.5 up to last Python 3 build.
Or, if you have a GNU/Linux system you can use the command “file” in a terminal:
$ file code.pyc
> code.pyc: python 3.5.2 byte-compiled
The official Python github repository no longer appears to keep the list in import.c.
When searching for a more current list than I could find elsewhere, I encountered what appears to be an up-to-date list from Google as of May 2017.
https://github.com/google/pytype/blob/master/pytype/pyc/magic.py
Adding to @Igor Popov’s answer.
To check the version of the compiled script from a newer version of Python:
# written in python3
filename = "outfit.cpython-39.pyc"
with open(filename,'rb') as f:
magic = f.read(4)
print(int.from_bytes(magic[:2], 'little'))
You can lookup the output number here:
https://github.com/google/pytype/blob/master/pytype/pyc/magic.py
Is there any way to know by which Python version the .pyc
file was compiled?
The first two bytes of the .pyc
file are the magic number that tells the version of the bytecodes. The word is stored in little-endian format, and the known values are:
Python version | Decimal | Hexadecimal | Comment |
---|---|---|---|
Python 1.5 | 20121 | 0x994e | |
Python 1.5.1 | 20121 | 0x994e | |
Python 1.5.2 | 20121 | 0x994e | |
Python 1.6 | 50428 | 0x4cc4 | |
Python 2.0 | 50823 | 0x87c6 | |
Python 2.0.1 | 50823 | 0x87c6 | |
Python 2.1 | 60202 | 0x2aeb | |
Python 2.1.1 | 60202 | 0x2aeb | |
Python 2.1.2 | 60202 | 0x2aeb | |
Python 2.2 | 60717 | 0x2ded | |
Python 2.3a0 | 62011 | 0x3bf2 | |
Python 2.3a0 | 62021 | 0x45f2 | |
Python 2.3a0 | 62011 | 0x3bf2 | ! |
Python 2.4a0 | 62041 | 0x59f2 | |
Python 2.4a3 | 62051 | 0x63f2 | |
Python 2.4b1 | 62061 | 0x6df2 | |
Python 2.5a0 | 62071 | 0x77f2 | |
Python 2.5a0 | 62081 | 0x81f2 | ast-branch |
Python 2.5a0 | 62091 | 0x8bf2 | with |
Python 2.5a0 | 62092 | 0x8cf2 | changed WITH_CLEANUP opcode |
Python 2.5b3 | 62101 | 0x95f2 | fix wrong code: for x, in ... |
Python 2.5b3 | 62111 | 0x9ff2 | fix wrong code: x += yield |
Python 2.5c1 | 62121 | 0xa9f2 | fix wrong lnotab with for loops and storing constants that should have been removed |
Python 2.5c2 | 62131 | 0xb3f2 | fix wrong code: for x, in ... in listcomp/genexp |
Python 2.6a0 | 62151 | 0xc7f2 | peephole optimizations and STORE_MAP opcode |
Python 2.6a1 | 62161 | 0xd1f2 | WITH_CLEANUP optimization |
Python 2.7a0 | 62171 | 0xdbf2 | optimize list comprehensions/change LIST_APPEND |
Python 2.7a0 | 62181 | 0xe5f2 | optimize conditional branches: introduce POP_JUMP_IF_FALSE and POP_JUMP_IF_TRUE |
Python 2.7a0 | 62191 | 0xeff2 | introduce SETUP_WITH |
Python 2.7a0 | 62201 | 0xf9f2 | introduce BUILD_SET |
Python 2.7a0 | 62211 | 0x03f3 | introduce MAP_ADD and SET_ADD |
Python 3000 | 3000 | 0xb80b | |
3010 | 0xc20b | removed UNARY_CONVERT |
|
3020 | 0xcc0b | added BUILD_SET |
|
3030 | 0xd60b | added keyword-only parameters | |
3040 | 0xe00b | added signature annotations | |
3050 | 0xea0b | print becomes a function |
|
3060 | 0xf40b | PEP 3115 metaclass syntax | |
3061 | 0xf50b | string literals become unicode | |
3071 | 0xff0b | PEP 3109 raise changes | |
3081 | 0x090c | PEP 3137 make __file__ and __name__ unicode |
|
3091 | 0x130c | kill str8 interning | |
3101 | 0x1d0c | merge from 2.6a0, see 62151 | |
3103 | 0x1f0c | __file__ points to source file |
|
Python 3.0a4 | 3111 | 0x270c | WITH_CLEANUP optimization |
Python 3.0a5 | 3131 | 0x3b0c | lexical exception stacking, including POP_EXCEPT |
Python 3.1a0 | 3141 | 0x450c | optimize list, set and dict comprehensions: change LIST_APPEND and SET_ADD , add MAP_ADD |
Python 3.1a0 | 3151 | 0x4f0c | optimize conditional branches: introduce POP_JUMP_IF_FALSE and POP_JUMP_IF_TRUE |
Python 3.2a0 | 3160 | 0x580c | add SETUP_WITH , tag: cpython-32 |
Python 3.2a1 | 3170 | 0x620c | add DUP_TOP_TWO , remove DUP_TOPX and ROT_FOUR , tag: cpython-32 |
Python 3.2a2 | 3180 | 0x6c0c | add DELETE_DEREF |
Sources:
- Python/import.c – merged by aix from Python 2.7.2 and Python 3.2.2
- Little endian hex values for comparison first two bytes of Igor Popov’s method added by jimbob
You can get the magic number of your Python as follows:
$ python -V
Python 2.6.2
# python
>>> import imp
>>> imp.get_magic().encode('hex')
'd1f20d0a'
To get the magic number for a pyc file you can do the following:
>>> f = open('test25.pyc')
>>> magic = f.read(4)
>>> magic.encode('hex')
'b3f20d0a'
>>> f = open('test26.pyc')
>>> magic = f.read(4)
>>> magic.encode('hex')
'd1f20d0a'
By comparing the magic numbers you’ll know the python version that generated the pyc file.
Take a look at my script in Python that detects and returns the version of Python by which the file (*.pyc or *.pyo) was compiled.
It detects versions of Python from Python 1.5 up to last Python 3 build.
Or, if you have a GNU/Linux system you can use the command “file” in a terminal:
$ file code.pyc
> code.pyc: python 3.5.2 byte-compiled
The official Python github repository no longer appears to keep the list in import.c.
When searching for a more current list than I could find elsewhere, I encountered what appears to be an up-to-date list from Google as of May 2017.
https://github.com/google/pytype/blob/master/pytype/pyc/magic.py
Adding to @Igor Popov’s answer.
To check the version of the compiled script from a newer version of Python:
# written in python3
filename = "outfit.cpython-39.pyc"
with open(filename,'rb') as f:
magic = f.read(4)
print(int.from_bytes(magic[:2], 'little'))
You can lookup the output number here:
https://github.com/google/pytype/blob/master/pytype/pyc/magic.py