Eu gostaria de entender como a função embutida "propriedade" funciona. O que me confunde é que a propriedade
também pode ser utilizada como decorador, mas só precisa de argumentos quando utilizada como uma função incorporada e não quando utilizada como decorador.
Este exemplo é da documentação:
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.")
Os argumentos de property
's são getx
, setx
, delx
e uma string doc.
No código abaixo propriedade
é utilizado como decorador. O objeto dele é a função x
, mas no código acima não há lugar para uma função de objeto nos argumentos.
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, como são criados os decoradores x.setter
e x.deleter
?
Eu estou confuso.
A função property()
devolve um objeto [descritor] especial1:
>>> property()
<property object at 0x10ff07940>
É este objecto que tem métodos 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>
Estes actuam como decoradores too. Eles devolvem um novo objeto de propriedade:
>>> property().getter(None)
<property object at 0x10ff079f0>
que é uma cópia do objeto antigo, mas com uma das funções substituídas.
Lembre-se, que a sintaxe @decorator
é apenas açúcar sintáctico; a sintaxe:
@property
def foo(self): return self._foo
realmente significa a mesma coisa que
def foo(self): return self._foo
foo = property(foo)
então foo
a função é substituída por property(foo)
, que vimos acima é um objeto especial. Então quando você utiliza @foo.setter()
, o que você está fazendo é chamar esse método property().setter
que eu mostrei acima, que retorna uma nova cópia da propriedade, mas desta vez com a função setter substituída pelo método decorado.
A sequência seguinte também cria uma propriedade completa, usando esses métodos de decoração.
Primeiro criamos algumas funções e um objeto "propriedade" com apenas um 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
A seguir utilizamos o método .setter()
para adicionar um setter:
>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True
Por último adicionamos um deleter com o método .deleter()
:
>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True
Por último, mas não menos importante, o objeto propriedade' age como um objeto [objeto descritor][2], então ele tem [
.get()][3], [
.set()][4] e [
.delete()`]5 métodos para se conectar ao atributo de instância obtendo, definindo e excluindo:
>>> class Foo: pass
...
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!
O Descritor Howto inclui uma implementação de amostra Python pura do tipo property()
:
propriedade de classe: "Emulate PyProperty_Type() in Objects/descrobject.c" > "Emulate 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 é nenhum e fget não é nenhum: doc = fget.doc auto.doc = doc
def get(self, obj, objtype=None): se obj é Nenhum: return self se self.fget é Nenhum: raise AttributeError("atributo não legível") return self.fget(obj)
def set(self, obj, value): se self.fset é Nenhum: raise AttributeError("não pode definir atributo") self.fset(obj, valor)
def delete(self, obj): se self.fdel é Nenhum: raise AttributeError("não é possível apagar atributo") 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)
A documentação diz é apenas um atalho para criar propriedades somente de leitura. Então
@property
def x(self):
return self._x
é equivalente a
def getx(self):
return self._x
x = property(getx)
A primeira parte é simples:
@property
def x(self): ...
é o mesmo que
def x(self): ...
x = property(x)
O próximo passo seria estender esta propriedade com um setter e um deleter. E isto acontece com os métodos apropriados:
@x.setter
def x(self, value): ...
devolve uma nova propriedade que herda tudo do antigo x
mais o setter dado.
O x.deleter
funciona da mesma maneira.