2015-11-13 3 views
3

Disons que j'ai les deux classes suivantesMéthode accessible uniquement à partir des descendants de classe en python

class A: 
    def own_method(self): 
     pass 
    def descendant_method(self): 
     pass 

class B(A): 
    pass 

et je veux descendant_method être appelable des instances de B, mais pas de A et own_method pour être appelable de partout .

Je peux penser à plusieurs solutions, tout insatisfaisants:

  1. Vérifiez un champ et augmenter manuellement NotImplementedError:
class A: 
    def __init__(self): 
     self.some_field = None 
    def own_method(self): 
     pass 
    def descendant_method(self): 
     if self.some_field is None: 
      raise NotImplementedError 

class B(A): 
    def __init__(self): 
     super(B, self).__init__() 
     self.some_field = 'B' 
    pass 

Mais cela est en train de modifier le comportement d'exécution de la méthode, que je n » Je veux faire

  1. Utiliser un mix:
class A: 
    def own_method(self): 
     pass 

class AA: 
    def descendant_method(self): 
     pass 

class B(AA, A): 
    pass 

C'est agréable aussi longtemps que descendant_method n'utilise pas beaucoup de A, ou bien nous allons devoir hériter AA(A) et cela défie le point entier

  1. méthode de marque privée dans A et le redéfinir dans une métaclasse:
class A: 
    def own_method(self): 
     pass 
    def __descendant_method(self): 
     pass 

class AMeta(type): 
    def __new__(mcs, name, parents, dct): 
     par = parents[0] 
     desc_method_private_name = '_{}__descendant_method'.format(par.__name__) 
     if desc_method_private_name in par.__dict__: 
      dct['descendant_method'] = par.__dict__[desc_method_private_name] 

     return super(AMeta, mcs).__new__(mcs, name, parents, dct) 

class B(A, metaclass=AMeta): 
    def __init__(self): 
     super(B, self).__init__() 

Cela fonctionne, mais il semble évidemment sale, tout comme l'écriture self.descendant_method = self._A__descendant_method dans B lui-même.

Quelle serait la bonne façon "pythonique" d'obtenir ce comportement? UPD: mettre la méthode directement dans B fonctionnerait, bien sûr, mais je m'attends à ce que A ait de nombreux descendants qui utiliseront cette méthode et ne voudront pas la définir dans chaque sous-classe.

+0

Si moi-même. \ __ class__ == A? Pas isinstance, test d'égalité? Je l'ai fait pour éviter d'enregistrer des classes "abstraites" lorsque seules les classes concrètes peuvent être utilisées. –

+3

C'est une exigence maladroite d'une méthode, qui indique généralement que vous vous approchez de votre problème. S'il vous plaît expalin * pourquoi * vous voulez faire cela, et quel est le problème original que vous essayez de résoudre en utilisant cette conception. Il pourrait y avoir une meilleure approche. – shx2

+0

@JLPeyret, oui, ce serait mieux que mon numéro 1, mais je veux toujours éviter toute vérification de l'exécution –

Répondre

1

Ce qui est si mal à faire AA Hériter de A? Il s'agit essentiellement d'une classe de base abstraite qui ajoute des fonctionnalités supplémentaires qui ne sont pas censées être disponibles dans A. Si vous ne voulez vraiment pas que AA soit instancié, alors la réponse pythonique ne doit pas vous inquiéter, et documenter que l'utilisateur n'est pas censé faire cela. Bien que si vous êtes vraiment insistant vous pouvez définir __new__ pour lancer une erreur si l'utilisateur essaye d'instancier AA.

class A: 
    def f(self): 
     pass 

class AA(A): 
    def g(self): 
     pass 
    def __new__(cls, *args, **kwargs): 
     if cls is AA: 
      raise TypeError("AA is not meant to be instansiated") 
     return super().__new__(cls) 

class B(AA): 
    pass 

Une autre alternative pourrait être de faire un AAAbstract Base Class. Pour que cela fonctionne, vous aurez besoin de définir au moins une méthode comme étant abstraite - __init__ pourrait le faire s'il n'y a pas d'autres méthodes que vous voulez dire sont abstraites.

from abc import ABCMeta, abstractmethod 

class A: 
    def __init__(self, val): 
     self.val = val 
    def f(self): 
     pass 

class AA(A, metaclass=ABCMeta): 
    @abstractmethod 
    def __init__(self, val): 
     super().__init__(val) 
    def g(self): 
     pass 

class B(AA): 
    def __init__(self, val): 
     super().__init__(val) 

Très enfin, ce qui est si mal d'avoir la méthode descendante disponible sur A, mais tout simplement pas l'utiliser. Vous écrivez le code pour A, donc n'utilisez simplement pas la méthode ... Vous pouvez même documenter la méthode qui n'est pas destinée à être utilisée directement par A, mais qui est plutôt destinée à être disponible pour les classes enfants. De cette façon, les futurs développeurs connaîtront vos intentions.

+0

D'accord avec ne pas utiliser de A. Mais, selon ce que fait la méthode, vous pouvez également implémenter un comportement de ne rien faire lors de l'appel de A - retourner wo change si la méthode modifie des choses, retourner une liste vide si elle est destinée à renvoyer des membres, etc ... En fin de compte, alors que l'exigence non-de-A peut avoir sa raison, insistant sur le fait que descendant_m lui-même reste béatement que dans sa mise en œuvre peut être irréaliste. –

2

Pour autant que je peux dire, cela peut être la façon la plus Pythonic d'accomplir ce que vous voulez:

class A: 
    def own_method(self): 
     pass 
    def descendant_method(self): 
     raise NotImplementedError 

class B(A): 
    def descendant_method(self): 
     ... 

Une autre option pourrait être la suivante:

class A: 
    def own_method(self): 
     pass 
    def _descendant_method(self): 
     pass 

class B(A): 
    def descendant_method(self): 
     return self._descendant_method(self) 

Ils sont tous les deux Pythonic parce que c'est explicite, lisible, clair et concis. Il est explicite car il ne fait pas de magie inutile. Il est explicite.

  • Il est lisible parce que on peut dire exactement ce que vous faites, et ce que votre intention était à première vue. Il est clair que le trait de soulignement simple principal est une convention largement utilisée dans la communauté Python pour les méthodes privées (non magiques) -tout développeur qui l'utilise doit savoir marcher avec prudence.Le choix entre l'une de ces approches dépend de la façon dont vous envisagez votre cas d'utilisation. Un exemple plus concret dans votre question serait utile.

  • +0

    Merci, ce sont des points valides. J'ajouterais à cela un "descendant_method" abstrait dans A pour indiquer qu'il n'appartient pas à cet objet, comme dans la mise à jour des questions. –

    +0

    Une objection peut être que la classe 'B' contient maintenant deux méthodes qui font la même chose –

    0

    Essayez de vérifier le nom de la classe en utilisant __class__.__name__.

    class A(object): 
    
        def descendant_method(self): 
         if self.__class__.__name__ == A.__name__: 
          raise NotImplementedError 
         print 'From descendant' 
    
    
    class B(A): 
        pass 
    
    
    b = B() 
    b.descendant_method() 
    a = A() 
    a.descendant_method()