Estou tentando entender o que são os descritores do Python e para que eles são úteis. Eu entendo como eles funcionam, mas aqui estão minhas dúvidas. Considere o seguinte código:
class Celsius(object):
def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
class Temperature(object):
celsius = Celsius()
Por que preciso da classe descritor?
O que é
instance
eowner
aqui? (dentro__get__
). Qual é o objetivo desses parâmetros?Como eu chamaria / usaria este exemplo?
fonte
self
einstance
?self
é a instância do descritor,instance
é a instância da classe (se instanciada) em que o descritor está (instance.__class__ is owner
).Temperature.celsius
fornece o valor de0.0
acordo com o códigocelsius = Celsius()
. O descritor Celsius é chamado, portanto, sua instância tem o valor init0.0
atribuído ao atributo da classe Temperature, celsius.Dá a você controle extra sobre como os atributos funcionam. Se você está acostumado a getters e setters em Java, por exemplo, é a maneira do Python fazer isso. Uma vantagem é que ele parece aos usuários apenas como um atributo (não há alteração na sintaxe). Assim, você pode começar com um atributo comum e, quando precisar fazer algo sofisticado, alterne para um descritor.
Um atributo é apenas um valor mutável. Um descritor permite executar código arbitrário ao ler ou definir (ou excluir) um valor. Então você pode imaginar usá-lo para mapear um atributo para um campo em um banco de dados, por exemplo - uma espécie de ORM.
Outro uso pode estar se recusando a aceitar um novo valor, lançando uma exceção
__set__
- efetivamente tornando o "atributo" somente leitura.Isso é bastante sutil (e a razão pela qual estou escrevendo uma nova resposta aqui - encontrei essa pergunta enquanto me perguntava a mesma coisa e não achei a resposta existente tão boa).
Um descritor é definido em uma classe, mas normalmente é chamado de uma instância. Quando ele é chamado de uma instância tanto
instance
eowner
são definidos (e você pode trabalharowner
a partir deinstance
então parece meio sem sentido). Mas quando chamado de uma classe, apenasowner
é definido - e é por isso que está lá.Isso é necessário apenas
__get__
porque é o único que pode ser chamado em uma classe. Se você definir o valor da classe, defina o próprio descritor. Da mesma forma para exclusão. É por isso queowner
não é necessário lá.Bem, aqui está um truque legal usando classes semelhantes:
(Estou usando o Python 3; para o python 2 você precisa ter certeza de que essas divisões são
/ 5.0
e/ 9.0
). Isso dá:Agora, existem outras maneiras, sem dúvida melhores, de obter o mesmo efeito em python (por exemplo, se celsius fosse uma propriedade, que é o mesmo mecanismo básico, mas coloca toda a fonte dentro da classe Temperature), mas isso mostra o que pode ser feito ...
fonte
Descritores são atributos de classe (como propriedades ou métodos) com qualquer um dos seguintes métodos especiais:
__get__
(método não descritor de dados, por exemplo, em um método / função)__set__
(método do descritor de dados, por exemplo, em uma instância de propriedade)__delete__
(método do descritor de dados)Esses objetos descritores podem ser usados como atributos em outras definições de classe de objeto. (Ou seja, eles vivem no
__dict__
objeto de classe.)Os objetos descritores podem ser usados para gerenciar programaticamente os resultados de uma pesquisa pontilhada (por exemplo
foo.descriptor
) em uma expressão normal, uma atribuição e até mesmo uma exclusão.Funções / métodos, métodos vinculados,
property
,classmethod
, estaticmethod
todo o uso destes métodos especiais para controlar como eles são acessados via a pesquisa pontilhada.Um descritor de dados , como
property
, pode permitir uma avaliação lenta de atributos com base em um estado mais simples do objeto, permitindo que as instâncias usem menos memória do que se você precomputasse cada atributo possível.Outro descritor de dados, a
member_descriptor
, criado por__slots__
, permite economia de memória, permitindo que a classe armazene dados em uma estrutura de dados mutável, do tipo tupla, em vez da mais flexível, mas que consome espaço__dict__
.Descritores que não são de dados, geralmente instância, classe e métodos estáticos, obtêm seus primeiros argumentos implícitos (geralmente nomeados
cls
eself
, respectivamente) a partir do método que não é o descritor de dados__get__
,.A maioria dos usuários do Python precisa aprender apenas o uso simples e não precisa aprender ou entender mais a implementação dos descritores.
Em profundidade: o que são descritores?
Um descritor é um objecto com qualquer um dos seguintes métodos (
__get__
,__set__
, ou__delete__
), destina-se a ser utilizada por meio de tracejado-pesquisa como se fosse um atributo típico de uma instância. Para um objeto-proprietário,,obj_instance
com umdescriptor
objeto:obj_instance.descriptor
chamadescriptor.__get__(self, obj_instance, owner_class)
retornando avalue
É assim que todos os métodos e a
get
propriedade on funcionam.obj_instance.descriptor = value
chamadescriptor.__set__(self, obj_instance, value)
retornandoNone
É assim que a
setter
propriedade on funciona.del obj_instance.descriptor
chamadescriptor.__delete__(self, obj_instance)
retornandoNone
É assim que a
deleter
propriedade on funciona.obj_instance
é a instância cuja classe contém a instância do objeto descritor.self
é a instância do descritor (provavelmente apenas uma para a classe doobj_instance
)Para definir isso com código, um objeto é um descritor se o conjunto de seus atributos cruzar com qualquer um dos atributos necessários:
Um Descritor de Dados possui um
__set__
e / ou__delete__
.Um não descritor de dados não tem nem
__set__
nem__delete__
.Exemplos de objetos do descritor interno:
classmethod
staticmethod
property
Descritores que não são de dados
Podemos ver isso
classmethod
estaticmethod
somos não descritores de dados:Ambos têm apenas o
__get__
método:Observe que todas as funções também são descritores que não são de dados:
Descritor de Dados,
property
No entanto,
property
é um descritor de dados:Ordem de pesquisa pontilhada
Essas são distinções importantes , pois afetam a ordem de pesquisa de uma pesquisa pontilhada.
obj_instance
's__dict__
, entãoA conseqüência dessa ordem de pesquisa é que descritores que não são de dados, como funções / métodos, podem ser substituídos por instâncias .
Recapitulação e próximas etapas
Nós aprendemos que os descritores são objetos com qualquer um
__get__
,__set__
ou__delete__
. Esses objetos descritores podem ser usados como atributos em outras definições de classe de objeto. Agora veremos como eles são usados, usando seu código como exemplo.Análise do código da pergunta
Aqui está o seu código, seguido pelas suas perguntas e respostas para cada um:
Seu descritor garante que você sempre tenha um float para este atributo de classe
Temperature
e que não possa usá-lodel
para excluir o atributo:Caso contrário, seus descritores ignoram a classe de proprietário e as instâncias do proprietário, em vez disso, armazenam o estado no descritor. Você poderia compartilhar facilmente o estado em todas as instâncias com um atributo de classe simples (desde que você sempre o defina como flutuador da classe e nunca exclua-o ou se sinta à vontade com os usuários do seu código):
Isso dá a você exatamente o mesmo comportamento do seu exemplo (consulte a resposta à pergunta 3 abaixo), mas usa o Pythons builtin (
property
), e seria considerado mais idiomático:instance
é a instância do proprietário que está chamando o descritor. O proprietário é a classe na qual o objeto descritor é usado para gerenciar o acesso ao ponto de dados. Veja as descrições dos métodos especiais que definem os descritores ao lado do primeiro parágrafo desta resposta para obter nomes de variáveis mais descritivos.Aqui está uma demonstração:
Você não pode excluir o atributo:
E você não pode atribuir uma variável que não pode ser convertida em um float:
Caso contrário, o que você tem aqui é um estado global para todas as instâncias, gerenciado pela atribuição a qualquer instância.
A maneira esperada pela qual os programadores Python mais experientes conseguiriam esse resultado seria usar o
property
decorador, que utiliza os mesmos descritores sob o capô, mas traz o comportamento para a implementação da classe owner (novamente, conforme definido acima):Que tem exatamente o mesmo comportamento esperado do trecho de código original:
Conclusão
Abordamos os atributos que definem os descritores, a diferença entre descritores de dados e não descritores de dados, objetos internos que os utilizam e perguntas específicas sobre o uso.
Então, novamente, como você usaria o exemplo da pergunta? Espero que não. Espero que você comece com a minha primeira sugestão (um atributo de classe simples) e passe para a segunda sugestão (o decorador de propriedades), se achar necessário.
fonte
Antes de entrar nos detalhes dos descritores, pode ser importante saber como a pesquisa de atributos no Python funciona. Isso pressupõe que a classe não tenha metaclasse e que use a implementação padrão de
__getattribute__
(ambas podem ser usadas para "personalizar" o comportamento).A melhor ilustração da pesquisa de atributo (no Python 3.x ou para novas classes de estilo no Python 2.x) nesse caso é da seção Compreendendo as metaclasses do Python (logel de código da ionel) . A imagem usa
:
como substituto para "pesquisa de atributo não personalizável".Isso representa a pesquisa de um atributo
foobar
em uminstance
deClass
:Duas condições são importantes aqui:
instance
tiver uma entrada para o nome do atributo e tiver__get__
e__set__
.instance
tiver nenhuma entrada para o nome do atributo, mas a classe tiver um e possuir__get__
.É aí que os descritores entram nele:
__get__
e__set__
.__get__
.Nos dois casos, o valor retornado é
__get__
chamado com a instância como primeiro argumento e a classe como segundo argumento.A pesquisa é ainda mais complicada para a pesquisa de atributos de classe (consulte, por exemplo, pesquisa de atributos de classe (no blog mencionado acima) ).
Vamos passar para suas perguntas específicas:
Na maioria dos casos, você não precisa escrever classes de descritores! No entanto, você provavelmente é um usuário final muito comum. Por exemplo, funções. Funções são descritores, é assim que as funções podem ser usadas como métodos com
self
passado implicitamente como primeiro argumento.Se você procurar
test_method
em uma instância, receberá de volta um "método vinculado":Da mesma forma, você também pode vincular uma função invocando seu
__get__
método manualmente (não é realmente recomendado, apenas para fins ilustrativos):Você pode até chamar esse "método auto-vinculado":
Observe que eu não forneci nenhum argumento e a função retornou a instância que eu havia vinculado!
Funções são descritores que não são de dados !
Alguns exemplos internos de um descritor de dados seriam
property
. Negligenciandogetter
,setter
edeleter
oproperty
descritor é (de descritor Guia HowTo "Propriedades" ):Desde que descritor de dados é invocado sempre que você olhar para cima o "nome" do
property
e simplesmente delegados para as funções decorados com@property
,@name.setter
e@name.deleter
(se houver).Existem vários outros descritores da biblioteca padrão, por exemplo
staticmethod
,classmethod
.O objetivo dos descritores é fácil (embora você raramente precise deles): Código comum abstrato para acesso ao atributo.
property
é uma abstração para acesso a variáveis de instância,function
fornece uma abstração para métodos,staticmethod
fornece uma abstração para métodos que não precisam de acesso à instância eclassmethod
fornece uma abstração para métodos que precisam de acesso à classe em vez de acesso à instância (isso é um pouco simplificado).Outro exemplo seria uma propriedade de classe .
Um exemplo divertido (usando
__set_name__
do Python 3.6) também pode ser uma propriedade que permite apenas um tipo específico:Então você pode usar o descritor em uma classe:
E brincando um pouco com isso:
Ou uma "propriedade preguiçosa":
Esses são os casos em que mover a lógica para um descritor comum pode fazer sentido, no entanto, também é possível resolvê-los (mas talvez com a repetição de algum código) por outros meios.
Depende de como você consulta o atributo. Se você procurar o atributo em uma instância, então:
Caso você procure o atributo na classe (assumindo que o descritor esteja definido na classe):
None
Então, basicamente, o terceiro argumento é necessário se você deseja personalizar o comportamento ao fazer uma pesquisa no nível de classe (porque o
instance
éNone
).Seu exemplo é basicamente uma propriedade que permite apenas valores que podem ser convertidos
float
e compartilhados entre todas as instâncias da classe (e na classe - embora seja possível usar apenas o acesso "leitura" na classe, caso contrário, você substituiria a instância do descritor ):É por isso que os descritores geralmente usam o segundo argumento (
instance
) para armazenar o valor e evitar compartilhá-lo. No entanto, em alguns casos, o compartilhamento de um valor entre instâncias pode ser desejado (embora eu não consiga pensar em um cenário no momento). No entanto, praticamente não faz sentido uma propriedade Celsius em uma classe de temperatura ... exceto talvez como exercício puramente acadêmico.fonte
Inspirado por Fluent Python por Buciano Ramalho
Imaginando que você tem uma classe como esta
Devemos validar o peso e o preço para evitar atribuir a eles um número negativo; podemos escrever menos código se usarmos o descritor como proxy, pois
defina a classe LineItem da seguinte maneira:
e podemos estender a classe Quantity para validar mais comum
fonte
weight = Quantity()
, mas os valores devem ser definidos no espaço para nome da instância usando apenasself
(por exemploself.weight = 4
)), caso contrário, o atributo seria recuperado para o novo valor e o descritor seria descartado .. Nice!weight = Quantity()
como variável de classe e sua__get__
e__set__
está trabalhando na variável de instância. Quão?Tentei (com pequenas alterações, conforme sugerido) o código da resposta de Andrew Cooke. (Estou executando o python 2.7).
O código:
O resultado:
Com o Python anterior a 3, certifique-se de subclassificar do objeto, o que fará com que o descritor funcione corretamente, pois o get magic não funciona nas classes de estilo antigo.
fonte
Você veria https://docs.python.org/3/howto/descriptor.html#properties
fonte