сравните, приводят ли два файла python к одному и тому же байтовому коду (идентичен ли код)

Мы занимаемся очисткой кода. Очистка касается только форматирования (если проблема, то давайте даже предположим, что номера строк не меняются, хотя в идеале я хотел бы также игнорировать изменения номеров строк)

Чтобы быть уверенным, что нет случайного изменения кода, я хотел бы найти простой/быстрый способ сравнить два исходных кода.

Итак, предположим, что у меня есть file1.py и file2.py

что работает, так это использовать py_compile.compile(filename) для создания файлов .pyc, а затем использовать uncompyle6 pycfile, затем удалить комментарии и сравнить результаты, но это излишне и очень медленно.

Другой подход, который я придумал, - это скопировать file1.py, например, в file.py, использовать py_compile.compile("file.py") и сохранить файл .pyc.

затем скопируйте file2.py, например, в file.py и используйте py_compile.compile("file.py"), сохраните файл .pyc и, наконец, сравните оба сгенерированных файла .pyc.

Будет ли это надежно работать со всеми (текущими) версиями ›= python 3.6

Насколько я помню, по крайней мере, для python2 файлы pyc могут содержать метки времени или абсолютные пути, что может привести к сбою сравнения. (по крайней мере, если генерация файла pyc выполнялась на двух разных машинах)

Есть ли чистый способ сравнить байтовый код файлов py2?

В качестве бонусной функции (если возможно) я хотел бы создать хэш для каждого байтового кода, который я мог бы сохранить для дальнейшего использования.


person gelonida    schedule 10.02.2021    source источник


Ответы (2)


Вы можете попробовать использовать внутреннюю функцию Python compile, которая может скомпилироваться из строки (в вашем случае читать из файла). Например, компиляция и сравнение результирующих объектов кода из двух эквивалентных программ и одной почти эквивалентной программы, а затем просто для демонстрационных целей (чего вам не хотелось бы делать) выполнение нескольких объектов кода:

import hashlib
import marshal
​
​
def compute_hash(code):
    code_bytes = marshal.dumps(code)
    code_hash = hashlib.sha1(code_bytes).hexdigest()
    return code_hash
​
​
source1 = """x = 3
y = 4
z = x * y
print(z)
"""
source2 = "x=3;y=4;z=x*y;print(z)"
​
source3 = "a=3;y=4;z=a*y;print(z)"
​
obj1 = compile(source=source1, filename='<string>', mode='exec', dont_inherit=1)
obj2 = compile(source=source2, filename='<string>', mode='exec', dont_inherit=1)
obj3 = compile(source=source3, filename='<string>', mode='exec', dont_inherit=1)
​
print(obj1 == obj2)
print(obj1 == obj3)
​
exec(obj1)
exec(obj3)
print(compute_hash(obj1))

Отпечатки:

True
False
12
12
48632a1b64357e9d09d19e765d3dc6863ee67ab9

Это избавит вас от необходимости копировать py-файлы, создавать pyc-файлы, сравнивать pyc-файлы и т. д.

Примечание.

Функция compute_hash нужна, если вам нужна хеш-функция, которая повторяема, т. е. многократно возвращает одно и то же значение для одного и того же объекта кода при вычислении в последовательных запусках программы.

person Booboo    schedule 10.02.2021
comment
Хорошая идея. попробую это. Даже не думал, что результат компиляции может быть сопоставим. Однако я боюсь, что хеш obj1 не будет таким же, как хэш obj2. Идея хеширования заключалась в том, что я мог просто сравнить хэши или сравнить хэши двух файлов py/pyc на двух разных машинах, чтобы увидеть, дают ли они результат. идентичный байт-код, но я уже проверю, нормально ли работает компиляция. - person gelonida; 10.02.2021
comment
Два источника не доступны с одной и той же машины? Это значительно упростило бы дело. Но я думаю, что если вы используете два идентичных интерпретатора Python, то есть одну и ту же версию, полученный код будет идентичным, и поэтому два хэша должны быть одинаковыми. - person Booboo; 10.02.2021
comment
У меня есть несколько вариантов использования. за то, что я пытаюсь сделать сейчас. одна и та же машина вполне приемлема. Однако хэши рандомизированы и на самом деле ничего не говорят о содержимом. Попробуйте, например, дважды вызвать python3 -c 'print(hash("hello new world"))'. или попробуйте напечатать хэш объекта и изменить какое-то значение объекта и перепечатать хэш. - person gelonida; 10.02.2021
comment
Тогда хэши гарантированно будут одинаковыми, иначе тест == между двумя объектами кода не пройдет. - person Booboo; 10.02.2021
comment
но в случае объекта кода, по крайней мере, для небольших примеров, вы правы. Хэши идентичны для обоих объектов кода (если они выполняются в одном и том же процессе). Поэтому я не могу сохранить их для дальнейшего использования, но это не проблема для того, что я пытаюсь сделать в данный момент. - person gelonida; 10.02.2021
comment
Возможно, вы могли бы добавить следующее к своему ответу? если вам нужен уникальный межпроцессный хэш, то будет работать следующее: import hashlib, затем import marshall, затем obj1 = compile(source=source1, filename='<string>', mode='exec', dont_inherit=1), затем code_bytes1 = marshal.dumps(obj1), затем code_hash1 = hashlib.sha1(code_bytes1).hexdigest() - person gelonida; 10.02.2021
comment
если у вас нет времени или вы не хотите изменить свой ответ, просто скажите мне. Я все равно приму ваш ответ. и может просто добавить альтернативный ответ для хешируемого решения. Не всем что-то нужно, это предсказуемые хэши при запуске на двух процессах или даже машинах. - person gelonida; 10.02.2021
comment
Я готов обновить свой ответ, если вы объясните, почему это хэш-вычисление будет лучше, чем мой ответ. Использование этого метода будет давать одинаковые хеш-значения только в том случае, если вы начнете с одинаковых объектов кода. Вы обеспокоены тем, что каким-то образом hash(obj1) будет давать разные результаты на разных машинах, даже если два объекта кода идентичны (если они не идентичны, ваша новая хеш-функция также не будет работать). Вы можете принять ответ - известно, что я изменяю ответы после того, как они были приняты, если у меня есть улучшение или новое понимание. - person Booboo; 10.02.2021
comment
измените свою программу, чтобы распечатать хеш и вызывать ее несколько раз. Вы увидите, что хэш не является фиксированным значением (по крайней мере, для моих тестов). Это означает, что вы не можете вычислить хэш на одной машине и на другой и обнаружить, что код на обеих машинах идентичен. На самом деле он даже не работает на одной машине с двумя разными процессами. Насколько мне известно, хэш python не является фиксированным значением. Мой подход преобразует объект кода в байты и вычисляет хэш sha1. упорядоченные байты, похоже, не меняются между разными запусками - person gelonida; 10.02.2021
comment
Вы правы! Ответ обновлен. - person Booboo; 10.02.2021
comment
опубликовал другой вопрос, который не такой, но похожий: stackoverflow.com/questions/66141364/ - person gelonida; 10.02.2021

Возможно, это не желаемый ответ, но почему бы вам не использовать инструмент сравнения для сравнения, если файлы изменены? https://linuxhandbook.com/diff-command/

И если файлы изменены, используйте инструмент слияния, такой как meld, чтобы сравнить изменения http://meldmerge.org/

person T1Berger    schedule 10.02.2021
comment
diff на самом деле нельзя использовать, так как следующие строки приведут к одному и тому же байт-коду. a = "hello" против a = u"hello" против a = "hello" против a = "hello" #and a comment Идея состоит в том, чтобы сделать код более читаемым, более однородным, удалить артефакты python2, такие как префикс u для строк Unicode / и т. д., но чтобы быть уверенным, что кто-то случайно не заменил неправильный u" как, например, в. a = u"I can see u"m, который должен стать a = "i can see u", но не должен стать a = "i can see " - person gelonida; 10.02.2021
comment
Я изменил название вопроса, чтобы сделать свое намерение (надеюсь) более ясным - person gelonida; 10.02.2021