Python Koans:类代理

2 投票
7 回答
3606 浏览
提问于 2025-04-17 08:36

我正在解决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 个回答

1

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__:
    ...
4

我理解你的问题可能和递归调用有关,特别是在你设置属性值的时候。根据文档的说明:

如果__setattr__()想要给一个实例属性赋值,它不能直接执行"self.name = value",因为这样会导致它自己又调用自己,形成递归。正确的做法是把值放进实例属性的字典里,比如"self.__dict__[name] = value"。对于新式类来说,不应该直接访问实例字典,而是应该调用基类中同名的方法,比如"object.__setattr__(self, name, value)"。

6

你在 __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)

撰写回答