python - 如何在运行时就地更改类实例行为?
问题描述
我正在做一个简单的模拟,我想在运行时更改类实例的方法。我对 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')
这是所需的行为,但是我想将Cat
andZombieCat
方法分开并跳过该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
解决方案
虽然像@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')
推荐阅读
- r - 如何从外部列表正确循环以在 dplyr 中使用内部 mutate
- kubernetes - 为什么这个 Pod 不针对这个 PSP 进行验证?
- bash - rsync 与 iCloud 文件夹
- python - 为什么 pandas `loc` 会抛出带有列名的 `KeyError`?
- python - 使用 rpy2 在 Python 中使用 R 包
- excel - 如果规则无法正常工作,则条件格式计数
- javascript - Vuex 不对复杂对象做出反应
- php - 在 composer.json 中更新 symfony/symfony 版本
- angular - Angular 8 FormArray 的 .Net Core API ModelState 错误
- angular - 更改控件属性后,垫子微调器不会隐藏