python koans:类代理

发布于 2024-12-21 14:34:08 字数 3169 浏览 3 评论 0原文

我正在解决python koans。 直到34号我才遇到任何真正的问题。

这就是问题所在:

项目:创建代理类

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

代理类已为您启动。您需要添加一个方法 缺少处理程序和任何其他支持方法。规格 Proxy 类的定义在 AboutProxyObjectProject 公案中给出。

注意:这比 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< /code> 由代理对象而不是电视对象获取。
我尝试操作 __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__中。

I'm solving the python koans.
I haven't got any real problem until the 34th.

this is the problem:

Project: Create a Proxy Class

In this assignment, create a proxy class (one is started for you
below). You should be able to initialize the proxy object with any
object. Any attributes called on the proxy object should be forwarded
to the target object. As each attribute call is sent, the proxy
should record the name of the attribute sent.

The proxy class is started for you. You will need to add a method
missing handler and any other supporting methods. The specification
of the Proxy class is given in the AboutProxyObjectProject koan.

Note: This is a bit trickier that it's Ruby Koans counterpart, but you
can do it!

and this is my solution until now:

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

It works until this test:

def test_proxy_records_messages_sent_to_tv(self):
    tv = Proxy(Television())

    tv.power()
    tv.channel = 10

    self.assertEqual(['power', 'channel='], tv.messages())

where tv.messages() is ['power'] because tv.channel=10 is taken by the proxy object and not the television object.
I've tried to manipulate the __setattr__ method, but I always end in a unlimited loop.

edit 1:

I'm trying this:

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

But then I get this error in a loop on the last entry:

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:      

The loop is in __getattr__.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

又怨 2024-12-28 14:34:08

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

在您的 __init__ 中,您有以下行:

  self._count = {}

这会使用 '_count' 调用 __setattr__ ,此时该值不存在(因此 >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)

You are using hasattr in __setattr__ to decide whether you should write to the local or proxied object. This works well for all but one case.

In your __init__ you have the following line:

  self._count = {}

This calls __setattr__ with '_count' which does not exist at that point and therefore (hence hasattr returns False) is forwarded to the proxied object.

If you want to use your approach you have to write your __init__ like this:

def __init__(self, target_object):
    object.__setattr__(self, '_count', {})
    #initialize '_obj' attribute last. Trust me on this!
    object.__setattr__(self, '_obj', target_object)
聆听风音 2024-12-28 14:34:08

据我了解,您的问题可能与设置属性值时的递归调用有关。 来自文档

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

As I understand maybe your problem is related with the recursive call when you set and attribute value. From docs:

If __setattr__() wants to assign to an instance attribute, it should not simply execute "self.name = value" -- this would cause a recursive call to itself. Instead, it should insert the value in the dictionary of instance attributes, e.g., "self.__dict__[name] = value". For new-style classes, rather than accessing the instance dictionary, it should call the base class method with the same name, for example, "object.__setattr__(self, name, value)".

找个人就嫁了吧 2024-12-28 14:34:08

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)

相反,我们
可以假设分配是针对代理的,除非它具有 _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 is called on all assignments. It's more like getattribute than getattr. This also affects code in the __init__ method.

This means that the first branch of this code will almost always fail, only attributes inherited from object will pass the test:

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

Instead we
can assume that assignments are meant for the Proxy unless it has an _obj attribute. Hence the comment in __init__. We set up our proxy's attributes, then add the target object and all future assignments get sent to it.

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

But by using hasattr we would also need to alter __getattr__ to check for _obj to prevent recursion:

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)

An alternative would be to inspect the proxy's __dict__ attribute directly in the __setattr__ method:

def __setattr__(self, name, value):
    if '_obj' in self.__dict__:
    ...
未蓝澄海的烟 2024-12-28 14:34:08

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

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_known',...)

抱歉,necro'ing老问题。

我发现 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)

from the test, it is a requirement for proxy to log all the attribute calls via proxy. And the proxy has only few built-in methods which are exceptionally used for logging, so my answer was:

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)

After this it is quite easy to implement other methods ('messages', 'was_called', ... )

Sorry for necro'ing old question.

and I found out that getattribute can be changed : just check whether the attribute is in the target object.

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)
林空鹿饮溪 2024-12-28 14:34:08
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
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
仅此而已 2024-12-28 14:34:08

我所做的是对代理中的属性进行所有调用,并通过 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


What I did was take all the calls to attributes in the proxy and call them via object.__getattribute__ to avoid recursion.

That did not work for methods so I wrapped the method calls in a try..except AttributeError to try them first in the proxy. and then if they raise an error try them in the child object.

If anyone has a more elegant solution would love to see it.

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


你穿错了嫁妆 2024-12-28 14:34:08

为什么不使用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

why not use method_missing?
my answer:

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文