【问题标题】:Create a hash of a method in python source在 python 源代码中创建方法的哈希
【发布时间】:2021-10-11 09:49:50
【问题描述】:

我面临一个问题,我们需要跟踪第三方代码中的某些 python 方法以查看它们是否已更改。

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

所以,我可以毫无问题地编写一个进程,该进程在被调用时会提供一个文件名和路径、一个类名和一个方法名。

我需要一些指示如何读取该方法 - 显然不能依赖行号 - 然后我可以创建一个哈希并存储它。

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

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

【问题讨论】:

  • 我无法理解“不打开就分析源代码”部分。
  • 主要是,我想避免加载模块 - 它是第三方代码 - 它有数百个方法和类 - 甚至可能有错误或可能无法执行 - 但我知道它是 python (即使它是带有阻止它执行的错误的python)-我只需要知道特定类中的任何特定方法是否已更改。

标签: python methods hashcode


【解决方案1】:

谢谢大家,我探索了你们的选择并取得了成功。

不过,最后,因为我们正在执行的环境是一个经过大量修改的环境(它是 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 改变了函数的“lineno”——在旧版本中,它是 def 和装饰器的最小值——在新版本中,它是 def 的行。

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

【讨论】:

    【解决方案2】:

    您可以使用__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'\xd8\x03\x04\xdfA8\x90L[\x8b\x97\xae~\xe7\x90\x91B^%+\xc2\x99\x14\xbf\xe2\xcaB\x8a\xe6\xa5\x96\xc4'
    
    

    【讨论】:

    • 如果你运行两次(来自不同的 Python 会话),你会得到不同的结果。
    • @bereal 你说得对,我更新了答案。
    【解决方案3】:

    您可以获取方法体(使用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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-10-06
      • 1970-01-01
      • 2015-03-01
      • 2021-08-25
      • 1970-01-01
      相关资源
      最近更新 更多