【问题标题】:Wrap an arbitrary class with a new method dynamically用新方法动态包装任意类
【发布时间】:2021-04-26 05:04:35
【问题描述】:

我有一个 A 类。

我有另一个 B 类。B 类的实例应该与 A 类完全一样,除了一个警告:我想要另一个可用的函数,称为 special_method(self, args, kwargs)

所以以下应该可以工作:

instance_A = classA(args, kwargs)
instance_B = classB(instance_A)
method_result = instance_B.special_method(args, kwargs)

我如何编写 B 类来实现这一点?

注意:如果我只想为一个 A 类执行此操作,我可以让 B 类继承 A 类。但我希望能够将 special_method 添加到 C、D、E、F 类...等等

【问题讨论】:

  • 您是否需要使用特殊方法,例如__add____len__ 等“dunder”方法?
  • 也许你想要一个mixin 类,它只包含.special_method()。您可以轻松定义添加此的其他类的扩展版本:例如class A_extended(A, B): pass
  • @juanpa.arrivillaga 我希望 instance_B 的功能与 instance_A 完全相同,只是添加了一个新功能。所有方法都应该正常工作。
  • 你也可以试试这个问题的方法stackoverflow.com/a/21060094/10798048

标签: python


【解决方案1】:

所以,您正在描述一个代理对象。对非特殊方法执行此操作在 Python 中是微不足道的,您可以使用 __getattr__

In [1]: class A:
   ...:     def foo(self):
   ...:         return "A"
   ...:

In [2]: class B:
   ...:     def __init__(self, instance):
   ...:         self._instance = instance
   ...:     def special_method(self, *args, **kwargs):
   ...:         # do something special
   ...:         return 42
   ...:     def __getattr__(self, name):
   ...:         return getattr(self._instance, name)
   ...:

In [3]: a = A()

In [4]: b = B(a)

In [5]: b.foo()
Out[5]: 'A'

In [6]: b.special_method()
Out[6]: 42

但是,这里有一个警告:这不适用于特殊方法,因为特殊方法会跳过这部分属性解析并直接在类 __dict__ 上查找。

另一种方法是,您可以简单地将方法添加到您需要的所有类中。比如:

def special_method(self, *args, **kwargs):
    # do something special
    return 42

for klass in [A, C, D, E, F]:
    klass.special_method = special_method

当然,这会影响这些类的所有实例(因为您只是在为类动态添加方法)。

如果你真的需要特殊的方法,最好的办法是创建一个子类,但你可以通过一个简单的辅助函数动态地做到这一点,例如:

def special_method(self, *args, **kwargs):
    # do something special
    return 42

_SPECIAL_MEMO = {}

def dynamic_mixin(klass, *init_args, **init_kwargs):
    if klass not in _SPECIAL_MEMO:
        child = type(f"{klass.__name__}Special", (klass,), {"special_method":special_method})
        _SPECIAL_MEMO[klass] = child
    return _SPECIAL_MEMO[klass](*init_args, **init_kwargs)

class Foo:
    def __init__(self, foo):
        self.foo = foo
    def __len__(self):
        return 88
    def bar(self):
        return self.foo*2

special_foo = dynamic_mixin(Foo, 10)

print("calling len", len(special_foo))
print("calling bar", special_foo.bar())
print("calling special method", special_foo.special_method())

上面的脚本打印:

calling len 88
calling bar 20
calling special method 42

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-05
    • 2013-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多