【问题标题】:Instance of Python class that responds to all method calls响应所有方法调用的 Python 类的实例
【发布时间】:2016-01-12 13:07:25
【问题描述】:

有没有办法创建一个实例响应任意方法调用的类?

我知道有一个特殊的方法__getattr__(self, attr) 当有人试图访问实例的属性时会调用它。我正在寻找类似的东西,使我也能够拦截方法调用。所需的行为如下所示:

class A(object):
    def __methodintercept__(self, method, *args, **kwargs): # is there a special method like this??
        print(str(method))


>>> a = A()
>>> a.foomatic()
foomatic

编辑

其他建议的问题没有解决我的情况:我不想包装另一个类或更改第二个类或类似的元类。我只想有一个响应任意方法调用的类。

感谢 jonrshape,我现在知道 __getattr__(self, attr) 在调用方法时也会被调用,就像访问属性时一样。但是如果attr来自方法调用或属性访问,我如何区分__getattr__,以及如何获取潜在方法调用的参数?

【问题讨论】:

  • 方法的处理与任何其他属性没有区别,您仍然需要__getattr__/__getattribute__
  • @jonrsharpe mmh 如果我用__getattr__ 实现一个A 类并在里面打印并执行a=A(); a.foo 它将打印foo 但如果我调用a=A(); a.foo() 它将引发TypeError: 'NoneType' object is not callable
  • __getattr__ 仍然需要 return 一些可调用的东西,而不是 call 它,否则你会得到默认的 None 返回
  • @jonrsharpe 啊,好的,谢谢,我误解了错误。解决了,谢谢!
  • 如果您已经回答了自己的问题,请使用您的工作代码发布答案并将其标记为已接受。这将有助于将来有同样问题的人。

标签: python


【解决方案1】:

这是我想出的,它的行为就像该方法存在一样。

首先让我们确定一件事:您无法在__getattr__ 中区分attr 来自函数调用还是“属性访问”,因为类方法是您的类的属性。因此,即使他们不打算调用该方法,也可以访问该方法,例如:

class Test:
    def method(self):
        print "Hi, I am method"

>> t = Test()
>> t.method # just access the method "as an attribute"
<bound method Test.method of <__main__.Test instance at 0x10a970c68>>

>> t.method() # actually call the method
Hi, I am method

因此,我能想到的最接近的就是这种行为:

创建一个A类,这样:

  1. 当我们尝试访问该类中已存在的属性/方法时,正常操作并返回请求的属性/方法。
  2. 当我们尝试访问类定义中不存在的内容时,请将其视为类方法并为所有此类方法提供 1 个全局处理程序。

我将首先编写类定义,然后展示访问不存在的方法的行为与访问存在的方法的行为完全相同,无论您只是访问它还是实际调用它。

类定义:

class A(object):
    def __init__(self):
        self.x = 1 # set some attribute

    def __getattr__(self,attr):
        try:
            return super(A, self).__getattr__(attr)
        except AttributeError:
            return self.__get_global_handler(attr)

    def __get_global_handler(self, name):
        # Do anything that you need to do before simulating the method call
        handler = self.__global_handler
        handler.im_func.func_name = name # Change the method's name
        return handler

    def __global_handler(self, *args, **kwargs):
        # Do something with these arguments
        print "I am an imaginary method with name %s" % self.__global_handler.im_func.func_name
        print "My arguments are: " + str(args)
        print "My keyword arguments are: " + str(kwargs)

    def real_method(self, *args, **kwargs):
        print "I am a method that you actually defined"
        print "My name is %s" % self.real_method.im_func.func_name
        print "My arguments are: " + str(args)
        print "My keyword arguments are: " + str(kwargs)

我添加了方法 real_method 只是为了让我有一些实际存在于类中的东西来比较它的行为与“虚构方法”的行为

结果如下:

>> a = A() 
>> # First let's try simple access (no method call)
>> a.real_method # The method that is actually defined in the class
<bound method A.real_method of <test.A object at 0x10a9784d0>>

>> a.imaginary_method # Some method that is not defined
<bound method A.imaginary_method of <test.A object at 0x10a9784d0>>

>> # Now let's try to call each of these methods
>> a.real_method(1, 2, x=3, y=4)
I am a method that you actually defined
My name is real_method
My arguments are: (1, 2)
My keyword arguments are: {'y': 4, 'x': 3}

>> a.imaginary_method(1, 2, x=3, y=4)
I am an imaginary method with name imaginary_method
My arguments are: (1, 2)
My keyword arguments are: {'y': 4, 'x': 3}

>> # Now let's try to access the x attribute, just to make sure that 'regular' attribute access works fine as well
>> a.x
1

【讨论】:

  • 感谢您的回答,让我对Python有了更深入的了解。我发现的唯一一件事:如果我打电话给a.attribute_that_doesnt_exist,它会毫无反应
  • @Salo 如果你打电话给a.attribute_that_doesnt_exist,它不应该什么都不回应(None)。它实际上应该返回一个“绑定方法”对象。所以:a.method 返回方法。如果在方法调用 (a.method()) 之后添加括号 ()(带参数,可选),它将被评估。打开一个 python 解释器(python,最好是ipython),然后输入a.attribute_that_doesnt_exist。你应该得到像&lt;bound method A.attribute_that_doesnt_exist of &lt;test.A object at 0x10974f490&gt;&gt;这样的东西,这是python告诉你这是一个类方法的方式,但你没有调用它。
  • func_name 的赋值很可爱但很危险:它会*永远*更改实际“全局处理程序”函数的名称——或者更确切地说,直到另一个查找再次更改它。
  • 此代码引发AttributeError: 'function' object has no attribute 'im_func'
  • __getattr__ 仅在未定义方法时才被调用。无需尝试调用 super().__getattr__ 来源:python-reference.readthedocs.io/en/latest/docs/dunderattr/…
【解决方案2】:

unittest.mock.Mock 默认执行此操作。

from unittest.mock import Mock

a = Mock()

a.arbitrary_method()                             # No error
a.arbitrary_method.called                        # True
a.new_method
a.new_method.called                              # False
a.new_method("some", "args")
a.new_method.called                              # True
a.new_method.assert_called_with("some", "args")  # No error
a.new_method_assert_called_with("other", "args") # AssertionError

【讨论】:

    【解决方案3】:

    这是我遇到这个问题时正在寻找的解决方案:

    class Wrapper:
        def __init__(self):
            self._inner = []  # or whatever type you want to wrap
    
        def foo(self, x):
            print(x)
    
        def __getattr__(self, attr):
            if attr in self.__class__.__dict__:
                return getattr(self, attr)
            else:
                return getattr(self._inner, attr)
    
    t = Test()
    t.foo('abc')  # prints 'abc'
    t.append('x')  # appends 'x' to t._inner
    

    非常欢迎批评。我想向 Splinter 包中的 Browser 类添加方法,但它只公开了一个返回实例的函数,而不是类本身。这种方法允许伪继承,这意味着我可以以声明方式将 DOM 代码与特定于网站的代码分离。 (事后看来,更好的方法可能是直接使用 Selenium。)

    【讨论】:

    • 注意:else中的语句应该改为return getattr(self._inner, attr)
    【解决方案4】:

    方法调用与属性访问没有任何不同。 __getattr__()__getattribute__()是响应任意属性请求的方式。

    你无法知道访问是来自“刚刚检索”还是“方法调用”。

    它的工作原理是这样的:首先,属性检索,然后,调用检索到的对象(在 Python 中,call 只是另一个运算符:任何东西都可以被调用,如果它不可调用,则会抛出异常)。一个不知道,也不应该知道另一个(好吧,您可以在调用堆栈中分析代码,但这完全不是这里要做的事情)。

    其中一个原因是 - 函数是 Python 中的一等对象,即函数(或者,更确切地说,对它的引用)与任何其他数据类型没有什么不同:我可以获取引用,保存它并传递它周围。 IE。请求数据字段和请求方法完全没有区别。

    详细说明您需要什么,以便我们提出更好的解决方案。

    例如,如果您需要能够使用不同的签名调用“方法”,那么*args**kwargs 就是要走的路。

    【讨论】:

      【解决方案5】:

      以下将响应所有未定义的方法调用:

      class Mock:
      
          def __init__(self, *args, **kwargs):
              pass
      
          def __getattr__(self, attr):
              def func(*args, **kwargs):
                  pass
              return func
      

      或者只使用unittest.mock.Mock

      【讨论】:

        猜你喜欢
        • 2023-03-04
        • 1970-01-01
        • 2015-06-10
        • 1970-01-01
        • 2023-04-01
        • 2021-06-30
        • 1970-01-01
        • 2019-01-17
        • 1970-01-01
        相关资源
        最近更新 更多