python koans:类代理

时间:2011-12-18 20:10:33

标签: python proxy-classes setattr

我正在解决the python koans。 直到34日我才有任何实际问题。

这就是问题:

  

项目:创建代理类

     

在此作业中,创建一个代理类(一个是为您启动的   下面)。您应该能够使用any初始化代理对象   宾语。应转发在代理对象上调用的任何属性   到目标对象。在发送每个属性调用时,代理   应该记录发送的属性的名称。

     

代理类是为您启动的。您需要添加一个方法   缺少处理程序和任何其他支持方法。规范   Proxy类的内容在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 个答案:

答案 0 :(得分:5)

您正在hasattr中使用__setattr__来决定是否应该写入本地或代理对象。除了一个案例之外,这适用于所有情况。

__init__中,您有以下内容:

  self._count = {}

这会调用带有__setattr__的{​​{1}},此时此时不存在,因此(因此'_count'返回hasattr)会转发到代理对象。

如果你想使用你的方法,你必须像这样写False

__init__

答案 1 :(得分:4)

据我所知,当你设置和属性值时,你的问题可能与递归调用有关。 From docs

如果__setattr__()想要分配给实例属性,则不应该只执行“self.name = value” - 这会导致对自身的递归调用。相反,它应该在实例属性的字典中插入值,例如“self.__dict__[name] = value”。对于新式类,它应该调用具有相同名称的基类方法,而不是访问实例字典,例如“object.__setattr__(self, name, value)”。

答案 2 :(得分:1)

在所有作业中调用setattr。它更像是getattribute而不是getattr。这也会影响__init__方法中的代码。

这意味着此代码的第一个分支几乎总是会失败,只有从object继承的属性才能通过测试:

def __setattr__(self, name, value):
    if hasattr(self, name):
        object.__setattr__(self,name,value)
    else: 
        object.__setattr__(self._obj, name, value)

相反,我们  可以假设分配是针对代理,除非它具有_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__:
    ...

答案 3 :(得分:1)

从测试开始,代理需要通过代理记录所有属性调用。代理只有很少的内置方法,特别用于日志记录,所以我的答案是:

class Proxy(object):

    def __init__(self, target_object):
        self.logs=[]
        self._obj = target_object


    def __getattribute__(self, attrname):
        if attrname in ['_obj','logs','messages','was_called','number_of_times_called'] :
            return object.__getattribute__(self, attrname)
        else:
            self.logs.append(attrname)
            return object.__getattribute__((object.__getattribute__(self, '_obj')), attrname)


    def __setattr__(self, name, value):
        if hasattr(self, '_obj'):
            self.logs.append(name)
            object.__setattr__(object.__getattribute__(self,'_obj'), name, value)
        else :
            object.__setattr__(self, name, value)

在此之后很容易实现其他方法('messages','was_called',...)

抱歉老问题。

我发现 getattribute 可以更改:只需检查该属性是否在目标对象中。

def __getattribute__(self, attrname):
        if attrname not in dir(object.__getattribute__(self, '_obj')):
            return object.__getattribute__(self, attrname)
        else:
            self.logs.append(attrname)
            return object.__getattribute__((object.__getattribute__(self, '_obj')), attrname)

答案 4 :(得分:0)

class Proxy(object):
   """Proxy class wraps any other class, and adds functionality to remember and report all messages called.
Limitations include that proxy blocks all direct subclass calls to:
messages, number_of_times_called, was_called, _obj, and _message_counts.
These calls must be made directly like my_proxy_instance._obj.messages.
"""


def __init__(self, target_object):
    print 'initializing a proxy for ' + target_object.__class__.__name__
    # WRITE CODE HERE
    self._message_counts = Counter();
    #initialize '_obj' attribute last. Trust me on this!
    self._obj = target_object

# WRITE CODE HERE                                   
def  __getattr__(self, attr_name):
    print 'getting an attribute: "' + attr_name + '" from "' + self._obj.__class__.__name__  + '"'
    self._message_counts[attr_name] += 1
    print self._message_counts
    return object.__getattribute__(self._obj, attr_name)

#def __getattribute__(self, attr_name):
#    print "intercepted!~ " + attr_name
#    object.__getattribute__(self, attr_name)

def __setattr__(self, attr_name, value):
    if((attr_name == '_obj') | (attr_name == '_message_counts')): # special proxy attributes.
        print 'setting the PROXY attribute: "' + attr_name + '"'
        object.__setattr__(self, attr_name, value)
    else:
        print 'setting the REAL attribute: "' + attr_name + '"'
        self._message_counts[attr_name+"="] += 1
        object.__setattr__(self._obj, attr_name, value)

def messages(self):
    return self._message_counts.keys()

def number_of_times_called(self, attr_name):
    return self._message_counts[attr_name]

def was_called(self, attr_name):
    return attr_name in self._message_counts

答案 5 :(得分:0)

我所做的是接受代理中所有属性的调用,并通过object.__getattribute__调用它们以避免递归。

这对方法不起作用,所以我将方法调用包装在try..except AttributeError中,首先在代理中尝试它们。如果他们引发错误,请在子对象中尝试它们。

如果有人有更优雅的解决方案,我很乐意看到它。

from runner.koan import *
from collections import Counter

class Proxy(object):
    def __init__(self, target_object):
        self._messages=[]
        self._obj = target_object

    def messages(self):
        return self._messages

    def was_called(self, message):
        return message in self._messages

    def number_of_times_called(self, message):
        _count = Counter(self._messages).get(message)
        if _count: 
            return _count
        else: # catch None
            return 0

    def __getattribute__(self, attr_name):
        try: # call on self
            retval = object.__getattribute__(self, attr_name)
        except AttributeError: # call on child object
            retval = self._obj.__getattribute__(attr_name)
            object.__getattribute__(self, '_messages').append(attr_name)

        return retval

    def __setattr__(self, attr_name, attr_value):
        if hasattr(self, '_obj'): # call child object and log message
            self._obj.__setattr__(attr_name, attr_value)
            attr_name += "="
            object.__getattribute__(self, '_messages').append(attr_name)
        else: # use this before_obj is set in __init__
            object.__setattr__(self, attr_name, attr_value)

    def messages(self):
        return self._messages


答案 6 :(得分:0)

为什么不使用method_missing?
我的回答:

class Proxy
  def initialize(target_object)
    @object = target_object
    # ADD MORE CODE HERE
    @messages = []
  end

  # WRITE CODE HERE
  def method_missing(method_name, *args, &block)
    @messages.push method_name unless method_name == :messages
    @object.send method_name, *args, &block
  end
  def messages
    @messages
  end
  def called? target
    @messages.include? target
  end
  def number_of_times_called target
    result = 0
    @messages.each do |t|
      result += 1 if t == target
    end
    result
  end
end