Quel est le moyen le plus simple et le plus concis de faire en sorte que les attributs sélectionnés dans une instance soient en lecture seule?

StackOverflow https://stackoverflow.com/questions/125034

  •  02-07-2019
  •  | 
  •  

Question

En Python, je souhaite que les attributs d'instance d'une classe sélectionnés soient en lecture seule pour être codés en dehors de la classe. Je veux qu'il n'y ait aucun moyen en dehors du code de modifier l'attribut, sauf indirectement en invoquant des méthodes sur l'instance. Je veux que la syntaxe soit concise. Quel est le meilleur moyen? (Je donne ma meilleure réponse actuelle ci-dessous ...)

Était-ce utile?

La solution

Vous devez utiliser le décorateur @property .

>>> class a(object):
...     def __init__(self, x):
...             self.x = x
...     @property
...     def xval(self):
...             return self.x
... 
>>> b = a(5)
>>> b.xval
5
>>> b.xval = 6
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

Autres conseils

class C(object):

    def __init__(self):

        self.fullaccess = 0
        self.__readonly = 22 # almost invisible to outside code...

    # define a publicly visible, read-only version of '__readonly':
    readonly = property(lambda self: self.__readonly)

    def inc_readonly( self ):
        self.__readonly += 1

c=C()

# prove regular attribute is RW...
print "c.fullaccess = %s" % c.fullaccess
c.fullaccess = 1234
print "c.fullaccess = %s" % c.fullaccess

# prove 'readonly' is a read-only attribute
print "c.readonly = %s" % c.readonly
try:
    c.readonly = 3
except AttributeError:
    print "Can't change c.readonly"
print "c.readonly = %s" % c.readonly

# change 'readonly' indirectly...
c.inc_readonly()
print "c.readonly = %s" % c.readonly

Cette sortie:

$ python ./p.py
c.fullaccess = 0
c.fullaccess = 1234
c.readonly = 22
Ne peut pas changer c.readonly
c.readonly = 22
c.readonly = 23

Mes doigts me démangent de pouvoir dire

    @readonly
    self.readonly = 22

i.e., utilisez un décorateur sur un attribut. Ce serait si propre ...

Voici comment:

class whatever(object):
  def __init__(self, a, b, c, ...):
    self.__foobar = 1
    self.__blahblah = 2

  foobar = property(lambda self: self.__foobar)
  blahblah = property(lambda self: self.__blahblah)

(En supposant que foobar et blahblah soient les attributs que vous souhaitez lire uniquement.) La précommande deux sous un nom d'attribut masque efficacement de l'extérieur de la classe, les versions internes ne seront pas accessibles de l'extérieur. Cela ne fonctionne que pour les classes de style nouveau héritant de l'objet car il dépend de la propriété .

D'autre part ... c'est une chose assez stupide à faire. Garder les variables privées semble être une obsession de C ++ et Java. Vos utilisateurs doivent utiliser l’interface publique de votre classe car elle est bien conçue, et non parce que vous les y obligez.

Modifier: on dirait que Kevin a déjà posté une version similaire.

Il n’existe aucun moyen réel de le faire. Il existe des moyens de rendre la tâche plus "difficile", mais il n'y a pas de concept d'attributs de classe complètement cachés et inaccessibles.

Si la personne qui utilise votre classe ne peut pas se fier à la documentation de l'API, alors c'est son propre problème. Protéger les gens des activités stupides signifie simplement qu'ils feront des choses stupides beaucoup plus élaborées, compliquées et dommageables pour essayer de faire ce qu’ils n’auraient pas dû faire au départ.

Vous pouvez utiliser une métaclasse qui encapsule automatiquement les méthodes (ou attributs de classe) qui suivent une convention de dénomination dans des propriétés (extraites de manière honteuse de Unification des types et des classes dans Python 2.2 :

class autoprop(type):
    def __init__(cls, name, bases, dict):
        super(autoprop, cls).__init__(name, bases, dict)
        props = {}
        for name in dict.keys():
            if name.startswith("_get_") or name.startswith("_set_"):
                props[name[5:]] = 1
        for name in props.keys():
            fget = getattr(cls, "_get_%s" % name, None)
            fset = getattr(cls, "_set_%s" % name, None)
            setattr(cls, name, property(fget, fset))

Ceci vous permet d'utiliser:

class A:
    __metaclass__ = autosuprop
    def _readonly(self):
        return __x

Je suis conscient que William Keller est de loin la solution la plus propre .. mais voici quelque chose que j'ai imaginé ..

class readonly(object):
    def __init__(self, attribute_name):
        self.attribute_name = attribute_name

    def __get__(self, instance, instance_type):
        if instance != None:
            return getattr(instance, self.attribute_name)
        else:
            raise AttributeError("class %s has no attribute %s" % 
                                 (instance_type.__name__, self.attribute_name))

    def __set__(self, instance, value):
        raise AttributeError("attribute %s is readonly" % 
                              self.attribute_name)

Et voici l'exemple d'utilisation

class a(object):
    def __init__(self, x):
        self.x = x
    xval = readonly("x")

Malheureusement, cette solution ne peut pas gérer les variables privées (__ variables nommées).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top