web-dev-qa-db-fra.com

Variables vraiment privées en Python 3

Je connais donc le moyen de créer une variable "privée" en python comme ceci:

class Foo:
    def __init__(self):
        self.__private = 'bar'

Ceci "fonctionne" et ne fonctionne pas, comme indiqué ci-dessous:

foo = Foo()
'__private' in vars(foo) #False
'_Foo__private' in vars(foo) #True

Maintenant, je comprends que c'est le moyen de créer des variables privées en python et j'aime de cette façon. Il vous permet de modifier les noms de manière à ce qu'aucune sous-classe ne l'ignore accidentellement (car elle commence par le nom de la classe) et que personne ne l'utilisera accidentellement. Cela vous donne également le pouvoir de changer les variables privées si vous savez ce que vous faites . En outre, c'est la meilleure façon de le faire, car les variables réellement privées sont impossibles.

Ou alors j'ai pensé.

Récemment, je lisais PEP 8 et j’ai vu cette ligne:

Nous n'utilisons pas le terme "privé" ici, car aucun attribut n'est vraiment privé en Python (sans un travail généralement inutile).

Cette citation se trouve dans la section Conception pour l'héritage du PEP 8 .

Notez la phrase "sans travail généralement inutile". Je suis maintenant sûr qu'il y a must un moyen d'obtenir des variables vraiment privées en python. Comment je ferais ça?

J'ai essayé de surcharger __getattribute__, mais le problème est qu'il n'y a aucun moyen de savoir si l'appel provient de l'intérieur de la classe ou non (à ma connaissance).

De plus, l'attribut __dict__ est pénible lorsque vous essayez de le faire car il contient des références à toutes les variables d'instance.

J'ai aussi pensé aux métaclasses, mais celles-ci semblent avoir les mêmes problèmes que __getattribute__.

Pensées?


Remarque: Je comprends que toute méthode permettant de créer des variables réellement privées en python devrait être (jamais} _ en code productif. Je veux juste savoir comment cela se fait {pourrait}.

8
Ethan K888

J'ai essayé de remplacer getattribute , mais le problème est qu’il n’ya aucun moyen de savoir si l’appel vient de la classe ou non (à ma connaissance).

Vous pouvez utiliser le module inspect pour rechercher le nom et le module de la fonction appelante, que vous pouvez comparer à une liste blanche.

Mais inspect a aussi getattr_static, ce qui peut éviter tout __getattribute__.


Rien n'est vraiment privé en Python. Il existe des moyens de rendre l'accès difficile, mais il existe toujours des moyens de contourner ces moyens. La seule solution est alors en dehors de l'interpréteur Python actuel. Vous pouvez utiliser une interface de fonction étrangère avec un autre langage, voire avec un autre interpréteur Python exécuté dans un sous-processus. La variable privée et toutes les fonctions autorisées à y accéder vivront en dehors de l'interpréteur actuel. Ensuite, il n'y a aucun moyen de l'inspecter.

4
gilch

La raison pour laquelle Python n'a pas d'attribut privé est que nous ne pouvons pas dire si c'est à l'intérieur ou à l'extérieur d'une classe. Ils partagent le même processus d'accès aux attributs. self.private est exactement le obj.private. Donc, si nous empêchons de obj.private, self.private est également empêché. La seule façon de les différencier est de donner un nom différent et de transformer le obj.private en proxy de self._private par @property ou data descriptor et de croire que ceux qui l'utilisent sont tous des adultes.

Quoi qu'il en soit, j'aimerais partager le concept de data descriptor qui pourrait rendre PRESQUE attributs privés en ajoutant une couche d'attribut proxy (comme je l'ai dit, cela empêcherait l'accès depuis «l'intérieur» de la classe):

class Private:
    def __init__(self, attribute):
        self.attribute = attribute

    def __get__(self, obj, type=None):
        raise AttributeError("'{}' object has no attribute '{}'".format(obj, self.attribute))

    def __set__(self, obj, value):
        obj.__dict__[self.attribute] = value

class YourClass:
    private = Private('private')

    def __init__(self):
        self.private = 10
        print(self.private)  # Raise AttributeError

Utilisez des soulignements doubles ou changez __getattribute__les deux mauvaises pratiques, surtout la dernière, peuvent provoquer des catastrophes.

3
Hou Lu

ce que j'aime faire, même si ce n'est pas à 100% privé, consiste à utiliser des méthodes de fermeture dans les méthodes pour les attributs normalement inaccessibles R/W en tant qu'objets membre_descriptor:

def privateNS():

    class MyObject(object):
        __slots__ = ['private'] # name doesn't matter

        def __new__(cls, value): # only sets inst.private on new instance creation
            inst = object.__new__(cls)

            setprivate(inst, value)

            return inst

        # __init__ is not needed, and can't be used here to set inst.private

        def showprivate(inst):
            return getprivate(inst)

    dsc = MyObject.private # get descriptor
    getprivate = dsc.__get__
    setprivate = dsc.__set__
    del MyObject.private # revoke normal access

    return MyObject

MyObject = privateNS()
del privateNS

inst = MyObject( 20 )
print( inst.showprivate() ) # 20

notez que le nom inst.private n'existe pas et générera une AttributeError si elle est référencée.
mais le descripteur de membre lui-même existe et est lié à la classe.

mais comme je l'ai dit, ce n'est pas 100% privé ...
vous pouvez accéder aux méthodes de description fournies aux méthodes de classe par leurs fermetures:

>>> inst.showprivate.__closure__[0].cell_contents
<method-wrapper '__get__' of member_descriptor object at 0x00E588A0>

c'est la première porte dérobée, si ladite méthode contient __set__ dans ses fermetures.
mais sinon, la 2e porte dérobée n’est qu’un peu plus compliquée:

>>> inst.showprivate.__closure__[0].cell_contents.__self__.__set__( inst, 30 )
>>> inst.showprivate()
30

quelque chose d’aide quand on utilise plusieurs fermetures, l’ordre des cellules de fermeture dépend de l’exécution en cours (comme les clés de dictionnaire).

malheureusement, je n'arrive pas à comprendre quoi que ce soit de plus sûr.

le problème est comme indiqué dans une réponse précédente:
Les attributs ne peuvent pas dire où ils sont accédés, et fournir ce niveau de fonctionnalité via du code python les laisse toujours ouverts, car ils peuvent toujours être consultés et modifiés.

si je me trompe, commentez s'il vous plaît :)

2
Tcll

Vous pouvez obtenir presque le même effet sans l'inspection sophistiquée en utilisant des fermetures au lieu d'attributs.

class Foo:
    def __init__(self):
        private = 'bar'
        def print_private():
            print(private)
        self.print_private = print_private

foo = Foo()
foo.print_private()  # works
foo.private  # kaboom

Bien entendu, inspect peut également voir les fermetures.

2
gilch

Bien après avoir regardé cette réponse à propos du module inspect, je l’ai (en quelque sorte) fait!

class Foo:
    def __init__(self, private):
        self.private = private

    def __getattribute__(self, attr):
        import inspect
        frame = inspect.currentframe()
        try:
            back_self = frame.f_back.__self__
            if not back_self == self: #is it inside the class?
                ban = ('private', '__dict__') #all private vars, ban __dict__ for no loopholes
                if attr in ban:
                    msg = 'Foo object has no attribute {!r}'
                    raise AttributeError(msg.format(attr))
        finally:
            del frame
        return super().__getattribute__(attr)

    def print_private(self):
        print(self.private) #access in the class!


foo = Foo('hi')
foo.print_private() #output: hi
foo.private #makes an error

Enfin presque. inspect peut également être utilisé pour trouver la valeur. Ceci est très proche, cependant. Il autorise object.attr à l'intérieur de la classe mais crée une erreur s'il est appelé de l'extérieur. C'est probablement aussi proche que possible.

2
Ethan K888