питон коаны: прокси класса - PullRequest
1 голос
/ 19 декабря 2011

Я решаю питон коаны .У меня нет никаких реальных проблем до 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 берется прокси-объектом, а нетелевизионный объект.
Я пытался манипулировать методом __setattr__, но я всегда заканчиваю бесконечный цикл.

edit 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__.

Ответы [ 6 ]

5 голосов
/ 19 июня 2012

Вы используете hasattr в __setattr__, чтобы решить, следует ли писать в локальный или прокси-объект.Это хорошо работает для всех случаев, кроме одного.

В вашем __init__ есть следующая строка:

  self._count = {}

Это вызывает __setattr__ с '_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)
4 голосов
/ 19 декабря 2011

Как я понимаю, возможно, ваша проблема связана с рекурсивным вызовом, когда вы устанавливаете значение атрибута. Из документов :

Если __setattr__() хочет присвоить атрибуту экземпляра, он не должен просто выполнять "self.name = value" - это вызовет рекурсивный вызов для себя.Вместо этого он должен вставить значение в словарь атрибутов экземпляра, например, "self.__dict__[name] = value".Для классов нового стиля вместо доступа к словарю экземпляров следует вызывать метод базового класса с тем же именем, например, "object.__setattr__(self, name, value)".

1 голос
/ 05 октября 2012

из теста, для прокси необходимо регистрировать все вызовы атрибута через прокси. И у прокси есть только несколько встроенных методов, которые исключительно используются для регистрации, поэтому мой ответ был:

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)
1 голос
/ 12 января 2012

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)

Альтернативой может быть проверка атрибута прокси __dict__ непосредственно в методе __setattr__:

def __setattr__(self, name, value):
    if '_obj' in self.__dict__:
    ...
0 голосов
/ 03 июня 2012

Я сделал все вызовы атрибутов в прокси и вызвал их через 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


0 голосов
/ 04 февраля 2012
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
...