如何使用装饰器记录来自特定模块的所有函数调用?

How to use a decorator to log all function calls from a particular module?

一些背景:

我正在尝试编写一个用于日志记录的装饰器。具体来说,我经常使用 arcpy,从工具中获取消息的方法是使用 arcpy.GetMessages()。然而,这是一个烦人的函数,因为它只保存最近的消息并且必须在每个工具之后调用。伪代码示例

import arcpy
import logging
log = logging.getLogger(__name__)

def test_function(in_data):
    out_data = 'C:/new_path/out_data'
    arcpy.MakeFeatureLayer_management(in_data, out_data)
    log.info(arcpy.GetMessages())

    arcpy.Delete_management(in_data)
    log.info(arcpy.GetMessages()) 

    # If you did log.info(arcpy.GetMessages()) again here you'd just get 
    # the message from the Delete tool again

最好编写一个装饰器来识别任何时候调用 arcpy 函数并记录它。喜欢:

def log_the_arcpy(fn):
    @functools.wraps(fn)
    def inner(*args, **kwargs):
        result = fn(*args, **kwargs)
        # Some magic happens here?!
        if module_parent == arcpy: #module_parent is obviously fake, is there a real attribute?
            log.info(arcpy.GetMessages())
        return result
    return inner

但是,我在两个地方陷入困境: (1) 如何识别单个函数的 "arcpy-ness"(或任何包),以及 (2 ) 使用装饰器挖掘函数内部并确定潜在许多函数调用的包成员资格的总体方法。

似乎有用的点点滴滴是:

None 其中的想法非常充实 - 这是因为其中许多主题对我来说都是全新的。如果有任何指示,我将不胜感激 - 我想早点问,这样我就不会被困在以后问一堆 XY Problem questions

这应该有效:

if hasattr(fn, '__module__') and getattr(fn, '__module__') == 'arcpy':
    log.info(arcpy.GetMessages())

全功能:

def log_the_arcpy(fn):
    @functools.wraps(fn)
    def inner(*args, **kwargs):
        result = fn(*args, **kwargs)
        if hasattr(fn, '__module__') and getattr(fn, '__module__') == 'arcpy':
            log.info(arcpy.GetMessages())
        return result
    return inner

如果您打算直接在 arcpy 上调用方法,包装模块可能是最简单且对性能影响最小的方法:

# arcpy_proxy.py
import arcpy as _arcpy
import logging

class _proxy(object):

    def __getattr__(self, item):
        ref = getattr(_arcpy, item)
        if callable(ref):  # wrap only function calls
            return self._wrap(ref)
        return ref

    @classmethod
    def _wrap(cls, func):
        def inner(*args, **kwargs):
            val = func(*args, **kwargs)
            logging.info(_arcpy.GetMessages())  # log the messages
            return val
        return inner

arcpy = _proxy()

那么您可以 from arcpy_proxy import arcpy 作为临时替代品。您甚至可以在主脚本中添加 sys.modules["arcpy"] = arcpy(当然是在导入之后),这样您就不必在其他任何地方替换它来代理它。