Python - 有没有方法在不写getter/setter的情况下包装属性赋值

5 投票
2 回答
2105 浏览
提问于 2025-04-18 00:30

这个问题的目的是想知道我是否可以在不单独写一个设置器的情况下,来包装设置一个对象的属性。

我正在尝试实现观察者模式,但我不想写太多的代码(所以我当然会写一个长长的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.set_x()obj.add_to_x()obj.subtract_from_x()obj.times_x() 等等的情况下,包装任何/所有对 obj.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 包装的函数,但我希望能创建一个可以在以下(虚构的伪代码)方式中使用的东西:

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_scoreset_text 方法并不是实现观察者模式的关键。

如果最终我不能跳过写设置器(或多个设置器),那我想我就只能这样做;我只是希望能避免这样。

而且虽然这现在是一个非常具体的例子,但我也在以一种通用的方式提问,因为能够只关注一个属性的变化,而不是在每个属性周围编写代码,以便准备好可能观察其他对象的一些变化,似乎真的很方便。 :/

2 个回答

0

新式类的实例特殊功能是通过类来查找的,而不是通过实例本身来查找。因此:

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"
1

特殊方法、描述符以及其他处理属性访问的方式只在类上有效。你不能在对象上重写这些方法。

使用事件系统(或者现在我们叫的发布/订阅系统)。让角色负责发出事件,而不是记住具体谁想要这些事件。

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 就有这个功能。

撰写回答