Monkey parcheando varios miembros de la clase en un bucle en python

Quiero parchear varios métodos de una clase a la vez en un bucle, para modificar cómo funciona una clase para las pruebas en todos los módulos.

Creo que object.assigndesde javascript haría algo similar.

Esto funciona:

class A:
  def foo1(self):
    print("a")
  def foo2(self):
    print("a")

class B:
  def foo1(self):
    print("b")
  def foo2(self):
    print("b")

A.foo1 = B.foo1
A.foo2 = B.foo2
A().foo1()  # prints "b"
A().foo2()  # prints "b"

Lo que me gustaría hacer esto en un bucle para evitar tener que especificar cada miembro individualmente.

Answer

El siguiente programa filtrará los atributos y métodos de clase y excluirá el reemplazo de métodos privados y todos los atributos.

import collections
from types import FunctionType

class A:
  def foo1(self):
    print("a")
  def foo2(self):
    print("a")

class B:
  a = 1
  def foo1(self):
    print("b")
  def foo2(self):
    print("b")


def patch_class(original, replacement):
    for key, val in replacement.__dict__.items():
        if key.startswith("_") or not isinstance(val, FunctionType):
            continue
        setattr(original, key, val)


patch_class(A, B)
A().foo1()  # prints "b"
A().foo2()  # prints "b"

varsdevuelve todos los métodos setattry getattrpuede escribirlos y leerlos en función de su nombre de cadena.

Ciertos miembros como __dict__no se pueden reemplazar, por lo que está envuelto en try/except.

def patch_class(original, replacement):
    """Replaces all methods in original with the ones in replacement."""
    for member in vars(replacement).keys():
        try:
            target = getattr(replacement, member)
            setattr(original, member, target)
            # or:
            # patch.object(original, member, target).__enter__()
            # type.__setattr__(original, member, target)
            print("monkey-patched", member)
        except Exception as e:
            print(f"could not monkey-patch {member}: {e.__class__.__name__} {e}")

class A:
  def foo1(self):
    print("a")
  def foo2(self):
    print("a")

class B:
  def foo1(self):
    print("b")
  def foo2(self):
    print("b")

patch_class(A, B)

A().foo1()  # prints "b"
A().foo2()  # prints "b"