質問

The goal of this question is to determine whether or not I can wrap setting an object's attribute, without just writing a setter and then wrapping the setter.

I am trying to implement an Observer pattern and I don't want to write more code than I need to (so of course I'll write a big long StackOverflow question, hah - I figure the long-term payoff is worth it).

I started experimenting by trying to wrap obj.__setattr__ with a function but it did not do what I expected it would do, so now I am wondering if I can even wrap the assignment or changing of an object's attribute if I do not just write a setter.

This is what I tried:

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"

If I just write a setter it would make for an easy hook, something like:

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

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

But I'd like to be able to implement the Observer pattern in such a way that whenever obj.x changes for any reason, the listener updates. If obj.x is an int for example I could be either setting it outright or using obj.x += some_int to set it, and so I'm wondering if there is a way of wrapping any/all setting of obj.x without, for example, writing obj.set_x(), obj.add_to_x(), obj.subtract_from_x(), obj.times_x(), etc etc.

EDIT: Thanks for pointing me at properties, but I don't see how it can help me to wrap this in the manner I'm implementing thus far.

For example, if I have an object as such:

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

...that's fine, and I see that I can modify the function @x.setter wraps directly, but I was hoping to create something which I can use in the following (imaginary pseudocode) way:

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

...such that when A.x changes, observing_function is called and does what it is going to do.

If it informs the answer at all -- I am working on a 'scoreboard' to display the score and lives of a central character in a video game. Instead of constantly checking during each loop or setting over and over (which I'm doing now, which seems excessive), I just want it to actually fire when the character's score/lives change, and a wrapper seemed the nicest way to do that.

I was hoping to avoid this:

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

...because that to me is awful, even though it'd work. Also it seems like a lot of code just to establish an observer pattern for two variables :P

But this is why I'd prefer to wrap the setter after the fact. I have two different objects that don't necessarily need to have hard-coded data about each other; just a player object with a self.score attribute and a scoreboard object with a self.text attribute, and while writing the 'glue' between them is of course necessary, I'd hoped writing methods to set_score and set_text wouldn't be crucial just to implement an Observer pattern.

If ultimately I can't skip writing a setter (or setters), then I suppose I'll go ahead and do it that way; I'd just hoped to avoid it.

And really while this is now a very specific example, I am also asking in a generic sense as well, because it seems really handy to be able to just watch an attribute for changes instead of coding around every attribute to be ready for maybe watching some changes of some other object. :/

役に立ちましたか?

解決

Special methods, descriptors, and all other ways of mucking with attribute access only work on classes. You can't override them on objects.

Use an event (or pub/sub or whatever we're calling it now) system. Make the character responsible for emitting events, but not for remembering specifically who wants them.

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

The objects necessarily get entangled somewhere, but it becomes an implementation detail rather than a major part of your code. In particular, the scoreboard doesn't have to be a global, and the character still works fine even when the scoreboard doesn't exist.

There are examples of implementations and existing libraries on this question, but if you're writing a game, it's possible you're already using a library that has its own built in. I know pyglet does.

他のヒント

Special functions of new-style class instances are looked up against their class not the instance, so:

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"
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top