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 ?
Réponses
Trop de publicités?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 exempleTkinter.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.
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
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()
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.
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 # >
9 votes
Il n'y a pas de notion de méthodes privées vs publiques en Python.
10 votes
Python n'est pas Java.
8 votes
@Tadeck, s'il vous plaît arrêtez de corriger à tort les gens. Dénaturer un nom ne le rend pas privé.
10 votes
@DanielRoseman: larsmans a dit "il n'y a pas de notion de méthodes privées vs publiques en Python". Je dis qu'il a tort. Documentation dit: "Puisqu'il y a un cas d'utilisation valide pour les membres privés de la classe (...), il y a un support limité pour un tel mécanisme, appelé embrouillage de noms.". Veuillez arrêter de débattre sans prouver que vous avez raison.
2 votes
@Tadeck, répétant ma réponse : une méthode marquée avec
__
peut toujours être appelée commeobj._ClassName__Method()
. Maintenant, pourriez-vous expliquer comment cela est privé ?4 votes
@BasicWolf: Veuillez lire pleinement mon commentaire en premier. Le mélange de noms vous donne une notion (partielle) des classes privées. Veuillez ne pas dire que l'appel de
obj._ClassName__Method()
en dehors deClassName
est un comportement correct.