2015-10-14 56 views
8

有没有办法创建哪个实例可以响应任意方法调用?对所有方法调用做出响应的Python类实例

我知道有一个特殊的方法__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)也会在调用方法时被调用,就像访问属性时一样。但是,如果来自方法调用或属性访问以及如何获取潜在方法调用的参数,我如何区分__getattr__

+2

方法是无异于任何其他属性,你仍然需要'__getattr__' /'__getattribute__'处理。 – jonrsharpe

+0

@jonrsharpe mmh如果我用'__getattr__'实现一个类A并在里面打印并且执行'a = A(); a.foo'会打印'foo',但如果我打电话'a = A(); a.foo()'它会引发一个'TypeError:'NoneType'对象不可调用' – Salo

+1

'__getattr__'仍然必须*返回*可调用的东西,而不是*调用*它,否则返回默认的'None' – jonrsharpe

回答

7

这是我想出来的东西,它的行为就好像存在方法一样。

首先,我们建立了一两件事:你不能在__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 
+0

感谢您的回答,让我更深入地理解Python。我发现的唯一的东西:如果我打电话给'a.attribute_that_doesnt_exist',它将没有任何响应 – Salo

+1

@Salo如果你调用'a.attribute_that_doesnt_exist'它不应该没有任何响应('None')。它实际上应该返回一个“绑定方法”对象。 所以:'a.method'返回方法。如果在方法调用('a.method()')之后添加括号'()'(带参数,可选),它将被评估。 打开一个python解释器('python',理想的是'ipython'),并键入'a.attribute_that_doesnt_exist'。你应该像'>' 这是python告诉你这是一个类方法,但你没有调用它的方式。 – tomas

+0

你是对的,再次感谢 – Salo

0

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

您无法知道访问是来自“正在检索”还是“方法调用”。

它的工作原理是这样的:第一,属性检索,然后,调用检索对象(在Python,通话只是另算:什么都可以调用,将抛出一个异常,如果它是不可调用的)。一个人不会,也不应该知道另一个人(也就是说,你可以在调用堆栈中分析代码,但这完全不是这里要做的)。

其中一个原因是 - 函数是Python中的第一类对象,即一个函数(或者说,对它的引用)与任何其他数据类型没有区别:我可以获取引用,将其保存并通过它。即请求数据字段和方法之间完全没有区别。

详细说明您需要什么,以便我们提出更好的解决方案。例如,如果您需要能够使用不同签名调用“方法”,则需要*args**kwargs

4

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 
相关问题