Vamos supor que queremos criar uma família de classes que sejam diferentes implementações ou especializações de um conceito abrangente. Vamos supor que haja uma implementação padrão plausível para algumas propriedades derivadas. Queremos colocar isso em uma classe base
class Math_Set_Base:
@property
def size(self):
return len(self.elements)
Assim, uma subclasse será capaz de contar automaticamente seus elementos neste exemplo bastante tolo
class Concrete_Math_Set(Math_Set_Base):
def __init__(self,*elements):
self.elements = elements
Concrete_Math_Set(1,2,3).size
# 3
Mas e se uma subclasse não quiser usar esse padrão? Isso não funciona:
import math
class Square_Integers_Below(Math_Set_Base):
def __init__(self,cap):
self.size = int(math.sqrt(cap))
Square_Integers_Below(7)
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# File "<stdin>", line 3, in __init__
# AttributeError: can't set attribute
Sei que existem maneiras de substituir uma propriedade por uma propriedade, mas eu gostaria de evitar isso. Como o objetivo da classe base é tornar a vida o mais fácil possível para o usuário, não adicionar inchaço impondo um método de acesso complicado e supérfluo (do ponto de vista restrito da subclasse).
Isso pode ser feito? Se não, qual é a próxima melhor solução?
fonte
__get__()
,__set__()
e__delete__()
) e elesAttributeError
geram um se você não fornecer nenhuma função para eles. Veja Python equivalente à implementação de propriedade .setter
ou__set__
da propriedade, isso funcionará. No entanto, aviso que isso também significa que você pode substituir facilmente a propriedade não apenas no nível da classe (self.size = ...
), mas também no nível da instância (por exemplo,Concrete_Math_Set(1, 2, 3).size = 10
seria igualmente válido). Comida para pensamentos :)Square_Integers_Below(9).size = 1
também é válido, pois é um atributo simples. Nesse caso de uso "tamanho" específico, isso pode parecer estranho (use uma propriedade), mas no caso geral "substitui um objeto computado facilmente na subclasse", pode ser bom em alguns casos. Você também pode controlar o acesso aos atributos,__setattr__()
mas isso pode ser esmagador.Esta será uma resposta prolongada que pode servir apenas como cortesia ... mas sua pergunta me levou para um passeio pela toca do coelho, então eu gostaria de compartilhar minhas descobertas (e dor) também.
Você pode achar que essa resposta não é útil para o seu problema real. De fato, minha conclusão é que - eu não faria isso. Dito isto, o pano de fundo desta conclusão pode entretê-lo um pouco, pois você está procurando mais detalhes.
Abordando alguns equívocos
A primeira resposta, embora correta na maioria dos casos, nem sempre é o caso. Por exemplo, considere esta classe:
inst_prop
, enquanto sendo umproperty
, é irrevogavelmente um atributo de instância:Tudo depende de onde você
property
está definido em primeiro lugar. Se seu@property
é definido dentro da classe "escopo" (ou realmente, onamespace
), ele se torna um atributo de classe. No meu exemplo, a classe em si não tem conhecimentoinst_prop
até ser instanciada. Obviamente, não é muito útil como propriedade aqui.Mas primeiro, vamos abordar seu comentário sobre a resolução de herança ...
Então, como exatamente a herança influencia esse problema? Este artigo a seguir mergulha um pouco no tópico e a Ordem de resolução do método está um pouco relacionada, embora discuta principalmente a amplitude da herança em vez da profundidade.
Combinado com a nossa descoberta, dadas as configurações abaixo:
Imagine o que acontece quando essas linhas são executadas:
O resultado é assim:
Note como:
self.world_view
foi capaz de ser aplicado, enquantoself.culture
falhouculture
não existe emChild.__dict__
(omappingproxy
da classe, não deve ser confundido com a instância__dict__
)culture
existac.__dict__
, não é referenciado.Você pode adivinhar o porquê -
world_view
foi substituído pelaParent
classe como uma não propriedade, e tambémChild
foi possível substituí-lo. Enquanto isso, comoculture
é herdado, ele existe apenas dentromappingproxy
deGrandparent
:De fato, se você tentar remover
Parent.culture
:Você notará que nem existe
Parent
. Porque o objeto está se referindo diretamente novamenteGrandparent.culture
.Então, e a ordem de resolução?
Portanto, estamos interessados em observar a ordem de resolução real, vamos tentar remover
Parent.world_view
:Gostaria de saber qual é o resultado?
Ele voltou ao Grandparent's
world_view
property
, apesar de termos conseguido atribuir oself.world_view
antes! Mas e se mudarmos vigorosamenteworld_view
no nível de classe, como a outra resposta? E se a excluirmos? E se atribuirmos o atributo de classe atual a uma propriedade?O resultado é:
Isso é interessante porque
c.world_view
é restaurado ao seu atributo de instância, enquantoChild.world_view
é o que atribuímos . Após remover o atributo de instância, ele reverte para o atributo de classe. E depois de reatribuir aChild.world_view
propriedade à, perdemos instantaneamente o acesso ao atributo de instância.Portanto, podemos supor a seguinte ordem de resolução :
property
, recupere seu valor viagetter
oufget
(mais sobre isso mais adiante). Classe atual primeiro da classe Base por último.property
atributo que não é de classe. Classe atual primeiro da classe Base por último.Nesse caso, vamos remover a raiz
property
:Que dá:
Surpresa!
Child
agora tem seus próprios comculture
base na inserção forçada emc.__dict__
.Child.culture
não existe, é claro, uma vez que nunca foi definido emParent
ouChild
atributo de classe eGrandparent
foi removido.Essa é a causa raiz do meu problema?
Na verdade não . O erro que você está recebendo, que ainda estamos observando ao atribuir
self.culture
, é totalmente diferente . Mas a ordem de herança define o pano de fundo para a resposta - que é aproperty
própria.Além do
getter
método mencionado anteriormente ,property
também tem alguns truques legais nas mangas. O mais relevante nesse caso é o método,setter
oufset
, que é acionado porself.culture = ...
linha. Como vocêproperty
não implementou nenhuma funçãosetter
oufget
, python não sabe o que fazer e lança umAttributeError
(iecan't set attribute
).Se, no entanto, você implementou um
setter
método:Ao instanciar a
Child
classe, você receberá:Em vez de receber um
AttributeError
, agora você está realmente chamando osome_prop.setter
método O que lhe dá mais controle sobre seu objeto ... com nossas descobertas anteriores, sabemos que precisamos ter um atributo de classe substituído antes que ele atinja a propriedade. Isso pode ser implementado dentro da classe base como um gatilho. Aqui está um novo exemplo:O que resulta em:
SURPRESA! Agora você pode substituir seu próprio atributo de instância em uma propriedade herdada!
Então, problema resolvido?
... Na verdade não. O problema com essa abordagem é que agora você não pode ter um
setter
método adequado . Há casos em que você deseja definir valores no seuproperty
. Mas agora, sempre que você definirself.culture = ...
, sempre substituirá qualquer função que você definiu nagetter
(que neste caso é realmente apenas a@property
parte envolvida. Você pode adicionar medidas mais sutis, mas de uma forma ou de outra sempre envolverá mais do que apenasself.culture = ...
. por exemplo:É muito mais complicado que a outra resposta,
size = None
no nível da classe.Você também pode escrever seu próprio descritor para manipular os métodos
__get__
e__set__
ou adicionais. Mas no final do dia, quandoself.culture
é referenciada,__get__
sempre será acionada primeiro e, quandoself.culture = ...
é referenciada,__set__
sempre será acionada primeiro. Não há como contornar isso, tanto quanto eu tentei.O cerne da questão, IMO
O problema que vejo aqui é - você não pode comer o seu bolo e também.
property
significa um descritor com acesso conveniente a partir de métodos comogetattr
ousetattr
. Se você também deseja que esses métodos atinjam um objetivo diferente, está apenas pedindo problemas. Talvez eu repensasse a abordagem:property
para isso?property
, existe algum motivo para substituí-lo?property
não se aplicar?property
s, um método separado me serviria melhor do que simplesmente reatribuir, pois a reatribuição pode acidentalmente anular osproperty
s?Para o ponto 5, minha abordagem seria ter um
overwrite_prop()
método na classe base que substituísse o atributo de classe atual para queproperty
não seja mais acionado:Como você pode ver, embora ainda seja um pouco artificial, é pelo menos mais explícito que um enigmático
size = None
. Dito isso, em última análise, eu não sobrescreveria a propriedade e reconsideraria meu projeto da raiz.Se você chegou até aqui - obrigado por percorrer essa jornada comigo. Foi um pequeno exercício divertido.
fonte
A
@property
é definido no nível da classe. A documentação entra em detalhes exaustivos sobre como funciona, mas basta dizer que definir ou obter a propriedade resolve chamar um método específico. No entanto, oproperty
objeto que gerencia esse processo é definido com a própria definição da classe. Ou seja, é definido como uma variável de classe, mas se comporta como uma variável de instância.Uma conseqüência disso é que você pode reatribuí-lo livremente no nível da classe :
E, como qualquer outro nome no nível da classe (por exemplo, métodos), você pode substituí-lo em uma subclasse, definindo-o explicitamente de forma diferente:
Quando criamos uma instância real, a variável de instância simplesmente oculta a variável de classe com o mesmo nome. O
property
objeto normalmente usa algumas travessuras para manipular esse processo (por exemplo, aplicando getters e setters), mas quando o nome no nível da classe não é definido como uma propriedade, nada de especial acontece e, portanto, age como você esperaria de qualquer outra variável.fonte
__dict__
? Além disso, existem maneiras de automatizar isso? Sim, é apenas uma linha, mas é o tipo de coisa que é muito enigmática de ler se você não estiver familiarizado com os detalhes sangrentos de propriedades etc.# declare size to not be a @property
Você não precisa de atribuição (a
size
).size
é uma propriedade na classe base, para que você possa substituir essa propriedade na classe filho:Você pode (micro) otimizar isso pré-computando a raiz quadrada:
fonte
size
é uma propriedade e não um atributo de instância, você não precisa fazer nada sofisticado.Parece que você deseja definir
size
em sua classe:Outra opção é armazenar
cap
na sua classe e calculá-la comsize
definida como uma propriedade (que substitui a propriedade da classe basesize
).fonte
Sugiro adicionar um setter assim:
Dessa forma, você pode substituir a
.size
propriedade padrão da seguinte maneira:fonte
Além disso, você pode fazer a próxima coisa
fonte
_size
ou está_size_call
lá. Você poderia ter ativado a chamada de função dentrosize
como uma condição e usartry... except...
para testar em_size
vez de usar uma referência de classe extra que não será usada. Independentemente disso, acho que isso é ainda mais enigmático do que a simplessize = None
substituição em primeiro lugar.Eu acho que é possível definir uma propriedade de uma classe base para outra propriedade de uma classe derivada, dentro da classe derivada e, em seguida, usar a propriedade da classe base com um novo valor.
No código inicial, existe um tipo de conflito entre o nome
size
da classe base e o atributoself.size
da classe derivada. Isso pode ser visível se substituirmos o nomeself.size
da classe derivada porself.length
. Isso produzirá:Então, se substituirmos o nome do método
size
porlength
todas as ocorrências do programa, isso resultará na mesma exceção:O código fixo, ou de qualquer maneira, uma versão que de alguma forma funcione, é manter o código exatamente o mesmo, com exceção da classe
Square_Integers_Below
, que definirá o métodosize
da classe base, para outro valor.E então, quando executamos todo o programa, a saída é:
Espero que isso tenha sido útil de uma maneira ou de outra.
fonte