2010-09-17 8 views
0

Étant donné un objet A, qui contient un objet appelable B, existe-t-il un moyen de déterminer "A" de l'intérieur B.__call__()?Accéder à l'objet contenant un objet injecté?

Ceci est à utiliser dans l'injection d'essai, où A.B est à l'origine une méthode.

Le code est quelque chose comme ceci:

# Class to be tested. 
class Outer(object): 
    def __init__(self, arg): 
    self.arg = arg 
    def func(self, *args, **kwargs): 
    print ('Outer self: %r' % (self,)) 

# Framework-provided: 
class Injected(object): 
    def __init__(self): 
    self._replay = False 
    self._calls = [] 
    def replay(self): 
    self._replay = True 
    self._calls.reverse() 
    def __call__(self, *args, **kwargs): 
    if not self._replay: 
     expected = (args[0], args[1:], kwargs) 
     self._calls.append(expected) 
    else: 
     expected_s, expected_a, expected_k = self._calls.pop() 
     ok = True 
     # verify args, similar for expected_k == kwargs 
     ok = reduce(lambda x, comp: x and comp[0](comp[1]), 
        zip(expected_a, args), 
        ok) 
     # what to do for expected_s == self? 
     # ok = ok and expected_s(this) ### <= need "this". ### 
     if not ok: 
     raise Exception('Expectations not matched.') 

# Inject: 
setattr(Outer, 'func', Injected()) 
# Set expectations: 
# - One object, initialised with "3", must pass "here" as 2nd arg. 
# - One object, initialised with "4", must pass "whatever" as 1st arg. 
Outer.func(lambda x: x.arg == 3, lambda x: True, lambda x: x=='here') 
Outer.func(lambda x: x.arg == 4, lambda x: x=='whatever', lambda x: True) 
Outer.func.replay() 
# Invoke other code, which ends up doing: 
o = Outer(3) 
p = Outer(5) 
o.func('something', 'here') 
p.func('whatever', 'next') # <- This should fail, 5 != 4 

La question est: Est-il possible (magie noire est très bien) dans Injected.__call__() pour accéder à ce « soi » aurait été en non-écrasement Outer.func() , à utiliser comme "ceci" (ligne marquée "###")?

Naturellement, le code est un peu plus complexe (les appels peuvent être configurés pour être dans un ordre arbitraire, les valeurs de retour peuvent être définies, etc.), mais ceci est l'exemple minimal que je pourrais trouver. problème et la plupart des contraintes.

Je ne peux pas injecter une fonction avec un argument par défaut au lieu de Outer.func - qui interrompt l'enregistrement (si une fonction était injectée, ce serait une méthode non liée et nécessite une instance de "Outer" comme premier argument, plutôt qu'un comparateur/validateur). En théorie, je pouvais simuler complètement "Outer" pour son appelant. Cependant, la mise en place des attentes il y aurait probablement plus de code, et non réutilisable ailleurs - tout cas/projet similaire devrait également réimplémenter "Outer" (ou son équivalent) comme un simulacre.

Répondre

2

Étant donné un objet A, qui contient un objet appelable B, est-il possible de déterminer "A" à l'intérieur de B. appel()?

Pas dans le cas général, à savoir, avec la généralité sans bornes dont vous avez besoin dans ce texte - à moins que B conserve une référence à A en quelque sorte, Python ne plus sûrement pas le garder au nom de B.

Pour votre cas d'utilisation prévue, il est encore pire: il ne serait pas utile, même si B se a fait conserver une référence à A (comme objet de méthode serait à sa classe), puisque vous piétiner partout B sans recours avec setattr, ce qui équivaut à une affectation. Il n'y a plus de trace dans la classe Outer qui, de retour en temps plus heureux, avait un attributavec certaines caractéristiques: cet attribut n'est plus, oblitéré par setattr. Ce n'est pas vraiment de l'injection de dépendance (un modèle de conception nécessitant la coopération de l'objet injecté), c'est le patch de singe, une de mes bêtes noires, et sa nature destructrice, non récupérable (que vous êtes en train de combattre avec) fait partie de pourquoi je pique à ce sujet.

+0

Vous avez raison, ce n'est pas de l'injection de dépendance, c'est du débordement. En tout cas, ça semble être aussi dur que je le craignais. Je devrais remplacer la méthode * et * faudrait remplacer __init__ pour remplacer la méthode à l'instanciation, cette fois avec une méthode de proxy qui passerait l'objet conteneur à 'B' ... faisable, mais pas maintenable , il vaut mieux laisser comme un Gedankenexperiment. – Gabe

Questions connexes