这个问题的目标是确定我是否可以包装设置对象的属性,而不必只编写 setter 然后包装 setter。
我正在尝试实现一个观察者模式,并且我不想编写超出我需要的代码(所以我当然会写一个又大又长的 StackOverflow 问题,哈 - 我认为长期回报是值得的)。
我开始尝试用obj.__setattr__一个函数来包装,但它没有达到我预期的效果,所以现在我想知道如果我不只编写一个 setter,我是否可以包装对象属性的赋值或更改。
这是我尝试过的:
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"
Run Code Online (Sandbox Code Playgroud)
如果我只写一个设置器,它将成为一个简单的钩子,例如:
class A(object):
def __init__(self, x):
self.x = x
def set_x(self, new_x):
self.x = x
Run Code Online (Sandbox Code Playgroud)
但我希望能够以这样的方式实现观察者模式:每当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
Run Code Online (Sandbox Code Playgroud)
...那很好,我看到我可以直接@x.setter修改函数包装,但我希望创建一些可以通过以下(想象的伪代码)方式使用的东西:
A.x.setter = observing_function(A, A.x.setter)
Run Code Online (Sandbox Code Playgroud)
...这样当A.x发生变化时,observing_function就会被调用并执行它将要做的事情。
如果它能提供答案的话——我正在开发一个“记分板”来显示视频游戏中中心人物的得分和生活。我不想在每个循环期间不断检查或一遍又一遍地设置(我现在正在这样做,这似乎有点过分),我只是希望它在角色的分数/生命发生变化时真正触发,而包装器似乎是最好的方法。
我希望避免这种情况:
def add_to_score(self, pts):
self.score += pts
scoreboard_object.text = self.score
Run Code Online (Sandbox Code Playgroud)
...因为这对我来说很糟糕,尽管它会起作用。另外,似乎有很多代码只是为了为两个变量建立观察者模式:P
但这就是为什么我更愿意在事后包装设置器。我有两个不同的对象,它们不一定需要具有彼此的硬编码数据;只是一个具有属性的玩家对象self.score和一个具有self.text属性的记分板对象,虽然在它们之间编写“粘合剂”当然是必要的,但我希望编写方法来实现观察者模式,set_score而这set_text并不是至关重要的。
如果最终我无法跳过编写一个(或多个)设置器,那么我想我会继续这样做;我只是希望能避免它。
实际上,虽然这现在是一个非常具体的示例,但我也在一般意义上提出问题,因为能够仅监视属性的更改而不是围绕每个属性进行编码以准备好观看某些属性似乎非常方便其他一些对象的变化。:/
特殊方法、描述符和所有其他破坏属性访问的方法仅适用于类。您无法在对象上覆盖它们。
使用事件(或发布/订阅或任何我们现在所说的)系统。让角色负责发出事件,但不负责具体记住谁想要这些事件。
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
Run Code Online (Sandbox Code Playgroud)
这些对象必然会在某个地方纠缠在一起,但它变成了一个实现细节,而不是代码的主要部分。特别是,记分板不必是全局的,即使记分板不存在,角色仍然可以正常工作。
关于这个问题有实现和现有库的示例,但如果您正在编写游戏,则可能您已经在使用内置的库。我知道 pyglet 确实如此。