Compreendendo os descritores __get__ e __set__ e Python

310

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()
  1. Por que preciso da classe descritor?

  2. O que é instancee owneraqui? (dentro __get__). Qual é o objetivo desses parâmetros?

  3. Como eu chamaria / usaria este exemplo?

Matt Bronson
fonte

Respostas:

147

O descritor é como o propertytipo de Python é implementado. Um descritor simplesmente implementa __get__, __set__etc. e depois é adicionado a outra classe em sua definição (como você fez acima com a classe Temperature). Por exemplo:

temp=Temperature()
temp.celsius #calls celsius.__get__

O acesso à propriedade à qual você atribuiu o descritor ( celsiusno exemplo acima) chama o método descritor apropriado.

instancein __get__é a instância da classe (assim acima, __get__receberia temp, enquanto owneré a classe com o descritor (assim seria Temperature).

Você precisa usar uma classe de descritor para encapsular a lógica que a alimenta. Dessa forma, se o descritor for usado para armazenar em cache alguma operação cara (por exemplo), ele poderá armazenar o valor em si e não em sua classe.

Um artigo sobre descritores pode ser encontrado aqui .

EDIT: Como jchl apontou nos comentários, se você simplesmente tentar Temperature.celsius, instanceserá None.

li.davidm
fonte
6
Qual é a diferença entre selfe instance?
Lemma Prism
2
'instance' pode ser instância de qualquer classe, self será instância da mesma classe.
TheBeginner
3
@LemmaPrism selfé a instância do descritor, instanceé a instância da classe (se instanciada) em que o descritor está ( instance.__class__ is owner).
Tcll 23/08/19
Temperature.celsiusfornece o valor de 0.0acordo com o código celsius = Celsius(). O descritor Celsius é chamado, portanto, sua instância tem o valor init 0.0atribuído ao atributo da classe Temperature, celsius.
Angel Salazar
109

Por que preciso da classe descritor?

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.

O que é instancee owneraqui? (dentro __get__). Qual é o objetivo desses parâmetros?

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 instancee ownersão definidos (e você pode trabalhar ownera partir de instanceentão parece meio sem sentido). Mas quando chamado de uma classe, apenas owneré 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 que ownernão é necessário lá.

Como eu chamaria / usaria este exemplo?

Bem, aqui está um truque legal usando classes semelhantes:

class Celsius:

    def __get__(self, instance, owner):
        return 5 * (instance.fahrenheit - 32) / 9

    def __set__(self, instance, value):
        instance.fahrenheit = 32 + 9 * value / 5


class Temperature:

    celsius = Celsius()

    def __init__(self, initial_f):
        self.fahrenheit = initial_f


t = Temperature(212)
print(t.celsius)
t.celsius = 0
print(t.fahrenheit)

(Estou usando o Python 3; para o python 2 você precisa ter certeza de que essas divisões são / 5.0e / 9.0). Isso dá:

100.0
32.0

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 ...

andrew cooke
fonte
2
As conversões estão erradas: elas devem ser C = 5 (F − 32) / 9, F = 32 + 9C / 5.
Musiphil 11/06/2015
1
Verifique se você tem um objeto de temperatura. Fazer o seguinte estraga tudo. t1 = Temperatura (190) imprime t1.celsius t1.celsius = 100 imprime t1.fahrenheit Agora, quando você verifica t.celcius e t.fahrenheit, eles também são modificados. t.celcius é 115 e t.fahrenheit é 32. o que está claramente errado. @Eric
Ishan Bhatt
1
@IshanBhatt: Eu acho que é por causa do erro apontado pelo musiphil acima. Além disso, não esta não é a minha resposta
Eric
69

Estou tentando entender o que são os descritores do Python e para que eles podem ser úteis.

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, e staticmethodtodo 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 clse self, 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_instancecom um descriptorobjeto:

  • obj_instance.descriptorchama
    descriptor.__get__(self, obj_instance, owner_class)retornando a value
    É assim que todos os métodos e a getpropriedade on funcionam.

  • obj_instance.descriptor = valuechama
    descriptor.__set__(self, obj_instance, value)retornando None
    É assim que a setterpropriedade on funciona.

  • del obj_instance.descriptorchama
    descriptor.__delete__(self, obj_instance)retornando None
    É assim que a deleterpropriedade 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 do obj_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:

def has_descriptor_attrs(obj):
    return set(['__get__', '__set__', '__delete__']).intersection(dir(obj))

def is_descriptor(obj):
    """obj can be instance of descriptor or the descriptor class"""
    return bool(has_descriptor_attrs(obj))

Um Descritor de Dados possui um __set__e / ou __delete__.
Um não descritor de dados não tem nem __set__nem __delete__.

def has_data_descriptor_attrs(obj):
    return set(['__set__', '__delete__']) & set(dir(obj))

def is_data_descriptor(obj):
    return bool(has_data_descriptor_attrs(obj))

Exemplos de objetos do descritor interno:

  • classmethod
  • staticmethod
  • property
  • funções em geral

Descritores que não são de dados

Podemos ver isso classmethode staticmethodsomos não descritores de dados:

>>> is_descriptor(classmethod), is_data_descriptor(classmethod)
(True, False)
>>> is_descriptor(staticmethod), is_data_descriptor(staticmethod)
(True, False)

Ambos têm apenas o __get__método:

>>> has_descriptor_attrs(classmethod), has_descriptor_attrs(staticmethod)
(set(['__get__']), set(['__get__']))

Observe que todas as funções também são descritores que não são de dados:

>>> def foo(): pass
... 
>>> is_descriptor(foo), is_data_descriptor(foo)
(True, False)

Descritor de Dados, property

No entanto, propertyé um descritor de dados:

>>> is_data_descriptor(property)
True
>>> has_descriptor_attrs(property)
set(['__set__', '__get__', '__delete__'])

Ordem de pesquisa pontilhada

Essas são distinções importantes , pois afetam a ordem de pesquisa de uma pesquisa pontilhada.

obj_instance.attribute
  1. Primeiro, o texto acima verifica se o atributo é um descritor de dados na classe da instância,
  2. Caso contrário, parece ver se o atributo está no obj_instance's __dict__, então
  3. finalmente retorna a um descritor que não é de dados.

A 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:

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()
  1. Por que preciso da classe descritor?

Seu descritor garante que você sempre tenha um float para este atributo de classe Temperaturee que não possa usá-lo delpara excluir o atributo:

>>> t1 = Temperature()
>>> del t1.celsius
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: __delete__

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):

class Temperature(object):
    celsius = 0.0

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:

class Temperature(object):
    _celsius = 0.0
    @property
    def celsius(self):
        return type(self)._celsius
    @celsius.setter
    def celsius(self, value):
        type(self)._celsius = float(value)
  1. O que é instância e proprietário aqui? (em get ). Qual é o objetivo desses parâmetros?

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.

  1. Como eu chamaria / usaria este exemplo?

Aqui está uma demonstração:

>>> t1 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1
>>> 
>>> t1.celsius
1.0
>>> t2 = Temperature()
>>> t2.celsius
1.0

Você não pode excluir o atributo:

>>> del t2.celsius
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: __delete__

E você não pode atribuir uma variável que não pode ser convertida em um float:

>>> t1.celsius = '0x02'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in __set__
ValueError: invalid literal for float(): 0x02

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 propertydecorador, que utiliza os mesmos descritores sob o capô, mas traz o comportamento para a implementação da classe owner (novamente, conforme definido acima):

class Temperature(object):
    _celsius = 0.0
    @property
    def celsius(self):
        return type(self)._celsius
    @celsius.setter
    def celsius(self, value):
        type(self)._celsius = float(value)

Que tem exatamente o mesmo comportamento esperado do trecho de código original:

>>> t1 = Temperature()
>>> t2 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1.0
>>> t2.celsius
1.0
>>> del t1.celsius
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't delete attribute
>>> t1.celsius = '0x02'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in celsius
ValueError: invalid literal for float(): 0x02

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.

Aaron Hall
fonte
1
Bom, eu aprendi o máximo com essa resposta (certamente aprendi com os outros também). Uma pergunta sobre esta afirmação "A maneira esperada pela qual os programadores Python mais experientes alcançariam esse resultado ...". A classe Temeperature que você define antes e depois da instrução é idêntica. Perdi o que você está recebendo aqui?
precisa saber é o seguinte
1
@YoloVoe não, está certo, eu adicionei uma palavreado entre parênteses para enfatizar que é uma repetição do que foi dito acima.
Aaron Hall
1
Esta é uma resposta INCRÍVEL. Vou precisar lê-lo mais algumas vezes, mas sinto que minha compreensão do Python aumentou alguns pontos
Lucas Young
20

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 foobarem um instancede Class:

insira a descrição da imagem aqui

Duas condições são importantes aqui:

  • Se a classe de instancetiver uma entrada para o nome do atributo e tiver __get__e __set__.
  • Se o nãoinstance tiver nenhuma entrada para o nome do atributo, mas a classe tiver um e possuir __get__.

É aí que os descritores entram nele:

  • Descritores de dados que possuem ambos __get__e __set__.
  • Descritores que não são de dados que possuem apenas __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:

Por que preciso da classe descritor?

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 selfpassado implicitamente como primeiro argumento.

def test_function(self):
    return self

class TestClass(object):
    def test_method(self):
        ...

Se você procurar test_methodem uma instância, receberá de volta um "método vinculado":

>>> instance = TestClass()
>>> instance.test_method
<bound method TestClass.test_method of <__main__.TestClass object at ...>>

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):

>>> test_function.__get__(instance, TestClass)
<bound method test_function of <__main__.TestClass object at ...>>

Você pode até chamar esse "método auto-vinculado":

>>> test_function.__get__(instance, TestClass)()
<__main__.TestClass at ...>

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. Negligenciando getter, settere deletero propertydescritor é (de descritor Guia HowTo "Propriedades" ):

class Property(object):
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        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):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

Desde que descritor de dados é invocado sempre que você olhar para cima o "nome" do propertye simplesmente delegados para as funções decorados com @property, @name.settere @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, functionfornece uma abstração para métodos, staticmethodfornece uma abstração para métodos que não precisam de acesso à instância e classmethodfornece 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:

class TypedProperty(object):
    __slots__ = ('_name', '_type')
    def __init__(self, typ):
        self._type = typ

    def __get__(self, instance, klass=None):
        if instance is None:
            return self
        return instance.__dict__[self._name]

    def __set__(self, instance, value):
        if not isinstance(value, self._type):
            raise TypeError(f"Expected class {self._type}, got {type(value)}")
        instance.__dict__[self._name] = value

    def __delete__(self, instance):
        del instance.__dict__[self._name]

    def __set_name__(self, klass, name):
        self._name = name

Então você pode usar o descritor em uma classe:

class Test(object):
    int_prop = TypedProperty(int)

E brincando um pouco com isso:

>>> t = Test()
>>> t.int_prop = 10
>>> t.int_prop
10

>>> t.int_prop = 20.0
TypeError: Expected class <class 'int'>, got <class 'float'>

Ou uma "propriedade preguiçosa":

class LazyProperty(object):
    __slots__ = ('_fget', '_name')
    def __init__(self, fget):
        self._fget = fget

    def __get__(self, instance, klass=None):
        if instance is None:
            return self
        try:
            return instance.__dict__[self._name]
        except KeyError:
            value = self._fget(instance)
            instance.__dict__[self._name] = value
            return value

    def __set_name__(self, klass, name):
        self._name = name

class Test(object):
    @LazyProperty
    def lazy(self):
        print('calculating')
        return 10

>>> t = Test()
>>> t.lazy
calculating
10
>>> t.lazy
10

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.

O que é instancee owneraqui? (dentro __get__). Qual é o objetivo desses parâmetros?

Depende de como você consulta o atributo. Se você procurar o atributo em uma instância, então:

  • o segundo argumento é a instância na qual você consulta o atributo
  • o terceiro argumento é a classe da instância

Caso você procure o atributo na classe (assumindo que o descritor esteja definido na classe):

  • o segundo argumento é None
  • o terceiro argumento é a classe em que você consulta o atributo

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).

Como eu chamaria / usaria este exemplo?

Seu exemplo é basicamente uma propriedade que permite apenas valores que podem ser convertidos floate 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 ):

>>> t1 = Temperature()
>>> t2 = Temperature()

>>> t1.celsius = 20   # setting it on one instance
>>> t2.celsius        # looking it up on another instance
20.0

>>> Temperature.celsius  # looking it up on the class
20.0

É 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.

MSeifert
fonte
não tenho certeza se o plano de fundo transparente do gráfico que realmente está sofrendo no modo escuro deve ser relatado como um bug no fluxo de pilha.
Tshirtman 18/04
@ Tshirtman Eu acho que isso é um problema com a própria imagem. Não é completamente transparente ... Eu peguei a postagem do blog e não sei como recriá-la com o fundo transparente adequado. É muito ruim, parece tão estranho com o fundo escuro :(
MSeifert 18/04
9

Por que preciso da classe descritor?

Inspirado por Fluent Python por Buciano Ramalho

Imaginando que você tem uma classe como esta

class LineItem:
     price = 10.9
     weight = 2.1
     def __init__(self, name, price, weight):
          self.name = name
          self.price = price
          self.weight = weight

item = LineItem("apple", 2.9, 2.1)
item.price = -0.9  # it's price is negative, you need to refund to your customer even you delivered the apple :(
item.weight = -0.8 # negative weight, it doesn't make sense

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

class Quantity(object):
    __index = 0

    def __init__(self):
        self.__index = self.__class__.__index
        self._storage_name = "quantity#{}".format(self.__index)
        self.__class__.__index += 1

    def __set__(self, instance, value):
        if value > 0:
            setattr(instance, self._storage_name, value)
        else:
           raise ValueError('value should >0')

   def __get__(self, instance, owner):
        return getattr(instance, self._storage_name)

defina a classe LineItem da seguinte maneira:

class LineItem(object):
     weight = Quantity()
     price = Quantity()

     def __init__(self, name, weight, price):
         self.name = name
         self.weight = weight
         self.price = price

e podemos estender a classe Quantity para validar mais comum

wllbll
fonte
1
Caso de uso interessante, pois mostra como usar o descritor para interagir com várias instâncias de usuários. Inicialmente, não entendi o ponto importante: um atributo com um descritor deve ser criado no espaço para nome da classe (por exemplo weight = Quantity(), mas os valores devem ser definidos no espaço para nome da instância usando apenas self(por exemplo self.weight = 4)), caso contrário, o atributo seria recuperado para o novo valor e o descritor seria descartado .. Nice!
minutos
Eu não sou capaz de entender uma coisa. Você está definindo weight = Quantity()como variável de classe e sua __get__e __set__está trabalhando na variável de instância. Quão?
Technocrat 23/04
0

Tentei (com pequenas alterações, conforme sugerido) o código da resposta de Andrew Cooke. (Estou executando o python 2.7).

O código:

#!/usr/bin/env python
class Celsius:
    def __get__(self, instance, owner): return 9 * (instance.fahrenheit + 32) / 5.0
    def __set__(self, instance, value): instance.fahrenheit = 32 + 5 * value / 9.0

class Temperature:
    def __init__(self, initial_f): self.fahrenheit = initial_f
    celsius = Celsius()

if __name__ == "__main__":

    t = Temperature(212)
    print(t.celsius)
    t.celsius = 0
    print(t.fahrenheit)

O resultado:

C:\Users\gkuhn\Desktop>python test2.py
<__main__.Celsius instance at 0x02E95A80>
212

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.

Gregory Kuhn
fonte
1
Os descritores funcionam apenas com novas classes de estilo. Para python 2.x este meio que derivam sua classe de "objeto", que é padrão no Python 3.
Ivo van der Wijk
0

Você veria https://docs.python.org/3/howto/descriptor.html#properties

class Property(object):
    "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
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        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):
        if self.fdel is 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__)
Yonks Somarl
fonte
1
Isso não responde à pergunta ou fornece informações úteis.
Sebastian Nielsen