簡體   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