在python源代码中创建一个方法的哈希值



我正面临一个问题,我们需要跟踪第三方代码中的某些python方法,以查看它们是否发生了变化。

我们不能散列整个文件,因为可能有各种不相关的更改。

所以,我写一个进程没有问题,当被调用时,将提供一个带有路径的文件名,一个类名和一个方法名。

我需要一些指针来读取这个方法——显然不能依赖行号——然后我可以创建一个散列并存储它。

我似乎找不到任何方法在python .py文件的Y类中找到方法X

注意这个被扫描的源可能甚至没有被路径到,所以我无法从内部找到或分析类-我需要一个可以分析源而不打开它的函数(它是一个我甚至没有路径到的文件库)。

您可以获得方法体(使用inspect),然后对其进行哈希(使用hashlib)。

假设您想从test.py文件中的test_class获取方法test_method的哈希值,如下所示:
test.py

class test_class:
def test_method():
return 'test'

你应该这样做:

import os
import inspect
import hashlib
import importlib.util
def get_hash(file_path, class_name, method_name):
try:
# get full path
if not file_path.startswith('/'):
file_path = os.path.join(os.path.dirname(__file__), file_path)
# get method body
spec = importlib.util.spec_from_file_location(class_name, file_path)
foo = importlib.util.module_from_spec(spec)
spec.loader.exec_module(foo)
my_class = getattr(foo, class_name)
my_method = getattr(my_class, method_name)
body = inspect.getsource(my_method)
# hash
hash_object = hashlib.sha256(bytes(body, 'utf-8'))
return hash_object.hexdigest()

except (AttributeError, FileNotFoundError, TypeError):
return ''
print(get_hash('test.py', 'test_class', 'test_method'))
# 1b7c4367c925d6891313b671a41600fc581854513a10c704b32441afea01d591

可以使用__import__导入方法,使用dis模块散列函数的字节码:

import dis
import hashlib
module = __import__('my_package.my_module', fromlist=['Klass'])
method_code = dis.Bytecode(module.Klass.method).codeobj.co_code
hasher = hashlib.sha256()
hasher.update(method_code)
h = hasher.digest()

例如摩尔requestsRequest类:

>>> requests = __import__('requests', fromlist=['Request'])
>>> method_code = dis.Bytecode(requests.Request.prepare).codeobj.co_code
>>> hasher = hashlib.sha256()
>>> hasher.update(method_code)
>>> hasher.digest()
b'xd8x03x04xdfA8x90L[x8bx97xae~xe7x90x91B^%+xc2x99x14xbfxe2xcaBx8axe6xa5x96xc4'

谢谢大家,我探索了你们的选择,总的来说成功了。

最后,因为我们执行的环境是一个经过大量修改的环境(它是Odoo框架),我遇到了一些问题,比如修改的加载器,它抱怨它的指定方式,等等。

最终,我预料到了这些问题,这就是为什么我在寻找一个不加载文件的解决方案,作为一个模块。

这就是我最后得到的…

import ast
def create_hash(self, fname, class_name, method_name):
source_item = False
next_item = False
with open(fname) as f:
tree = ast.parse(f.read(), filename=fname)
for item in tree.body:
if source_item:
next_item = item
break
if item.__class__.__name__ == 'ClassDef' and item.name == class_name:
for subitem in item.body:
if source_item:
next_item = subitem
break
if subitem.__class__.__name__ == 'FunctionDef' and subitem.name == method_name:
source_item = subitem
if next_item:
break
assert source_item, 'Unable to find method %s on %s' % (method_name, class_name)
from_line = min(
[source_item.lineno]
+ (hasattr(source_item, 'decorator_list') and [d.lineno for d in source_item.decorator_list] or [])
)
to_line = next_item and min(
[next_item.lineno]
+ (hasattr(next_item, 'decorator_list') and [d.lineno for d in next_item.decorator_list] or [])
) - 1 or False
with open(fname) as f:
if to_line:
code = lines[from_line - 1:to_line]
else:
code = lines[from_line - 1:]
hash_object = hashlib.sha256(bytes(''.join(code), 'utf-8'))
hexdigest = hash_object.hexdigest()
return hexdigest

编辑:

AST的不同版本似乎改变了函数的"线条"。-在旧版本中,它是def和装饰符的最小值-在新版本中,它是def的行。

所以我改变了代码,以允许两种实现....

最新更新