Je cherche un moyen d'avoir une collection d'objets homogènes, de les envelopper dans un autre objet, mais de faire en sorte que l'objet wrapper ait la même API que l'original et de transmettre l'appel d'API correspondant à ses membres d'objet.

class OriginalApi:
  def __init__(self):
    self.a = 1
    self.b = "bee"

  def do_something(self, new_a, new_b, put_them_together=None):
    self.a = new_a or self.a
    self.b = new_b or self.b

    if put_them_together is not None:
      self.b = "{}{}".format(self.a, self.b)

  # etc.

class WrappedApi:
  def __init__(self):
    self.example_1 = OriginalApi()
    self.example_2 = OriginalApi()

Quelques pistes de solutions envisagées mais insuffisantes :

  • Réécrire toute l'API Pourquoi pas ? Pas adéquat car l'API est assez grande et en expansion. Devoir maintenir l'API à plusieurs endroits n'est pas réaliste.

    Exemple de code:

    class WrappedApi:
      def __init__(self):
        self.example_1 = OriginalApi()
        self.example_2 = OriginalApi()
    
      def do_something(self, new_a, new_b, put_them_together=None):
        self.example_1.do_something(new_a, new_b, put_them_together)
        self.example_2.do_something(new_a, new_b, put_them_together)
    
  • Utiliser une liste et une boucle for Cela modifie l'API sur l'objet. Cela dit, c'est la solution de sauvegarde au cas où je ne trouverais pas quelque chose de plus élégant. Dans ce cas, la classe WrappedApi n'existerait pas.

    Exemple de code:

    wrapped_apis = [OriginalApi(), OriginalApi()]
    for wrapped_api in wrapped_apis:
      wrapped_api.do_something(1, 2, True)
    
  • j'ai essayé d'utiliser Python Object Wrapper, mais je ne voyais pas comment l'appeler plusieurs sous-objets avec les mêmes arguments.

Et pour quiconque est curieux de connaître le cas d'utilisation, il s'agit en fait d'une collection de plusieurs objets matplotlib axes. Je ne veux pas réimplémenter toute l'API axes (c'est gros), et je ne veux pas changer tout le code qui fait des appels sur les axes (comme plot, step, etc.)

10
TinyTheBrontosaurus 17 mars 2019 à 22:46

2 réponses

Meilleure réponse

Si vous n'implémentez que des méthodes, un __getattr__ générique peut faire l'affaire

class Wrapper: 
    def __init__(self, x): 
        self.x = x 
    def __getattr__(self, name): 
        def f(*args, **kwargs): 
            for y in self.x: 
                getattr(y, name)(*args, **kwargs) 
        return f

Par exemple, avec x = Wrapper([[], [], []]) après avoir appelé x.append(12), les trois objets de la liste auront 12 comme dernier élément.

Notez que la valeur de retour sera toujours None... une option pourrait être de collecter les valeurs de retour et de les renvoyer sous forme de liste, mais cela "casserait bien sûr l'API".

7
6502 17 mars 2019 à 20:10

Je pense que tu as la bonne idée là

wrapped_apis = [OriginalApi(), OriginalApi()]
for wrapped_api in wrapped_apis:
    wrapped_api.do_something(1, 2, True)

Vous pouvez définir votre classe wrapper en héritant de list, puis gérer les appels d'API à ses éléments une fois qu'elle est créée.

class WrapperClass(list):
    def __init__(self, api_type):
        self.api_type = api_type

        for func in dir(api_type):
            if callable(getattr(api_type, func)) and not func.startswith("__"):
                setattr(self, func, lambda *args, **kwargs: 
                    [getattr(o, func)(*args, **kwargs) for o in self])

w = WrapperClass(OriginalApi)
o1, o2 = [OriginalApi()]*2
w.append(o1)
w.append(o2)
print(w.do_something(1, 2, True))
# [None, None]
print(w[0].b)
# 12
print(w[1].b)
# 12
print(o1.b)
# 12

Ici, je réitère chaque méthode de votre classe API et crée une méthode dans la classe wrapper qui applique ses arguments à tous ses éléments de liste. Il renvoie ensuite une liste de compréhension constituée des résultats.

Inutile de dire que vous devriez probablement valider le type d'un nouvel objet ajouté à ce WrapperClass comme ceci,

def append(self, item):
    if not isinstance(item, self.api_type):
        raise TypeError('Wrong API type. Expected %s'.format(self.api_type))
    super(WrapperClass, self).append(item)
1
darksky 17 mars 2019 à 21:14