Python Koans:类代理
我正在解决Python Koans的练习。到现在为止,我没有遇到什么真正的问题,直到第34个。
这是问题:
项目:创建一个代理类
在这个任务中,你需要创建一个代理类(下面已经给你开始了一个)。你应该能够用任何对象来初始化这个代理对象。对代理对象调用的任何属性都应该转发到目标对象上。每次调用属性时,代理应该记录下被调用的属性名称。
代理类已经为你开始了。你需要添加一个方法缺失处理器和其他支持方法。关于代理类的具体要求可以在AboutProxyObjectProject的koan中找到。
注意:这个任务比Ruby Koans中的对应任务要复杂一些,但你可以做到的!
这是我到目前为止的解决方案:
class Proxy(object):
def __init__(self, target_object):
self._count = {}
#initialize '_obj' attribute last. Trust me on this!
self._obj = target_object
def __setattr__(self, name, value):pass
def __getattr__(self, attr):
if attr in self._count:
self._count[attr]+=1
else:
self._count[attr]=1
return getattr(self._obj, attr)
def messages(self):
return self._count.keys()
def was_called(self, attr):
if attr in self._count:
return True
else: False
def number_of_times_called(self, attr):
if attr in self._count:
return self._count[attr]
else: return False
这个方案在这个测试之前都能正常工作:
def test_proxy_records_messages_sent_to_tv(self):
tv = Proxy(Television())
tv.power()
tv.channel = 10
self.assertEqual(['power', 'channel='], tv.messages())
在这里,tv.messages()
返回的是['power']
,因为tv.channel=10
是由代理对象处理的,而不是电视对象。
我尝试过修改__setattr__
方法,但总是陷入无限循环。
编辑 1:
我正在尝试这个:
def __setattr__(self, name, value):
if hasattr(self, name):
object.__setattr__(self,name,value)
else:
object.__setattr__(self._obj, name, value)
但是在最后一个条目上我遇到了这个错误,循环不断:
RuntimeError: maximum recursion depth exceeded while calling a Python object
File "/home/kurojishi/programmi/python_koans/python 2/koans/about_proxy_object_project.py", line 60, in test_proxy_method_returns_wrapped_object
tv = Proxy(Television())
File "/home/kurojishi/programmi/python_koans/python 2/koans/about_proxy_object_project.py", line 25, in __init__
self._count = {}
File "/home/kurojishi/programmi/python_koans/python 2/koans/about_proxy_object_project.py", line 33, in __setattr__
object.__setattr__(self._obj, name, value)
File "/home/kurojishi/programmi/python_koans/python 2/koans/about_proxy_object_project.py", line 36, in __getattr__
if attr in self._count:
这个循环出现在__getattr__
中。
7 个回答
setattr在所有赋值操作时都会被调用。它的工作方式更像是getattribute,而不是getattr。这也会影响到__init__方法中的代码。
这意味着这段代码的第一个分支几乎总是会失败,只有从对象继承来的属性才能通过测试:
def __setattr__(self, name, value):
if hasattr(self, name):
object.__setattr__(self,name,value)
else:
object.__setattr__(self._obj, name, value)
所以我们可以假设赋值是针对Proxy的,除非它有一个_obj属性。因此在__init__方法中的注释就是这个意思。我们先设置代理的属性,然后添加目标对象,之后所有的赋值操作都会发送给它。
def __setattr__(self, name, value):
if hasattr(self, '_obj'):
object.__setattr__(self._obj, name, value)
else:
object.__setattr__(self, name, value)
但是如果使用hasattr,我们还需要修改__getattr__,以检查_obj属性,防止出现递归调用:
def __getattr__(self, name):
if '_obj' == name:
raise AttributeError
if attr in self._count:
self._count[attr]+=1
else:
self._count[attr]=1
return getattr(self._obj, attr)
另一种方法是在__setattr__方法中直接检查代理的__dict__属性:
def __setattr__(self, name, value):
if '_obj' in self.__dict__:
...
我理解你的问题可能和递归调用有关,特别是在你设置属性值的时候。根据文档的说明:
如果__setattr__()
想要给一个实例属性赋值,它不能直接执行"self.name = value
",因为这样会导致它自己又调用自己,形成递归。正确的做法是把值放进实例属性的字典里,比如"self.__dict__[name] = value
"。对于新式类来说,不应该直接访问实例字典,而是应该调用基类中同名的方法,比如"object.__setattr__(self, name, value)
"。
你在 __setattr__
里使用了 hasattr
来判断是写入本地对象还是代理对象。这种方法在大多数情况下都很好用,但有一个例外。
在你的 __init__
方法中,有这么一行代码:
self._count = {}
这行代码调用了 __setattr__
,传入的参数是 '_count'
,但这个时候 '_count'
还不存在,因此 hasattr
返回 False
,结果就被转发到了代理对象上。
如果你想继续使用这种方法,你需要把你的 __init__
方法写成这样:
def __init__(self, target_object):
object.__setattr__(self, '_count', {})
#initialize '_obj' attribute last. Trust me on this!
object.__setattr__(self, '_obj', target_object)