为复杂和重复的函数调用创建类似 属性 的解决方案

Creating a property-like solution for complicated and repetitive function calls

我正在扩展现有的 PlayerClassFromGameEngine class 以允许自定义效果仅在特定持续时间内生效。

示例: 使用原始的 class,我会说 player.move_type = MoveTypes.Freeze 冻结玩家,然后说 player.move_type = MoveTypes.Normal 解冻他。 现在我想扩展 class,这样我就可以改用函数调用:player.freeze(5),将播放器冻结五秒钟。

我显然需要两个函数,效果函数和撤消函数,f.e。 freeze()unfreeze()。 这是我当前的 class,效果很好:

class MyPlayer(PlayerClassFromGameEngine):
    def __init__(self, index):
        super().__init__(index)  # Let the original game engine handle this
        self.effects = defaultdict(set)

    def freeze(self, duration):
        self.move_type = MoveType.FREEZE  # Move type comes from the super class
        thread = threading.Thread(target=self._unfreeze)
        thread.args = (duration, thread)
        self.effects['freeze'].add(thread)
        thread.start()

    def _unfreeze(self, duration, thread):
        time.sleep(duration)
        self.effects['freeze'].remove(thread)
        if not self.effects['freeze']:  # No more freeze effects
            self.move_type = MoveType.NORMAL

如您所见,只有一个效果需要 10 多行代码,其中 20 行会很糟糕,因为它们的工作方式完全相同,只是键不同 ('freeze', burn, 等等),有些调用函数而不是访问 move_type 属性.

我基本上不知道从哪里开始,也许是描述符和装饰器,但有人能给我一些建议,或者更好的工作解决方案吗?


编辑: 这是我根据 Martijn 的建议想到的,但它不起作用,因为我无法访问 Effect class

中的播放器
from collections import defaultdict
from threading import Thread
from time import sleep

class Effect(object):
    def __init__(self, f, undo_f=None):
        self.f = f
        self.undo_f = undo_f
        self._thread = None

    def __call__(self, duration):
        self._thread = Thread(target=self._execute, args=(duration, ))
        self._thread.start()

    def _execute(self, duration):
        self.f()
        sleep(duration)
        self.undo_f()

    def undo(self, undo_f):
        return type(self)(self.f, undo_f)

class Player:
    def __init__(self, index):
        self.index = index
        self._effects = defaultdict(set)

    @Effect
    def freeze(self):
        print('FROZEN')

    @freeze.undo
    def freeze(self):
        print('UNFROZEN')

p = Player(1)
p.freeze(3)

我想我需要的是以某种方式访问​​ Effect class 内部的播放器,因为我无法在 self.f(player)self.undo_f(player) 中调用=26=] 方法,我也无法访问播放器的 effects 字典。 我想我在任何地方都不需要 key 参数,因为我可以为每个效果生成一个随机数(唯一的一个),因为它不会显示给任何人。

这是一个可行的方法:

from functools import partial
import time
import threading


class aftereffect(object):
    def __init__(self, func):
        self._func = func
        self._aftereffect = lambda instance: None


    def aftereffect(self, func):
        self._aftereffect = func
        return self


    def __get__(self, instance, cls):
        # this is the descriptor protocol, and
        # instance is the actual object

        def delayed_effect(timeout):
            time.sleep(timeout)
            self._aftereffect(instance)

        def caller(*args, **kwargs):
            timeout = kwargs.pop("_timeout", 1.)
            t = threading.Thread(target=partial(delayed_effect, timeout=timeout))
            t.start()
            return self._func(*args, **kwargs)

        return caller.__get__(instance, cls)


class Thing(object):
    @aftereffect
    def something(self):
        print "something", self

    @something.aftereffect
    def something(self):
        print "after_something", self


t = Thing()
t.something()
t.something(_timeout=5)