Python脚本能否对自身进行MD5哈希?

4
我希望能够使脚本在每次运行时自动哈希。如果不必提供脚本路径,是否可能实现此功能?我可以看到两种方法。第一种方法是哈希源Python文本文件。第二种方法是哈希已编译的字节码。
我打算采用第二种选择,这引发了另外几个问题:
  1. 脚本能否确定其编译后的字节码来自脚本内部?
  2. 我会在另一个问题中提出这个问题。

我必须等待20分钟才能发布第二个问题。 - starflyer
1
这是什么目的? - NullUserException
我想找出一个Python脚本是否有改变。选项1是最保守的,因为缩进的改变(从空格到制表符)不会影响脚本的语法结构,也不会改变脚本的行为。选项2是对编译后的字节码文件进行md5哈希。也许还有我不知道的解决方案? - starflyer
2个回答

7

使用以下Python脚本,可以找到它自己的路径:

import os

path = os.path.abspath(__file__)

接下来,您可以打开源文件并通过hashlib.md5运行它。

脚本文件没有编译的字节码文件;只有模块才有。

请注意,在Python 2中,__file__路径使用实际加载的文件的扩展名;对于模块,仅当有一个缓存的字节码文件可以重用时,才使用.pyc.pyo。如果没有字节码文件或字节码文件过期,它是.py

您需要考虑到您的代码被调用时使用了修改Python加载的字节码的命令行开关;如果给出了-O-OO开关,或设置了PYTHONOPTIMIZE环境标志,则Python将加载或编译为.pyo文件。


参见:https://dev59.com/enE85IYBdhLWcg3wvGOm - NullUserException
如果一个脚本没有编译的字节码文件,那么这就无法实现选项2。 - starflyer

1
一种可能的(未经测试的)解决方案是使用反汇编模块dis.dis()将Python类或模块(但不是实例)转换为汇编语言。两个完全相同的类,但具有不同的类名称,将显示为相同,但可以通过在运行组合字符串之前添加cls.__name__来修复这个问题,然后通过md5进行处理。
请注意,dis.dis()打印到stdout而不是返回字符串,因此还需要通过StringIO捕获打印输出。

_

_ >>> import dis, md5
_ >>> class A(object): 
_ ...   def __init__(self, item): print "A(%s)" % item
_ ... 
_ >>> dis.dis(A)
_ Disassembly of __init__:
_   2           0 LOAD_CONST               1 ('A(%s)')
_               3 LOAD_FAST                1 (item)
_               6 BINARY_MODULO       
_               7 PRINT_ITEM          
_               8 PRINT_NEWLINE       
_               9 LOAD_CONST               0 (None)
_              12 RETURN_VALUE        
_ 
_ >>> class B(A):
_ ...   def __init__(self, item): super(A, cls).__init__(item); print "B(%s)" % item
_ ... 

_ >>> dis.dis(B)
_ Disassembly of __init__:
_   2           0 LOAD_GLOBAL              0 (super)
_               3 LOAD_GLOBAL              1 (A)
_               6 LOAD_GLOBAL              2 (cls)
_               9 CALL_FUNCTION            2
_              12 LOAD_ATTR                3 (__init__)
_              15 LOAD_FAST                1 (item)
_              18 CALL_FUNCTION            1
_              21 POP_TOP             
_              22 LOAD_CONST               1 ('B(%s)')
_              25 LOAD_FAST                1 (item)
_              28 BINARY_MODULO       
_              29 PRINT_ITEM          
_              30 PRINT_NEWLINE       
_              31 LOAD_CONST               0 (None)
_              34 RETURN_VALUE        
_ 
_ >>> class Capturing(list):
_ ...     def __enter__(self):
_ ...         self._stdout = sys.stdout
_ ...         sys.stdout = self._stringio = StringIO()
_ ...         return self
_ ...     def __exit__(self, *args):
_ ...         self.extend(self._stringio.getvalue().splitlines())
_ ...         del self._stringio    # free up some memory
_ ...         sys.stdout = self._stdout
_ ... 
_ >>> with Capturing() as dis_output: dis.dis(A)
_ >>> A_md5 = md5.new(A.__name__ + "\n".join(dis_output)).hexdigest()
_ '7818f1864b9cdf106b509906813e4ff8'

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接