Qual è il modo più semplice e conciso per rendere leggibili gli attributi selezionati in un'istanza?
-
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 ...)
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).