Vorrei capire come funziona la funzione built-in property
. Quello che mi confonde è che property
può essere usata anche come decoratore, ma prende solo argomenti quando viene usata come funzione built-in e non quando viene usata come decoratore.
Questo esempio è tratto dalla documentazione:
class C(object):
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
Gli argomenti di property
'sono getx
, setx
, delx
e una stringa doc.
Nel codice che segue, property
è usato come decoratore. Il suo oggetto è la funzione x
, ma nel codice sopra non c'è posto per una funzione oggetto negli argomenti.
class C(object):
def __init__(self):
self._x = None
@property
def x(self):
"""I'm the 'x' property."""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
E come vengono creati i decoratori x.setter
e x.deleter
?
Sono confuso.
La funzione property()
restituisce uno speciale oggetto descrittore:
>>> property()
<property object at 0x10ff07940>
È questo oggetto che ha dei metodi extra:
>>> property().getter
<built-in method getter of property object at 0x10ff07998>
>>> property().setter
<built-in method setter of property object at 0x10ff07940>
>>> property().deleter
<built-in method deleter of property object at 0x10ff07998>
Questi agiscono come decoratori anche. Restituiscono un nuovo oggetto proprietà:
>>> property().getter(None)
<property object at 0x10ff079f0>
che è una copia del vecchio oggetto, ma con una delle funzioni sostituite.
Ricordate che la sintassi @decoratore
è solo uno zucchero sintattico; la sintassi:
@property
def foo(self): return self._foo
in realtà significa la stessa cosa di
def foo(self): return self._foo
foo = property(foo)
quindi foo
la funzione è sostituita da property(foo)
, che abbiamo visto sopra è un oggetto speciale. Quindi quando usate @foo.setter()
, quello che state facendo è chiamare il metodo property().setter
che vi ho mostrato sopra, che restituisce una nuova copia della proprietà, ma questa volta con la funzione setter sostituita dal metodo decorato.
Anche la sequenza seguente crea una proprietà completa, usando quei metodi decoratori.
Prima creiamo alcune funzioni e un oggetto property
con solo un getter:
>>> def getter(self): print('Get!')
...
>>> def setter(self, value): print('Set to {!r}!'.format(value))
...
>>> def deleter(self): print('Delete!')
...
>>> prop = property(getter)
>>> prop.fget is getter
True
>>> prop.fset is None
True
>>> prop.fdel is None
True
Poi usiamo il metodo .setter()
per aggiungere un setter:
>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True
Infine aggiungiamo un deleter con il metodo .deleter()
:
>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True
Ultimo ma non meno importante, l'oggetto property
agisce come un oggetto descrittore, quindi ha i metodi .__get__()
, .__set__()
e .__delete__()
per agganciare l'ottenimento, l'impostazione e la cancellazione degli attributi di istanza:
>>> class Foo: pass
...
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!
Il Descriptor Howto include una implementazione Python pura del tipo property()
:
classe Property: "Emulare PyProperty_Type() in Objects/descrobject.c"
def init(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel se doc è None e fget non è None doc = fget.doc self.doc = doc
def get(self, obj, objtype=None): se obj è None: return self se self.fget è None: raise AttributeError("attributo illeggibile") return self.fget(obj)
def set(self, obj, value): if self.fset is None: raise AttributeError("can't set attribute") self.fset(obj, value)
def delete(self, obj): se self.fdel è None: raise AttributeError("can't delete attribute") self.fdel(obj)
def getter(self, fget): return type(self)(fget, self.fset, self.fdel, self.doc)
def setter(self, fset): return type(self)(self.fget, fset, self.fdel, self.doc)
def deleter(self, fdel): return type(self)(self.fget, self.fset, fdel, self.doc)
La documentazione dice che è solo una scorciatoia per creare proprietà di sola lettura. Quindi
@property
def x(self):
return self._x
è equivalente a
def getx(self):
return self._x
x = property(getx)
La prima parte è semplice:
@property
def x(self): ...
è uguale a
def x(self): ...
x = property(x)
proprietà
con solo un getter.Il passo successivo sarebbe quello di estendere questa proprietà con un setter e un deleter. E questo avviene con i metodi appropriati:
@x.setter
def x(self, value): ...
restituisce una nuova proprietà che eredita tutto dalla vecchia x
più il setter dato.
x.deleter
funziona allo stesso modo.