简体   繁体   English

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

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

I am working on a simple simulation where I would like to change the methods of a class instance at runtime. 我正在进行一个简单的仿真,我想在运行时更改类实例的方法。 I am quite new to OOP so I am not sure which approach fits my case best. 我对OOP还是很陌生,因此我不确定哪种方法最适合我的情况。

I have created a couple of samples with the example being a Cat class which can turn into a zombie cat at runtime, changing it's behaviour. 我创建了一些示例,示例是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')

This is the desired behaviour however I would like to separate the Cat and ZombieCat methods and skip the if statement. 这是理想的行为,但是我想将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')

This works well but I am not sure if there are any side effects to changing self.__class__ , it seems to be discouraged How dangerous is setting self.__class__ to something else? 这很好用,但我不确定改变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')

When googling I came across the strategy pattern. 谷歌搜索时,我遇到了策略模式。 This also works but feels a bit messier than creating a child class. 这也可以,但是比创建子类感觉有些混乱。 For example to override an additional method when the cat is a zombie it requires changes in 3 places instead of 1. 例如,当猫是僵尸时要覆盖其他方法,则需要在3个地方而不是1个地方进行更改。

Feel free to suggest other patterns, I'm sure there are things I have not considered yet. 请随意提出其他模式,我确定有些事情我还没有考虑。


Edit: After a helpful answer from @martineau I'd like to add that it would be useful if any references to a Cat instance are updated when .turn_to_zombie is called, ie 编辑:在@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

While something like @Anton Abrosimov's answer using __getattr__() , is probably the canonical way to do it, it does have a negative side-effect of introducing the overhead of an additional function-call to every call to one of the instance's methods. 虽然类似@Anton Abrosimov的使用__getattr__()的答案可能是完成此操作的规范方法,但它确实带来了负面影响,即对实例方法之一的每次调用都引入了额外的函数调用的开销。

Well, like the saying goes, there's more than one way to skin a cat , so here's an alternative approach which avoids that overhead by changing what function associated with the method's name of the given instance. 嗯,就像俗话所说, 给猫皮剥皮多种方法 ,因此这是另一种方法,它通过更改与给定实例的方法名称关联的函数来避免这种开销。 (Technically it could also be used to add methods to an instance that didn't already exist.) (从技术上讲,它也可以用于向不存在的实例添加方法。)

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

Something like that, I think: 我想是这样的:

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

Self merged classes is too complex and not intuitive, I think. 我认为,自合并类太复杂且不直观。

Dark magic attention Do not use it, but it's worked: 黑暗魔力的注意力不要使用它,但它的工作原理是:

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