92 votes

Constructeur privé en Python

Comment créer un constructeur privé qui ne doit être appelé que par la fonction statique de la classe et non par un autre endroit ?

9 votes

Il n'y a pas de notion de méthodes privées vs publiques en Python.

10 votes

8 votes

@Tadeck, s'il vous plaît arrêtez de corriger à tort les gens. Dénaturer un nom ne le rend pas privé.

4voto

gimel Points 30150

Citer le guide de style Python (PEP 8):

De plus, les formes spéciales suivantes utilisant des tirets bas en début ou en fin sont reconnues (celles-ci peuvent généralement être combinées avec n'importe quelle convention de cas):

  • tiret_bas_simple_début: indicateur de faible utilisation "interne". Par exemple, "from M import *" n'importe pas les objets dont le nom commence par un tiret bas.

  • tiret_bas_simple_fin: utilisé par convention pour éviter les conflits avec les mots-clés Python, par exemple Tkinter.Toplevel(master, class_='NomDeClasse')

  • tiret_bas_double_début: lors du nommage d'un attribut de classe, invoque le "name mangling" (à l'intérieur de la classe FooBar, __boo devient _FooBar__boo; voir ci-dessous).

  • tiret_bas_double_début_et_fin: objets ou attributs "magiques" qui vivent dans des espaces de noms contrôlés par l'utilisateur. Par exemple, __init__, __import__ ou __file__. Ne jamais inventer de tels noms; utilisez-les uniquement tels que documentés.

2voto

BasicWolf Points 8119

Tout d'abord, le terme "constructeur" ne s'applique pas à Python, car, bien que la méthode __init__() joue un rôle similaire, c'est simplement une méthode qui est appelée lorsqu'un objet a déjà été créé et nécessite une initialisation.

Chaque méthode d'une classe en Python est publique. En général, les programmeurs marquent les méthodes "privées" avec _ ou __ dans le nom d'une méthode, par exemple :

# hériter de l'objet est pertinent uniquement pour Python 2.x
class MaClasse(object): 
    # un peu comme un "constructeur"
    def __init__(self):
        pass

    # voici une méthode "privée"
    def _une_methode(self):
        pass

    # ... et une méthode publique
    def une_autre_methode(self):
        pass

2voto

Joel Berkeley Points 1532

Si une fonction au niveau du module est OK au lieu d'une méthode statique, consultez ma autre réponse

Vous pouvez obtenir un effet similaire avec une classe abstraite. Toute instance d'attributs qui doivent être définis dans le "constructeur privé" peuvent être des propriétés abstraites. Ensuite, la méthode de classe de votre classe d'usine construit sa propre classe concrète en peuplant ces attributs abstraits, ainsi qu'en effectuant tout autre travail d'initialisation tel que la validation des données.

from abc import ABC, abstractmethod

class Foo(ABC):
    @property
    @abstractmethod
    def _a(self) -> int:
        pass

    def bar(self) -> int:
        return self._a + 1

    @classmethod
    def from_values(cls, a: int) -> 'Foo':
        class _Foo(cls):
            def __init__(self, __a):
                self.__a = __a

            @property
            def _a(self):
                return self.__a

        return _Foo(a)

Foo()  # TypeError: Cannot instantiate the class ...
Foo.from_values(1).bar()  # 1

Si vous constatez que vous n'avez pas besoin d'attributs abstraits sur Foo, vous n'obtiendrez pas d'erreur TypeError lors de l'appel de Foo(). Dans ce cas, vous pouvez soit vous fier à l'héritage de ABC comme documentation, soit définir un attribut fictif pour plus de sécurité.

Modifications facultatives

  • Besoin d'attributs d'instance mutables ? Ajoutez des setters.
  • Vous vous fichez de la différence entre les attributs de classe et d'instance ? Simplifiez avec

    class _Foo(cls):
        _a = a
    
    return _Foo()

2voto

Random Person 323 Points 372

Si vous souhaitez simplement indiquer de manière simple qu'un constructeur ne doit pas être utilisé directement, vous pouvez suivre le modèle suivant pour faire en sorte que l'utilisation par défaut du constructeur génère une exception :

class Foo:
    def __init__(self, arg, __private=False):
        assert __private, "Le constructeur ne doit pas être appelé directement"
        self.arg = arg

    @classmethod
    def from_bar(cls, bar: Bar):
        return cls(bar.arg, __private=True)

    @classmethod
    def from_baz(cls, baz: Baz):
         return cls(baz.arg, __private=True)

Bien sûr, n'importe qui peut toujours faire Foo('abc', __private=True) mais cela enverrait un signal clair que ce comportement n'est pas pris en charge, ce qui suit la conception générale de Python.

1voto

Daniel Schaffer Points 14707

D'autres réponses ont mentionné qu'il n'existe pas de "constructeurs" privés (ou modificateurs d'accès en général) en Python. Bien que cela soit vrai, vous pouvez toujours profiter du comportement de préfixage __ pour accomplir le même effet en "masquant" l'implémentation réelle dans une classe imbriquée avec un double préfixe de tiret bas :

class MyPublicClassWithPrivateConstructor:

    class __Implementation:
        def __init__(self, *args, **kwargs):
            # faire les choses
            self.args = args
            self.kwargs = kwargs

    @classmethod
    def make_my_thing(cls, *args, **kwargs)
        return cls.__Implementation(*args, **kwargs)

    def __new__(cls, *args, **kwargs):
        raise Exception("MyPublicClassWithPrivateConstructor ne peut pas être initialisé directement, utilisez MyPublicClassWithPrivateConstructor.make_my_thing")

# ailleurs
from whateva import MyPublicClassWithPrivateConstructor

MyPublicClassWithPrivateConstructor.__Implementation  # objectif de type 'MyPublicClassWithPrivateConstructor' n'a pas l'attribut '__Implementation'
MyPublicClassWithPrivateConstructor.make_my_thing  # >

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X