Qual è il modo più semplice e conciso per rendere leggibili gli attributi selezionati in un'istanza?

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

  •  02-07-2019
  •  | 
  •  

Domanda

In Python, voglio fare in modo che gli attributi di istanza selezionati di una classe siano di sola lettura per codificare al di fuori della classe. Voglio che non ci sia modo in cui il codice esterno possa modificare l'attributo, se non indirettamente invocando metodi sull'istanza. Voglio che la sintassi sia concisa. Qual è il modo migliore? (Fornisco la mia migliore risposta attuale di seguito ...)

È stato utile?

Soluzione

Dovresti usare il decoratore @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

Altri suggerimenti

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

Questo output:

$ python ./p.py
c.fullaccess = 0
c.fullaccess = 1234
c.readonly = 22
Non posso cambiare c.readonly
c.readonly = 22
c.readonly = 23

Mi pizzicano le dita per poterlo dire

    @readonly
    self.readonly = 22

cioè, usa un decoratore su un attributo. Sarebbe così pulito ...

Ecco come:

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)

(Supponendo che foobar e blahblah siano gli attributi che vuoi essere di sola lettura.) Preparare due caratteri di sottolineatura a un nome di attributo si nasconde efficacemente dall'esterno della classe, quindi le versioni interne non saranno accessibili dall'esterno. Questo funziona solo per le classi di nuovo stile ereditate dall'oggetto poiché dipende dalla proprietà .

D'altra parte ... questa è una cosa piuttosto sciocca da fare. Mantenere le variabili private sembra essere un'ossessione che proviene da C ++ e Java. I tuoi utenti dovrebbero usare l'interfaccia pubblica per la tua classe perché è ben progettata, non perché li costringi a.

Modifica: sembra che Kevin abbia già pubblicato una versione simile.

Non esiste un modo reale per farlo. Esistono modi per renderlo più "difficile", ma non esiste un concetto di attributi di classe completamente nascosti e inaccessibili.

Se la persona che utilizza la tua classe non può essere considerata attendibile per seguire i documenti API, questo è il loro problema. Proteggere le persone dal fare cose stupide significa solo che faranno cose stupide molto più elaborate, complicate e dannose per provare a fare tutto ciò che non avrebbero dovuto fare in primo luogo.

Potresti usare una metaclasse che avvolge automaticamente metodi (o attributi di classe) che seguono una convenzione di denominazione in proprietà (spudoratamente presa da Tipi e classi unificanti in 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))

Questo ti permette di usare:

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

Sono consapevole che William Keller è di gran lunga la soluzione più pulita .. ma ecco qualcosa che mi è venuto in mente ..

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)

Ed ecco l'esempio di utilizzo

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

Purtroppo questa soluzione non può gestire variabili private (__ variabili nominate).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top