Python - могу ли я обернуть назначение атрибута без написания получателя / установщика

Цель этого вопроса - определить, могу ли я обернуть установку атрибута объекта без, просто написав установщик и затем обернув установщик.

Я пытаюсь реализовать шаблон Observer, и я не хочу писать больше кода, чем мне нужно (поэтому, конечно, я напишу большой длинный вопрос StackOverflow, ха - я считаю, что долгосрочная отдача того стоит).

Я начал экспериментировать, пытаясь обернуть obj.__setattr__ функцией, но она не сделала того, что я ожидал, поэтому теперь мне интересно, могу ли я даже обернуть присвоение или изменение атрибута объекта, если я не просто напишу установщик.

Вот что я пробовал:

class A(object):
    pass

def wrapper(obj, func):
    def inner(*args, **kwargs):
        print "called it"
        return func(*args, **kwargs)
    return inner

Stick = A()
Stick.__setattr__ = wrapper(Stick, Stick.__setattr__)
Stick.x = 14    #does not print "called it"

Если я просто напишу сеттер, получится простой крючок, например:

class A(object):
    def __init__(self, x):
        self.x = x

    def set_x(self, new_x):
        self.x = x

Но я хотел бы иметь возможность реализовать шаблон наблюдателя таким образом, чтобы всякий раз, когда obj.x изменяется по какой-либо причине, слушатель обновлялся. Если obj.x - это int, например, я мог бы либо установить его напрямую, либо использовать obj.x += some_int для его установки, и поэтому мне интересно, есть ли способ обернуть любые / все настройки obj.x без, например, написания obj.set_x(), obj.add_to_x(), obj.subtract_from_x(), obj.times_x() и т. Д. И т. Д.

РЕДАКТИРОВАТЬ: Спасибо, что указали мне на свойства, но я не понимаю, как это может помочь мне обернуть это так, как я реализовываю до сих пор.

Например, если у меня есть объект как таковой:

class Foo(object):
    def __init__(self, ex):
        self._x = ex
    @property
    def x(self):
        return self._x
    @x.setter
    def x(self, value):
        self._x = value

... это нормально, и я вижу, что могу изменить функцию @x.setter wraps напрямую, но я надеялся создать что-то, что можно было бы использовать следующим образом (воображаемый псевдокод):

A.x.setter = observing_function(A, A.x.setter)

... таким образом, что когда A.x изменяется, observing_function вызывается и выполняет то, что собирается делать.

Если это вообще дает ответ - я работаю над «табло», чтобы отображать счет и жизни центрального персонажа в видеоигре. Вместо постоянной проверки во время каждого цикла или настройки снова и снова (что я делаю сейчас, что кажется чрезмерным), я просто хочу, чтобы он действительно срабатывал, когда счет / жизни персонажа меняются, и оболочка казалась лучшим способом сделать это .

Я надеялся избежать этого:

def add_to_score(self, pts):
    self.score += pts
    scoreboard_object.text = self.score

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

Но это, почему я бы предпочел обернуть установщик постфактум. У меня есть два разных объекта, которые не обязательно должны иметь жестко закодированные данные друг о друге; просто объект игрока с атрибутом self.score и объект табло с атрибутом self.text, и хотя, конечно, необходимо написать «клей» между ними, я надеялся, что написание методов для set_score и set_text не будет иметь решающего значения, просто чтобы реализовать Паттерн наблюдателя.

Если в конечном итоге я не могу пропустить написание сеттера (или сеттеров), то, полагаю, я продолжу и сделаю это таким же образом; Я просто надеялся избежать этого.

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


person Community    schedule 30.03.2014    source источник
comment
Почему бы не сделать это собственностью?   -  person Ignacio Vazquez-Abrams    schedule 30.03.2014
comment
Возможно, я не понял, что должен делать @property - я смотрел на него (до и после вашего предложения), но не видел, как это мне поможет   -  person    schedule 30.03.2014


Ответы (2)


Специальные методы, дескрипторы и все другие способы избавления от доступа к атрибутам работают только с классами. Вы не можете переопределить их на объектах.

Используйте систему событий (или pub / sub, или как мы ее сейчас называем). Сделайте персонажа ответственным за отправку событий, но не за то, чтобы он точно запомнил, кто их хочет.

class ScoreChanged(Event):
    ...

class Character(ListenerMixin):
    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, new):
        old = self._score
        self._score = new
        self.fire_event(ScoreChanged(self, old, new))

protag = Character()
scoreboard = Scoreboard()
scoreboard.listen_event(protag, ScoreChanged, scoreboard.on_score_change)

protag.score += 10

Объекты обязательно запутываются где-то, но это становится деталью реализации, а не основной частью вашего кода. В частности, табло не обязательно должно быть глобальным, и персонаж по-прежнему работает нормально, даже если табло не существует.

В этом вопросе есть примеры реализаций и существующих библиотек, но если вы пишете игру , возможно, вы уже используете встроенную библиотеку. Я знаю, что у pyglet есть.

person Eevee    schedule 30.03.2014
comment
Крысы, до сих пор я мог делать все это со своими собственными обертками ... так близко: / Полагаю, поэтому эти штуки существуют, ведь люди умнее меня построили лучшую мышеловку. Баггер: D Что ж, спасибо, что дали мне почитать что-то новое - person ; 31.03.2014
comment
написать библиотеку событий не так уж сложно; просто незачем заморачиваться, когда уже существуют десятки :) - person Eevee; 31.03.2014
comment
Есть ли причина, почему это так? Например, было ли сознательное решение не позволять обертывать доступ к атрибутам так же, как это может быть obj.method_i_wrote()? В последнее время я получил массу преимуществ от функций обертывания, поэтому я обнаружил, что эта ситуация стала своего рода сюрпризом. - person ; 31.03.2014
comment
Я считаю, что это в основном из-за скорости. Учтите, что скромному obj.method_i_wrote() уже нужно найти и позвонить как __getattr__, так и __call__. если бы этим поисковым запросам приходилось каждый раз перебирать __dict__ объекта, это добавляло бы значительные накладные расходы на каждую операцию в Python. в отличие от того, что у нас есть сейчас, где поиск специальных методов в классах C (object, dict, _7 _...) - это просто проверка указателя NULL в правильном слоте класса. - person Eevee; 31.03.2014
comment
Полагаю, я понятия не имею, что находится под капотом Python и почему, поэтому верю вам на слово. В любом случае, у меня есть блестящая новинка для обмена сообщениями, с которой можно поиграть. - person ; 01.04.2014

Специальные функции экземпляров класса нового стиля сравниваются с их классом, а не с экземпляром, поэтому:

class A(object):
    pass

def wrapper(func):
    def inner(*args, **kwargs):
        print "called it"
        return func(*args, **kwargs)
    return inner

Stick = A()
A.__setattr__ = wrapper(A.__setattr__)
Stick.x = 14    # prints "called it"
Stick.x *= 2    # also prints "called it"
person martineau    schedule 30.03.2014