繁体   English   中英

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

[英]How to change class instance behaviour at runtime in-place?

我正在进行一个简单的仿真,我想在运行时更改类实例的方法。 我对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__是否有任何副作用,似乎不建议这样做。将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提供有用的答案后,我想补充一点,如果在.turn_to_zombie时更新对Cat实例的任何引用(例如,

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

虽然类似@Anton Abrosimov的使用__getattr__()的答案可能是完成此操作的规范方法,但它确实带来了负面影响,即对实例方法之一的每次调用都引入了额外的函数调用的开销。

嗯,就像俗话所说, 给猫皮剥皮多种方法 ,因此这是另一种方法,它通过更改与给定实例的方法名称关联的函数来避免这种开销。 (从技术上讲,它也可以用于向不存在的实例添加方法。)

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')

我想是这样的:

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)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM