为了账号安全,请及时绑定邮箱和手机立即绑定

以最少侵入性和最隐蔽的方式检测对象使用情况

以最少侵入性和最隐蔽的方式检测对象使用情况

DIEA 2021-12-21 16:37:26
有没有办法自动检测何时使用 python 对象(并可能对此做出反应)?例如,假设我有一个类型为 的对象Foo。我没有为 编写类代码Foo,因为它来自外部库。我想以这样的方式“装饰”我的对象,每当使用它的方法之一,或者每当它的内部状态(成员)发生变化或被访问时,我都会得到一些日志信息,比如"Foo is being used".我使用“装饰”一词来强调我不想更改Foo使用类型对象的所有接口。我只想为它添加一些功能。此外,我将避免Foo直接修改的类代码,即通过print在其每个方法的开头显式添加一条语句(无论哪种方式都不会通知我其成员何时发生变化)。而且我不想将我的对象显式注册到其他一些对象,因为这将是一种“侵入性”方法,需要更改“客户端”代码(使用Foo对象的代码),这将是这很容易被遗忘。
查看完整描述

3 回答

?
慕码人2483693

TA贡献1860条经验 获得超9个赞

我可以想到一个解决方案,它并不完美,但可能是一个开始。我们可以通过从装饰类继承的类中的__getattribute__和捕获实例属性访问__setattribute__:


import re


dunder_pattern = re.compile("__.*__")

protected_pattern = re.compile("_.*")


def is_hidden(attr_name):

    return dunder_pattern.match(attr_name) or protected_pattern.match(attr_name)



def attach_proxy(function=None):

    function = function or (lambda *a: None)


    def decorator(decorated_class):


        class Proxy(decorated_class):

            def __init__(self, *args, **kwargs):

                function("init", args, kwargs)

                super().__init__(*args, **kwargs)


            def __getattribute__(self, name):

                if not is_hidden(name):

                    function("acces", name)

                return object.__getattribute__(self, name)


            def __getattr__(self, name):

                if not is_hidden(name):

                    function("acces*", name)

                return object.__getattr__(self, name)


            def __setattribute__(self, name, value):

                if not is_hidden(name):

                    function("set", name, value)

                return object.__setattribute__(self, name, value)


            def __setattr__(self, name, value):

                if not is_hidden(name):

                    function("set*", name, value)

                return object.__setattr__(self, name, value)


        return Proxy


    return decorator

然后你可以用它来装饰你的班级:


@attach_proxy(print)

class A:

    x = 1

    def __init__(self, y, msg="hello"):

        self.y = y


    @classmethod

    def foo(cls):

        print(cls.x)


    def bar(self):

        print(self.y)

这将导致以下结果:


>>> a = A(10, msg="test")

init (10,) {'msg': 'test'}

set* y 10

>>> a.bar()

acces bar

acces y

10

>>> a.foo() # access to x is not captured

acces foo

1

>>> y = a.y

acces y

>>> x = A.x # access to x is not captured

>>> a.y = 3e5

set* y 300000.0

问题:


不会捕获类属性访问(为此需要一个元类,但我看不到即时执行的方法)。


TypeA是隐藏的(在 type 后面Proxy),这可能更容易解决:


>>> A

__main__.attach_proxy.<locals>.decorator.<locals>.Proxy

另一方面,这不一定是问题,因为这会按预期工作:


>>> a = A(10, msg="test")

>>> isinstance(a, A)

True

编辑请注意,我不会将实例传递给function调用,但这实际上是一个好主意,将调用替换为function("acces", name)to function("acces", self, name)。这将允许与您的装饰者一起制作更多有趣的东西。


查看完整回答
反对 回复 2021-12-21
?
Helenr

TA贡献1780条经验 获得超3个赞

您可以使用猴子补丁来实现这一点。将对象上的成员函数之一重新分配为装饰函数,该函数又调用原始函数,并添加了一些日志记录。


例如:


a = Test() # An object you want to monitor

a.func() # A specific function of Test you want to decorate


# Your decorator

from functools import wraps

def addLogging(function):

    @wraps(function)

    def wrapper(*args, **kwargs):

        print 'Calling {}'.format(function.func_name)   

        return function(*args, **kwargs)

    return wrapper


a.func = addLogging(a.func)

但是请注意,猴子补丁最好仅用于单元测试,而不是生产代码。它可能有不可预见的副作用,应谨慎使用。


至于识别成员变量的值何时发生变化,可以参考这个。


所有这些都需要您修改客户端代码——如果有一种方法可以在不改变客户端代码的情况下实现这一点,我不知道。


查看完整回答
反对 回复 2021-12-21
?
暮色呼如

TA贡献1853条经验 获得超9个赞

您可以将@suicideteddy 提供的答案与方法检查结合起来,结果类似于以下内容:


# Your decorator

def add_logging(function):

    @wraps(function)

    def wrapper(*args, **kwargs):

        print 'Calling {}'.format(function.func_name)   

        return function(*args, **kwargs)

    return wrapper


instance = Test() # An object you want to monitor


# list of callables found in instance

methods_list = [

   method_name for method_name in dir(instance) if callable(

      getattr(instance, method_name)

   )

]


# replaces original method by decorated one

for method_name in methods_list:

    setattr(instance, method_name, add_logging(getattr(instance, method_name))

我还没有测试过这个,但类似的东西应该可以完成工作,祝你好运!


查看完整回答
反对 回复 2021-12-21
  • 3 回答
  • 0 关注
  • 138 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信