通用对象"watcher"代理,用于包装集合对象以记录状态更改 (add/del)



我想为库使用的一些变量添加"观察者"。它们的类型分别为collections.dequedictweakref.WeakSet,和CCD_ 4。

每当向他们添加/添加项目或从他们弹出项目时,我都想生成日志消息。

我想尽量减少对原始代码的更改:

  • 我不想在变量被访问和修改。

  • 我不想创建自己的继承自原始类的类类别(例如class InstrumentedDeque(collections.deque): ...

相反,这就是问题所在,是否有可能创建一个适用于所有集合的泛型类(或包装器/装饰器)对象,因此唯一需要更改的地方是对象是最初创建的。如果这是原始代码,则带有2要"监视"的变量:self.scheduledself.ready。。。

def __init__(self):
    self.scheduled = []
    self.ready = collections.deque()

那么唯一需要的改变就是。。。

def __init__(self):
    self.scheduled = MyLogger([], var_name='scheduled')
    self.ready = MyLogger(collections.deque(), var_name='ready')

无仪表

test = Test()
test.add(1)
test.add(2)
test.pop()

仪表变更后

test = Test()
test.add(1)
***instrumentation output. scheduled: [1]
***instrumentation output. ready: deque([1])
test.add(2)
***instrumentation output. scheduled: [1, 2]
***instrumentation output. ready = deque([1, 2])
test.pop()
***instrumentation output. scheduled: [2]
***instrumentation output. ready: deque([2])

其中示例CCD_ 8和CCD_。。。

    def add(self, val):
        heapq.heappush(self.scheduled, val)
        self.ready.append(val)
    def pop(self):
        heapq.heappop(self.scheduled)
        self.ready.popleft()

我尝试创建一个"包装器"类,并使用__new____init____getattr__,但无法让它发挥作用。像这样的。。。

class MyLooger:
    def __new__(cls):
        # what to do?
    def __init__(self):
        # what to do?
    def __getattr__(self, name):
        # what to do?

非常感谢您的帮助。

注意:以下抽象不适用于直接进入封装对象的底层内部的C扩展代码(如heapq.heappush,既适用于CPython,也适用于PyPy);在Python级别上,没有什么可以减轻这种情况的。您可能会看到是否可以在C级别"修补泄漏",但随后您将不得不在编写C和Python扩展时弄脏自己的手。

解决方案:您不需要达到__new__。以下内容将在所有对象上通用。它还将使isinstance在包装器上工作,就像在包装的对象上调用它一样。

from functools import wraps
class Logged(object):
    def __init__(self, obj, obj_name):
        self.obj = obj
        self.obj_name = obj_name
    def __getattribute__(self, attr_name):
        obj = object.__getattribute__(self, 'obj')
        obj_name = object.__getattribute__(self, 'obj_name')
        attr = getattr(obj, attr_name)
        # this is not 100% generic, mathematically speaking,
        # but covers all methods and the `__class__` attribute:
        if not callable(attr) or isinstance(attr, type):
            return attr
        @wraps(attr)
        def fn(*args, **kwargs):
            print "%s called on %s with: %s and %s" % (attr_name, obj_name, args, kwargs)
            return attr(*args, **kwargs)
        return fn
    def __repr__(self):
        return repr(object.__getattribute__(self, 'obj'))

然后只是:

>>> scheduled = Logged([], obj_name="scheduled")
>>> scheduled.append
<function append>
>>> scheduled.append(3)
append called on scheduled with: (3,) and {}
>>> scheduled.extend([1,2])
extend called on scheduled with: ([1, 2],) and {}
>>> isinstance(scheduled, list)
True
>>> scheduled
[3, 1, 2]

最新更新