【问题标题】:Combining dict in super class's init and subclass's init automatically?自动组合超类的init和子类的init中的dict?
【发布时间】:2015-09-04 03:55:51
【问题描述】:

我正在创建一个事件系统,它使用以下事件类:

class Event(set):
    def __init__(self, name, iterable=()):
        super().__init__(iterable)
        self.name = name

    def __iadd__(self, listener):
        self.add(listener)
        return self

    def __isub__(self, listener):
        self.remove(listener)
        return self

    def fire(self, **eargs):
        for listener in self:
            listener(**eargs)

现在我正在尝试创建某种字典,它会自动在其__init__ 中创建事件,如下所示:

class EventDict(dict):
    def __init__(self, prefix, *event_names):
        super().__init__({
            name: Event('%s.%s' % (prefix, name))
            for name in event_names
        })

下面是一个用法示例:

class Player:
    def __init__(self, name):
        self._name = name
        self.events = EventDict('Player', 'change_name')

    @property
    def name(self):
        returns self._name

    @name.setter
    def name(self, value):
        old_name = self.name
        self.name = value
        self.events['change_name'].fire(player=self, old_name=old_name)

现在我面临的问题是子类化。 如果我要继承我的Player 类以包含health 属性,我不能使用相同的方式来创建事件字典,因为它会覆盖现有的字典,我无法再访问change_name

所以我正在尝试找到一种方法,让我可以做这样的事情(理想的解决方案):

class Player:
    events = EventDict('Player', 'change_name')

class Player2(Player):
    events = EventDict('Player2', 'attack', 'kill')

p2 = Player2()
p2.events['change_name'] += my_event_listener  # Still access Player class's events

这样的事情可能吗?


我知道我能做到:

class Player2(Player):
    def __init__(self, name):
        super().__init__()
        self.events.update(...)

但不一样:P

【问题讨论】:

    标签: python python-3.x dictionary superclass


    【解决方案1】:

    认为你想要的是:

    class Player:
    
        EVENTS = ('change_name',)
    
        def __init__(self, name):
            self._name = name
            self.events = EventDict(
                self.__class__.__name__, 
                *self.EVENTS,
            )
    
        ...
    

    那么在Player2 中你只需要:

    class Player2(Player):
    
        EVENTS = Player.EVENTS + ('attack', 'kill')
    

    继承的__init__ 可以正常工作。

    【讨论】:

    • 请注意,这将导致 EventDict 中的条目Player.attack,而我认为提问者想要Player2.attack。要解决此问题,您必须删除 EventDict 上的约束,即构造函数创建的每个事件都具有相同的前缀。
    • @SteveJessop 我不认为它会 - 当从 Player2 实例调用 Player.__init__ 时,self.__class__.__name__'Player2'
    • 是的,我弄错了。当我认为提问者想要Player.change_name 时,它将导致一个名为Player2.change_name 的条目。当然我们不知道Event.name 是用来做什么的,这可能根本不重要,我只是想指出,如果它确实比EventDict 需要更改(或者提问者必须求助于在之后添加事件) EventDict 已创建)。
    【解决方案2】:

    停止使用EventDict。 类本身有自己的 dict 支持这样的继承。

    class Player:
        def __init__(self, name):
            self._name = name
            self.change_name_event = Event('Player.change_name')
    
    class Player2(Player):
        def __init__(self, name):
            super().__init__(name)
            self.attack_event = Event('Player2.attack')
            self.kill_event = Event('Player2.kill')
    

    无论如何都会添加来自子类的所有事件。

    我注意到,也许您想表明它们是事件,所以我在字段名称中添加了“事件”,但如果您不想这样做,则不需要。

    如果您希望前缀始终相同,那么您可以将字符串从 'Player.change_name' 更改为 self.__class__.__name__ + '.change_name'。这样,它总是得到对象的 actual 类。这是@jonrsharpe 的解决方案试图达到的一部分。

    如果您想让其他人可以动态添加更多事件,他们可以简单地执行类似playerObj.my_new_event = Event('Player.my_new_event') 的行您可以在Player 类中提供一个不错的方法来制作他们的生活更轻松:

    def add_event(self, event_name):
        setattr(self, event_name, Event(self.__class__.__name__ + '.' + event_name)
    

    【讨论】:

    • 如果我想遍历事件怎么办?如果我希望它们可以通过字符串访问,使用 getattr() 听起来不太有说服力。
    • 假设你有一个很好的循环事件用例,那么我会选择@jonrsharpe 的解决方案(或稍作修改的版本)。一些不错的修改是使用EVENTS = super().EVENTS + ... 而不是EVENTS = Player.EVENTS + ...,因为在某些条件下mro 可能会变得非常混乱。如果你想走这条路,你也可以删除self.__class__.__name__ 的东西。
    • 或者你可以克服自己并使用self.events.update(...) :) 这没有什么可耻的。
    • @jonrsharpe 很有趣...好吧,从技术上讲,您可以使用super() 获取类属性,但我想您需要一个实例来执行此操作...很高兴知道。我以前没有尝试过,所以我只是假设它有效。如果我一直在使用扩展的 super() 调用,我可能会意识到这一点,因为您必须传入一个实例。所以,是的,@MarkusMeskanen,请忽略我这里关于更改为使用 super() 的 cmets。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-03
    相关资源
    最近更新 更多