如何在运行时就地更改类实例的行为?

时间:2019-07-07 20:54:51

标签: python oop

我正在做一个简单的仿真,我想在运行时更改类实例的方法。我对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')

这是理想的行为,但是我想将CatZombieCat方法分开,并跳过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__是否有任何副作用,似乎不建议使用How dangerous is setting self.__class__ to something else?

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

2 个答案:

答案 0 :(得分:0)

虽然类似@Anton Abrosimov使用__getattr__()的答案可能是做到这一点的规范方法,但它确实带来了负面影响,即在每次调用其中一个函数时都会引入额外的函数调用的开销。实例的方法。

好吧,就像俗话所说的那样,这里有more than one way to skin a 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')

答案 1 :(得分:0)

我想是这样的

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__(foo)
        self.bar = bar

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

class SchroedingerCat:
    _cat = Cat
    _zombie = ZombieCat
    _schroedinger = None

    def __init__(self, foo, bar=None):
        if bar is not None:
            self._schroedinger = self._zombie(foo, bar)
        else:
            self._schroedinger = self._cat(foo)

    def turn_to_zombie(self, bar):
        self._schroedinger = self._zombie(self._schroedinger.foo, bar)
        return self

    def __getattr__(self, name):
        return getattr(self._schroedinger, name)

SchroedingerCat('aaa').do_things()
SchroedingerCat('aaa').turn_to_zombie('bbb').do_things()

我认为自合并类太复杂且不直观。

暗黑魔法注意力不要使用它,但是它可以起作用:

from functools import partial

class DarkMagicCat:
    _cat = Cat
    _zombie = ZombieCat
    _schroedinger = None

    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar
        self._schroedinger = self._cat

    def turn_to_zombie(self, bar):
        self._schroedinger = self._zombie
        return self

    def __getattr__(self, name):
        return partial(getattr(self._schroedinger, name), self=self)