【问题标题】:Python - can I wrap attribute assignment without writing a getter/setterPython - 我可以在不编写 getter/setter 的情况下包装属性赋值吗
【发布时间】:2014-05-09 10:23:07
【问题描述】:

这个问题的目的是确定我是否可以包装设置一个对象的属性,而不只写一个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"

如果我只写一个 setter,它会成为一个简单的钩子,比如:

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

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

但我希望能够以这样一种方式实现观察者模式,即每当obj.x 因任何原因发生更改时,侦听器都会更新。例如,如果obj.xint,我可以直接设置它或使用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 包装直接,但我希望创建一些我可以在以下(虚构的伪代码)方式中使用的东西:

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

但是 this 是我更愿意在事后包装 setter 的原因。我有两个不同的对象,它们不一定需要彼此有硬编码的数据;只是一个带有self.score 属性的玩家对象和一个带有self.text 属性的记分牌对象,虽然在它们之间编写“胶水”当然是必要的,但我希望编写set_scoreset_text 的方法不会仅仅实现观察者模式并不重要。

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

虽然现在这是一个非常具体的示例,但我也是在一般意义上提出问题,因为能够只观察属性的变化而不是围绕每个属性进行编码以做好准备似乎真的很方便也许正在观察其他对象的一些变化。 :/

【问题讨论】:

  • 为什么不让它成为一个属性?
  • 也许我不明白 @property 应该做什么 - 我看过它(在你的建议之前和之后),但我没有看到它对我有什么帮助

标签: python observer-pattern


【解决方案1】:

新式类实例的特殊功能是根据它们的类而不是实例来查找的,所以:

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"

【讨论】:

    【解决方案2】:

    特殊方法、描述符和所有其他使用属性访问的方法仅适用于类。您不能在对象上覆盖它们。

    使用事件(或发布/订阅或我们现在所称的任何内容)系统。让角色负责发出事件,而不是具体记住谁想要它们。

    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
    

    对象必然会在某处纠缠在一起,但它会成为实现细节,而不是代码的主要部分。特别是,记分牌不必是全局的,即使记分牌不存在,角色仍然可以正常工作。

    this question 上有实现和现有库的示例,但如果您正在编写游戏,则可能您已经在使用内置的库。我知道 pyglet 可以。

    【讨论】:

    • 老鼠,到目前为止,我可以用我自己的包装器来完成所有这一切......如此接近:/ 我想这就是这些东西存在的原因,b/c 比我更聪明的人建造了一个更好的捕鼠器。 Bugger :D 非常感谢你给了我一些新的阅读内容
    • 写一个事件库并不难;当几十个已经存在时,没有理由打扰:)
    • 有什么原因为什么会这样吗?是否有意识地决定不允许以与obj.method_i_wrote() 相同的方式包装属性访问?最近我在包装函数方面已经取得了很多进展,所以我发现这种情况有点令人惊讶。
    • 我相信这主要是为了速度。考虑到不起眼的obj.method_i_wrote() 已经需要查找并调用__getattr____call__。如果这些查找每次都必须通过对象的__dict__ 进行搜索,那么它将给 Python 中的 every 操作增加大量开销。与我们现在所拥有的相比,在 C 类(objectdictint...)上查找特殊方法只需检查右侧类槽中的 NULL 指针。
    • 我想我不知道 Python 的底层是什么,或者为什么,所以我相信你的话。无论如何,我有一个闪亮的新消息可以玩
    猜你喜欢
    • 1970-01-01
    • 2017-04-10
    • 1970-01-01
    • 1970-01-01
    • 2010-09-05
    • 2020-12-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多