首页 > 解决方案 > 如何在运行时就地更改类实例行为?

问题描述

我正在做一个简单的模拟,我想在运行时更改类实例的方法。我对 OOP 很陌生,所以我不确定哪种方法最适合我的情况。

我创建了几个示例,示例是一个Cat可以在运行时变成僵尸猫的类,从而改变它的行为。

class Cat:
    def __init__(self, foo):
        self.foo = foo
        self.is_zombie = False

    def turn_to_zombie(self, bar):
        self.is_zombie = True
        self.zombie_bar = bar

    def do_things(self):
        if self.is_zombie:
            print('Do zombie cat things')
        else:
            print('Do cat things')

这是所需的行为,但是我想将CatandZombieCat方法分开并跳过该if语句。

class Cat:
    def __init__(self, foo):
        self. foo = foo

    def do_things(self):
        print('Do cat things')

    def turn_to_zombie(self, bar):
        self.bar = bar
        self.__class__ = ZombieCat

class ZombieCat(Cat):
    def __init__(self, foo, bar):
        super().__init__(self, foo)
        self.bar = bar

    def do_things(self):
        print('Do zombie cat things')

这很好用,但我不确定更改是否有任何副作用self.__class__,似乎不鼓励将 self.__class__ 设置为其他东西有多危险?

class Cat:
    def __init__(self, foo):
        self.foo = foo
        self.strategy = CatStrategy

    def do_things(self):
        self.strategy.do_things(self)

    def turn_to_zombie(self, bar):
        self.bar = bar
        self.strategy = ZombieCatStrategy

class CatStrategy:
    @staticmethod
    def do_things(inst):
        print('Do cat things')

class ZombieCatStrategy(CatStrategy):
    @staticmethod
    def do_things(inst):
        print('Do zombie cat things')

谷歌搜索时,我遇到了策略模式。这也有效,但感觉比创建子类有点混乱。例如,当猫是僵尸时,要覆盖一个附加方法,它需要在 3 个位置而不是 1 个位置进行更改。

随意建议其他模式,我敢肯定有些事情我还没有考虑过。


编辑:在@martineau 提供有用的回答后,我想补充一点,如果在调用Cat时更新对实例的任何引用,这将很有用.turn_to_zombie,即

cats_list1 = [cat1, cat2]
cats_list2 = [cat1, cat2]
cats_list1[0].do_things() # -> Do cat things
cats_list1[0].turn_to_zombie('bar')
cats_list2[0].do_things() # -> Do zombie cat things

标签: pythonoop

解决方案


虽然像@Anton Abrosimov 的回答 using __getattr__(), 可能是规范的方法,但它确实有一个负面的副作用,即在每次调用实例方法之一时引入额外的函数调用的开销。

好吧,就像俗话说的那样,给 cat 剥皮的方法不止一种,所以这里有一种替代方法,它通过更改与给定实例的方法名称相关联的函数来避免这种开销。(从技术上讲,它还可以用于向不存在的实例添加方法。)

import types


class Cat:
    def __init__(self, foo):
        self.foo = foo

    def do_things(self):
        print('Doing Cat things')

    def _change_method(self, method_name, method, **kwattrs):
        bound_method = types.MethodType(method, self)
        setattr(self, method_name, bound_method)
        self.__dict__.update(kwattrs)


class ZombieCat(Cat):
    def __init__(self, foo, bar):
        super().__init__(foo)
        self.bar = bar

    @classmethod
    def turn_into_zombie(cls, cat, bar):
        cat._change_method('do_things', cls.do_things, bar=bar)

    def do_things(self):
        print(f'Doing ZombieCat things (bar={bar!r})')


if __name__ == '__main__':

    foo, bar = 'foo bar'.split()

    cat = Cat(foo)
    cat.do_things()  # -> Doing Cat things
    ZombieCat.turn_into_zombie(cat, bar)
    cat.do_things()  # -> Doing ZombieCat things (bar='bar')

推荐阅读