Python - мога ли да обвия присвояването на атрибут, без да пиша getter/setter

Целта на този въпрос е да се определи дали мога или не да обвия настройката на атрибут на обект, без просто да напиша сетер и след това да обвия сетера.

Опитвам се да внедря модел на наблюдател и не искам да пиша повече код, отколкото трябва (така че, разбира се, ще напиша голям дълъг въпрос за 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

Но бих искал да мога да внедря модела Observer по такъв начин, че когато 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
Плъхове, досега можех да правя всичко това със собствените си опаковки... толкова близо :/ Предполагам, че затова съществуват тези неща, б/в по-умните хора от мен създадоха по-добър капан за мишки. Bugger :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, int...) е въпрос на просто проверка за 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