2017-08-25 9 views
1

Existe-t-il un moyen d'empêcher certains mais pas tous les arguments d'être envoyés à une super-classe?Est-il possible d'être sélectif sur les kwargs à transmettre à une super-classe en python?

J'ai une classe de base qui vérifie l'entrée d'utilisateur:

class Base(object): 
    def __init__(self, **kwargs): 
     self.kwargs = kwargs 
     super(Base, self).__init__() 

    @staticmethod 
    def check_integrity(allowed, given): 
     """ 
     Verify user input. 
     :param allowed: list. Keys allowed in class 
     :param given: list. Keys given by user 
     :return: 
     """ 
     for key in given: 
      if key not in allowed: 
       raise Exception('{} not in {}'.format(key, allowed)) 

>>> base = Base() 
>>> print base.__dict__ 
output[0]: {'kwargs': {}} 

A hérite de Base et utilise la méthode pour vérifier ses mots-clés

class A(Base): 
    def __init__(self, **kwargs): 
     super(A, self).__init__(**kwargs) 
     self.default_properties = {'a': 1, 
            'b': 2} 

     self.check_integrity(self.default_properties.keys(), kwargs.keys()) 

>>> a = A(a=4) 
>>> print a.__dict__ 
output[1]: {'default_properties': {'a': 1, 'b': 2}, 'kwargs': {'a': 4}} 

Je dois mentionner aussi que je suis sorti une autre méthode que j'ai utilisée dans cette classe pour mettre à jour les propriétés de classe, puisque c'est une complication non pertinente à la question (par conséquent pourquoi a n'est pas mis à jour à 4 dans l'exemple ci-dessus)

Le problème vient lorsque vous essayez d'hériter de A et en ajoutant kwargs supplémentaire à la sous-classe:

class B(A): 
    def __init__(self, **kwargs): 
     super(B, self).__init__(**kwargs) 

     self.default_properties = {'a': 2, 
            'c': 3, 
            'd': 4} 

     self.check_integrity(self.default_properties.keys(), kwargs.keys()) 



>>> b = B(d=5) 


Traceback (most recent call last): 
    File "/home/b3053674/Documents/PyCoTools/PyCoTools/Tests/scrap_paper.py", line 112, in <module> 
    b = B(d=5) 
    File "/home/b3053674/Documents/PyCoTools/PyCoTools/Tests/scrap_paper.py", line 96, in __init__ 
    super(B, self).__init__(**kwargs) 
    File "/home/b3053674/Documents/PyCoTools/PyCoTools/Tests/scrap_paper.py", line 92, in __init__ 
    self.check_integrity(self.default_properties.keys(), kwargs.keys()) 
    File "/home/b3053674/Documents/PyCoTools/PyCoTools/Tests/scrap_paper.py", line 84, in check_integrity 
    raise Exception('{} not in {}'.format(key, allowed)) 
Exception: d not in ['a', 'b'] 

Ici d est transmis à la superclasse, même si son seul nécessaire dans la sous-classe. Cependant, les arguments a et b sont utilisés dans A et doivent être passés de B à A.

+1

'del kwargs ['d']'? –

+0

Besoin de passer 'kwargs' pour superclasser ceux qui ne sont pas dans' self.default_properties'. Est-ce votre attente @CiaranWelsh? – Rajez

+0

Je voudrais 'a' et' b' de 'B(). Default_properties()' à passer à 'A' mais pas' d' de 'B(). Default_properties()'. – CiaranWelsh

Répondre

3

Existe-t-il un moyen d'empêcher l'envoi de certains arguments à une superclasse?

Eh bien oui tout simplement: ne les passez pas. Vous êtes censé savoir quels arguments votre classe prend et quels arguments il est superclasse (es) prennent trop, passer si seulement ce que la superclasse attend:

class Base(object): 
    def __init__(self, arg1, arg2): 
     self.arg1 = arg1 
     self.arg2 = arg2 

class Child(object): 
    def __init__(self, arg1, arg2, arg3): 
     super(Child, self).__init__(arg1, arg2) 
     self.arg3 = arg3 

Ce qui précède est simple simple lisible et maintenable et garanteed travailler. Et si vous voulez des valeurs par défaut, il est pas un problème non plus:

class Base(object): 
    def __init__(self, arg1=1, arg2=2): 
     self.arg1 = arg1 
     self.arg2 = arg2 

class Child(object): 
    def __init__(self, arg1=1, arg2=2, arg3=3): 
     super(Child, self).__init__(arg1, arg2) 
     self.arg3 = arg3 

Maintenant, si votre responsabilité de la classe est de valider les entrées utilisateur arbitraires contre une donnée « schéma » (default_properties dans votre extrait), il y a en effet un couple d'erreurs logiques dans votre code - principalement, 1. que vous validez vos entrées dans l'initialiseur et 2. appelez l'initialiseur du parent AVANT de surcharger default_properties de votre objet, ainsi quand l'initialiseur de superclasse est appelé il ne valide pas contre le schéma correct. Aussi vous définissez default_properties comme un attribut d'instance dans votre initialiseur donc si vous changez simplement les instructions d'abord définir default_propertie et alors seulement appeler l'initialiseur du parent celui-ci redéfinira default_properties

Une solution simple serait de simplement faire default_properties un attribut class:

class Base(object): 

    # empty by default 
    default_properties = {} 

    def __init__(self, **kwargs):     
     self.kwargs = kwargs 
     # object.__init__ is a noop so no need for a super call here 
     self.check_integrity()     

    def check_integrity(self): 
     """ 
     Verify user input. 
     """ 
     for key in self.kwargs: 
      if key not in self.default_properties: 
       raise ValueError('{} not allowed in {}'.format(key, self.default_properties)) 

et vous ne devez pas passer outre l'initialiseur du tout:

class A(Base): 
    default_properties = {'a': 1, 
          'b': 2} 


class B(A): 
    default_properties = {'a': 1, 
          'b': 2, 
          'd': 3} 

et vous êtes don e - check_integrity utilisera la classe de l'instance en cours default_properties et vous n'avez pas à vous préoccuper de "choisir les kwargs que vous passez à la superclasse".

Maintenant, c'est encore moyen de simpliste de travailler efficacement comme une validation d'entrée framewoek, surtout si vous voulez l'héritage ... Si B est une sous-classe appropriée de A, il devrait être en mesure d'ajouter à default_properties sans avoir à redéfinir complètement (ce qui est une violation évidente DRY). Et la validation des entrées utilisateur est généralement beaucoup plus impliquée que la simple vérification des noms des arguments ... Vous pouvez étudier comment les autres bibliothèques/frameworks ont résolu le problème (les formes de Django viennent à l'esprit ici).

+0

Salut bruno, j'apprécie vraiment votre réponse détaillée - elle cherche à être très précieuse pour moi (en tant que programmeur python autodidacte). Je viens d'avoir quelques questions. Tout d'abord, quand je fais vos changements suggérés à la, l'appel 'self.check_integrity()' dans la classe 'Base' soulève un' NameError'. Voici un [lien] (http://www.tutorialspoint.com/execute_python_online.php?PID=0Bw_CjBb95KQMbElGSW9IYUdGSHM) pour démontrer l'erreur. Deuxièmement, est-il nécessaire de définir un 'default_properties' vide? Quelle est la valeur de cela? Merci. – CiaranWelsh

+0

@CiaranWelsh il y avait en effet une erreur dans mon extrait de code, c'est corrigé maintenant. Le 'default_properties' vide n'est pas strictement requis (et n'est nullement requis pour être vide) mais sans cela' check_integrity() 'se casserait pour la classe enfant sans' default_properties'. Cela rend également l'intention un peu plus claire. –