web-dev-qa-db-fra.com

Constructeur privé dans Python

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

58
user1057793

Comment créer un constructeur privé?

En substance, c'est impossible à la fois parce que python n'utilise pas les constructeurs comme vous le pensez si vous venez de autres OOP langues et parce que python ne fait pas appliquer confidentialité, il a juste une syntaxe spécifique pour suggérer qu'une méthode donnée/property devrait être considéré comme privé. Permettez-moi d'élaborer ...

Premièrement: le plus proche d'un constructeur que vous pouvez trouver dans python est la __new__ Méthode mais c'est très très rarement utilisé (vous utilisez normalement __init__, Qui modifie l'objet qui vient d'être créé (en fait il a déjà self comme premier paramètre).

Quoi qu'il en soit, python est basé sur l'hypothèse que tout le monde est un adulte consentant, donc privé/public n'est pas appliqué comme le fait une autre langue.

Comme mentionné par un autre intervenant, les méthodes censées être "privées" sont normalement ajoutées par un ou deux traits de soulignement: _private Ou __private. La différence entre les deux est que cette dernière va brouiller le nom de la méthode, vous ne pourrez donc pas l'appeler depuis l'extérieur de l'instanciation de l'objet, alors que la première ne le fera pas.

Par exemple, si votre classe A définit à la fois _private(self) et __private(self):

>>> a = A()
>>> a._private()   # will work
>>> a.__private()  # will raise an exception

Normalement, vous voulez utiliser le trait de soulignement simple, car - en particulier pour les tests unitaires - avoir des doubles tirets bas peut rendre les choses très difficiles ...

HTH!

28
mac

Le _ et __ les préfixes n'offrent pas de solution pour restreindre l'instanciation d'un objet à une 'fabrique' spécifique, cependant Python est une puissante boîte à outils et le comportement souhaité peut être obtenu de plusieurs manières ( comme l'a démontré @Jesse W en Z). Voici une solution possible qui garde la classe publiquement visible (permettant isinstance etc.) mais assure que la construction n'est possible que par les méthodes de classe:

class OnlyCreatable(object):

    __create_key = object()

    @classmethod
    def create(cls, value):
        return OnlyCreatable(cls.__create_key, value)

    def __init__(self, create_key, value):
        assert(create_key == OnlyCreatable.__create_key), \
            "OnlyCreatable objects must be created using OnlyCreatable.create"
        self.value = value

Construire un objet avec la méthode de classe create:

>>> OnlyCreatable.create("I'm a test") 
<__main__.OnlyCreatable object at 0x1023a6f60>

Lors de la tentative de construction d'un objet sans utiliser la méthode de classe create, la création échoue en raison de l'assertion:

>>> OnlyCreatable(0, "I'm a test")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in __init__
AssertionError: OnlyCreatable objects can only be created using OnlyCreatable.create

Si la tentative de création d'un objet en mimant la méthode de classe create échoue en raison de la manipulation du compilateur de OnlyCreatable.__createKey.

>>> OnlyCreatable(OnlyCreatable.__createKey, "I'm a test")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'OnlyCreatable' has no attribute '__createKey'

La seule façon de construire OnlyCreatable en dehors d'une méthode de classe est de connaître la valeur de OnlyCreatable.__create_key. Étant donné que la valeur de cet attribut de classe est générée au moment de l'exécution et que son nom est préfixé avec __ le marquant comme inaccessible, il est effectivement "impossible" d'obtenir cette valeur et/ou de construire l'objet.

17
Adam

Comme personne ne l'a encore mentionné - vous pouvez avoir un contrôle considérable sur ce que noms sont visibles dans quels domaines - et il y a beaucoup des portées disponibles. Voici deux  trois autres façons de limiter la construction d'une classe à une méthode d'usine:

#Define the class within the factory method
def factory():
  class Foo:
    pass
  return Foo()

OR

#Assign the class as an attribute of the factory method
def factory():
  return factory.Foo()
class Foo:
  pass
factory.Foo = Foo
del Foo

(Remarque: cela permet toujours à la classe d'être référencée de l'extérieur (pour les vérifications isinstance, par exemple), mais il est assez évident que vous n'êtes pas censé l'instancier directement.)

OR

#Assign the class to a local variable of an outer function
class Foo:
  pass
def factory_maker():
  inner_Foo=Foo
  def factory():
    return inner_Foo()
  return factory
factory = factory_maker()
del Foo
del factory_maker

Cela rend impossible (au moins, sans utiliser au moins une propriété magique (double soulignement)) pour accéder à la classe Foo, mais permet toujours à plusieurs fonctions de l'utiliser (en les définissant avant de supprimer le nom global de Foo.

9
Jesse W at Z

Citant le Guide de style Python (PEP 8) :

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

  • _single_leading_underscore: Faible indicateur "usage interne". Par exemple. "from M import *" N'importe pas les objets dont le nom commence par un trait de soulignement.

  • single_trailing_underscore_: Utilisé par convention pour éviter les conflits avec Python, par exemple Tkinter.Toplevel(master, class_='ClassName')

  • __double_leading_underscore: Lorsque vous nommez un attribut de classe, invoque le changement de nom (à l'intérieur de la classe FooBar, __boo Devient _FooBar__boo; Voir ci-dessous).

  • __double_leading_and_trailing_underscore__: Objets ou attributs "magiques" qui vivent dans des espaces de noms contrôlés par l'utilisateur. Par exemple. __init__, __import__ Ou __file__. N'inventez jamais de tels noms; utilisez-les uniquement comme indiqué.

7
gimel

Tout d'abord, le terme "constructeur" ne s'applique pas à Python, car, bien que la méthode __init__() joue le rôle d'un, c'est juste 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 dans Python est public. Généralement, les programmeurs marquent les méthodes "privées" avec _ ou __ au nom d'une méthode, par exemple:

# inheriting from object is relevant for Python 2.x only
class MyClass(object): 
    # kinda "constructor"
    def __init__(self):
        pass

    # here is a "private" method
    def _some_method(self):
        pass

    # ... and a public one
    def another_method(self):
        pass
3
Zaur Nasibov
class MongoConn(object):
    @classmethod
    def instance(cls):
        if not hasattr(cls, '_instance'):
            cls._instance = cls()
        return cls._instance

    def __init__(self):
        assert not hasattr(self.__class__, '_instance'), 'Do not call constructor directly!'

Si vous voulez une seule instance.

1
BaiJiFeiLong