我尝试创建跟踪装饰器以跟踪调用哪个方法。 让我们先阅读代码。代码是用python 2.7编写的
class Decor(object):
def __init__(self,cls):
self.cls=cls
def __call__(self,*arg):
self.instance = self.cls(*arg)
return self
def __getattr__(self,name):
print "attribute name of instance of class {} = {}".format(self.cls.__name__,name)
return getattr(self.instance,name)
@Decor
class Foo(object):
attr1 = 10
def __init__(self,value):
self.value = value
def method1(self):
self.method2()
print self
print "value={}".format(self.value)
def method2(self):
print "hi"
f=Foo(10)
print f
f.method1()
输出:
1. <__main__.Decor object at 0x02758B90>
2. attribute name of instance of class Foo = method1
3. hi
4. <__main__.Foo object at 0x02758C30>
5. value=10
我预计method2会以method1的形式获得print。你能解释为什么method2没有详细打印吗?
我不理解输出line4。它显示Foo对象而不是Decor对象。 f是一个Decor对象,为什么self是Foo对象?请解释一下!
请给我一个解决此问题的建议。感谢
答案 0 :(得分:2)
此处的问题是self
内的Foo.instance1
是Foo
个实例,而不是Decor
个实例。
如果您打印f.method1
本身就可以看到这一点:它是一个实例为f
的绑定方法:
>>> f.method1
attribute name of instance of class Foo = method1
<bound method Foo.method1 of <__main__.Foo object at 0x10c641e90>>
为什么呢?好吧,你返回了getattr(self.instance, 'method1')
,它与self.instance.method1
做了同样的事情,所以它的self.instance
会被绑定到绑定的方法中。
因此,当method1
代码查找self.method2
(或self.value
)时,它会在Foo
实例上查找,因此您的代码不会#39; t跑步。
如果您不理解这一点,请尝试阅读How Methods Work。 (如果您不想接受属性查找是不可思议的,请点击Descriptor HowTo Guide末尾的链接。)
因此,如果您希望self
成为Decor
内的instance1
实例,则必须使用Decor
实例返回绑定方法,而不是{{1实例。您可以通过types.MethodType
手动创建一个来完成此操作。 (或者,一旦理解了描述符,就可以直接查找函数实现,然后手动调用它Foo
。
例如:
__get__
(如果你想让它与classmethods,staticmethods,非方法callables一起使用,比如存储在实例dict中的函数等等,当然它需要比def __getattr__(self, name):
print "attribute name of instance of class {} = {}".format(self.cls.__name__,name)
attr = getattr(self.instance, name)
if callable(attr):
return types.MethodType(attr.__func__, self, type(self))
else:
return attr
更复杂一点。)
现在:
callable
所以,当我们称之为:
>>> f.method1
attribute name of instance of class Foo = method1
Out[26]: <bound method Decor.method1 of <__main__.Decor object at 0x10c7e9f10>>
答案 1 :(得分:1)
My other answer解释了为什么您现有的代码无法正常工作以及如何修复它(虽然我不确定 如何解释它......)。< / p>
但是,通常还有一种更简单的方法可以做你正在尝试做的事情:你可以对这个类进行monkeypatch而不是包装它。当然,这意味着您必须使用__getattribute__
而不是__getattr__
,并且会不小心递归地给自己打电话:*
def Decor(cls):
old_getattribute = getattr(cls, '__getattribute__')
def new_getattribute(self, name):
print "attribute name of instance of class {} = {}".format(cls.__name__, name)
return old_getattribute(self, name)
cls.__getattribute__ = new_getattribute
return cls
这可能会打印出比您想要的更多 - 例如,在f = Foo(10)
期间,它会显示正在查找的__class__
属性(but not __init__
)。您当然可以过滤掉您不想记录的任何名称。
这也有一些限制,包装和委托不起作用。它不会在旧式课程上工作;你不能用它动态地包装内置或C扩展类;另一方面,它确实意味着Foo
有自己的名称,文档字符串等,而不是Decor
,而不必在functools.wraps
上复制它们}式的。
*请注意,文档说您应该始终在__getattribute__
内调用属性访问的基类版本,但在这种情况下,您要调用cls
的原始版本版本 - 如果cls
没有定义一个版本,它当然是基类版本。