Como o decorador @property funciona?

981

Gostaria de entender como a função propertyinterna funciona . O que me confunde é que propertytambém pode ser usado como um decorador, mas ele só aceita argumentos quando usado como uma função interna e não quando usado como um 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.")

propertyOs argumentos de getx , setx, delxe uma série doc.

No código abaixo propertyé usado como decorador. O objetivo disso é ox função, 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 é o x.settere x.deleterdecoradores criado? Estou confuso.

como ele
fonte
13
Consulte também: Como funcionam as propriedades Python?
Martin Thoma
3
propertyé realmente uma classe (não uma função), embora provavelmente chame o __init__()método quando você cria um objeto, é claro. Usando a help(property)partir do terminal é perspicaz. helptambém é uma aula por algum motivo.
Brōtsyorfuzthrāx
Eu acho que esta ligação é um bom exemplo: [propriedade] ( journaldev.com/14893/python-property-decorator )
Sheng Bi
4
@Shule Thread de 2 anos, mas ainda assim: Tudo é uma classe. Até aulas.
Artemis ainda não confia em SE
2
Isso também foi confuso para mim. Finalmente encontrei um artigo que foi capaz de detalhá-lo para mim. Espero que isto ajude alguém. programiz.com/python-programming/property Não sou afiliado de forma alguma ao site.
Jjwdesign

Respostas:

1010

A property()função retorna um objeto descritor especial :

>>> property()
<property object at 0x10ff07940>

É este objeto que possui métodos extras :

>>> 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 agem como decoradores também . Eles retornam um novo objeto de propriedade:

>>> property().getter(None)
<property object at 0x10ff079f0>

isso é uma cópia do objeto antigo, mas com uma das funções substituídas.

Lembre-se de que a @decoratorsintaxe é apenas açúcar sintático; 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 fooa função é substituída por property(foo), que vimos acima é um objeto especial. Então, quando você usa @foo.setter(), o que você está fazendo é chamar issoproperty().setter método que 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 a seguir também cria uma propriedade completa, usando esses métodos do decorador.

Primeiro, criamos algumas funções e um propertyobjeto 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

Em seguida, usamos o .setter()método 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 .deleter()método:

>>> 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 propertyobjeto atua como um objeto descritor , para que ele tenha .__get__(), .__set__()e .__delete__()métodos para ligar para atributo de instância recebendo, a criação e exclusão de:

>>> class Foo: pass
... 
>>> prop.__get__(Foo(), Foo)
Get!
>>> prop.__set__(Foo(), 'bar')
Set to 'bar'!
>>> prop.__delete__(Foo())
Delete!

O Descriptor Howto inclui uma implementação de amostra Python pura do property()tipo:

class Property:
    "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__)
Martijn Pieters
fonte
10
Muito bom. Você pode adicionar o fato de que depois Foo.prop = proppode fazer Foo().prop = 5; pront Foo().prop; del Foo().propo resultado desejado.
glglgl
12
Os objetos de método são criados dinamicamente e podem reutilizar o mesmo local de memória, se disponível.
Martijn Pieters
1
@ MarkusMeskanen: Prefiro usar type()como acesso a atributos e métodos Dunder, para serem usados ​​como pontos de extensão pelas funções e operadores padrão.
Martijn Pieters
2
@MarkusMeskanen: como o objeto é imutável, e se você o modificou no local, não poderia ser especializado em uma subclasse.
Martijn Pieters
5
@ MarkusMeskanen: veja Python substituindo getter sem setter ; se @human.name.getteralterasse o propertyobjeto no local em vez de retornar um novo, o human.nameatributo seria alterado, alterando o comportamento dessa superclasse.
Martijn Pieters
201

A documentação diz que é apenas um atalho para criar propriedades somente leitura. assim

@property
def x(self):
    return self._x

é equivalente a

def getx(self):
    return self._x
x = property(getx)
J0HN
fonte
19
O contexto completo (resposta mais votada) é bom, mas essa resposta foi praticamente útil para descobrir por que alguém havia usado @propertycomo decorador em sua classe.
ijoseph
1
@property também pode ser usado quando você deseja adicionar um atributo a uma classe e precisa manter a compatibilidade com objetos criados anteriormente dessa classe (por exemplo, que podem ser salvos em um arquivo pickle).
AndyP 31/01
111

Aqui está um exemplo mínimo de como @propertypode ser implementado:

class Thing:
    def __init__(self, my_word):
        self._word = my_word 
    @property
    def word(self):
        return self._word

>>> print( Thing('ok').word )
'ok'

Caso contrário, wordpermanece um método em vez de uma propriedade.

class Thing:
    def __init__(self, my_word):
        self._word = my_word
    def word(self):
        return self._word

>>> print( Thing('ok').word() )
'ok'
AlexG
fonte
1
Como seria esse exemplo se a palavra função / propriedade () precisasse ser definida no init ?
JJ
5
Alguém pode explicar por que eu iria criar um decorador de propriedade aqui, em vez de apenas ter self.word = my_word- o que, em seguida, trabalhar da mesma formaprint( Thing('ok').word ) = 'ok'
SilverSlash
1
@SilverSlash Este é apenas um exemplo simples, um caso de uso real, envolveria um método mais complicado
AlexG
você pode me explicar como a impressão Thing('ok').wordchama a função internamente em tempo de execução?
Vicrobot
83

A primeira parte é simples:

@property
def x(self): ...

é o mesmo que

def x(self): ...
x = property(x)
  • que, por sua vez, é a sintaxe simplificada para criar um propertycom apenas um getter.

O próximo passo seria estender essa propriedade com um setter e um deleter. E isso acontece com os métodos apropriados:

@x.setter
def x(self, value): ...

retorna uma nova propriedade que herda tudo do antigo xmais o setter fornecido.

x.deleter funciona da mesma maneira.

glglgl
fonte
49

O seguinte:

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

É o mesmo que:

class C(object):
    def __init__(self):
        self._x = None

    def _x_get(self):
        return self._x

    def _x_set(self, value):
        self._x = value

    def _x_del(self):
        del self._x

    x = property(_x_get, _x_set, _x_del, 
                    "I'm the 'x' property.")

É o mesmo que:

class C(object):
    def __init__(self):
        self._x = None

    def _x_get(self):
        return self._x

    def _x_set(self, value):
        self._x = value

    def _x_del(self):
        del self._x

    x = property(_x_get, doc="I'm the 'x' property.")
    x = x.setter(_x_set)
    x = x.deleter(_x_del)

É o mesmo que:

class C(object):
    def __init__(self):
        self._x = None

    def _x_get(self):
        return self._x
    x = property(_x_get, doc="I'm the 'x' property.")

    def _x_set(self, value):
        self._x = value
    x = x.setter(_x_set)

    def _x_del(self):
        del self._x
    x = x.deleter(_x_del)

Qual é o mesmo que:

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
Bill Moore
fonte
4
O primeiro e o último exemplo de código são os mesmos (literalmente).
Adomas Baliuka
47

Abaixo está outro exemplo de como @propertyajudar quando é necessário refatorar o código que é retirado daqui (apenas o resumo abaixo):

Imagine que você criou uma classe Moneycomo esta:

class Money:
    def __init__(self, dollars, cents):
        self.dollars = dollars
        self.cents = cents

e um usuário cria uma biblioteca dependendo dessa classe em que ele usa, por exemplo

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

Agora, vamos supor que você decida alterar sua Moneyclasse e se livrar dos atributos dollarse, centsmas decida rastrear apenas a quantidade total de centavos:

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

Se o usuário acima mencionado agora tentar executar sua biblioteca como antes

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))

isso resultará em um erro

AttributeError: o objeto 'Money' não possui atributo 'dollars'

Isso significa que agora todo mundo que confia na sua Moneyclasse original precisaria alterar todas as linhas de código onde dollarse centsusadas, o que pode ser muito doloroso ... Então, como isso pode ser evitado? Usando@property !

É assim que:

class Money:
    def __init__(self, dollars, cents):
        self.total_cents = dollars * 100 + cents

    # Getter and setter for dollars...
    @property
    def dollars(self):
        return self.total_cents // 100

    @dollars.setter
    def dollars(self, new_dollars):
        self.total_cents = 100 * new_dollars + self.cents

    # And the getter and setter for cents.
    @property
    def cents(self):
        return self.total_cents % 100

    @cents.setter
    def cents(self, new_cents):
        self.total_cents = 100 * self.dollars + new_cents

quando agora chamamos da nossa biblioteca

money = Money(27, 12)

print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 27 dollar and 12 cents.

funcionará como esperado e não tivemos que alterar uma única linha de código em nossa biblioteca! De fato, nem precisaríamos saber que a biblioteca da qual dependemos mudou.

Também setterfunciona bem:

money.dollars += 2
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 12 cents.

money.cents += 10
print("I have {} dollar and {} cents.".format(money.dollars, money.cents))
# prints I have 29 dollar and 22 cents.

Você @propertytambém pode usar em classes abstratas; Eu dou um exemplo mínimo aqui .

Cleb
fonte
seu resumo é muito bom, o exemplo que o site leva é um pouco estranho .. Um iniciante perguntaria .. por que não podemos nos ater ao self.dollar = dollars? fizemos muito com @property, mas parece que nenhuma funcionalidade de extração foi adicionada.
Sheng Bi
1
@ShengBi: Não se concentre muito no exemplo real, mas mais no princípio subjacente: se - por qualquer motivo - você precisar refatorar o código, poderá fazê-lo sem afetar o código de outra pessoa.
cleb
21

Li todas as postagens aqui e percebi que podemos precisar de um exemplo da vida real. Por que, na verdade, temos @property? Portanto, considere um aplicativo Flask no qual você usa o sistema de autenticação. Você declara um usuário modelo em models.py:

class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(64), unique=True, index=True)
    username = db.Column(db.String(64), unique=True, index=True)
    password_hash = db.Column(db.String(128))

    ...

    @property
    def password(self):
        raise AttributeError('password is not a readable attribute')

    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

Nesse código, nós "escondemos" o atributo passwordusando o @propertyque aciona a AttributeErrorasserção quando você tenta acessá-lo diretamente, enquanto usamos @ property.setter para definir a variável de instância real password_hash.

Agora auth/views.py, podemos instanciar um usuário com:

...
@auth.route('/register', methods=['GET', 'POST'])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        user = User(email=form.email.data,
                    username=form.username.data,
                    password=form.password.data)
        db.session.add(user)
        db.session.commit()
...

Atributo de aviso passwordque vem de um formulário de registro quando um usuário preenche o formulário. A confirmação da senha acontece no front end com EqualTo('password', message='Passwords must match')(no caso de você estar se perguntando, mas é um formulário diferente do Flask relacionado ao tópico).

Espero que este exemplo seja útil

Leo Skhrnkv
fonte
18

Este ponto foi esclarecido por muitas pessoas lá em cima, mas aqui está um ponto direto que eu estava procurando. Acho que é importante começar pelo decorador @property. por exemplo:-

class UtilityMixin():
    @property
    def get_config(self):
        return "This is property"

A chamada da função "get_config ()" funcionará assim.

util = UtilityMixin()
print(util.get_config)

Se você notar que não usei colchetes "()" para chamar a função. Esta é a coisa básica que eu estava procurando pelo decorador @property. Para que você possa usar sua função como uma variável.

Devendra Bhat
fonte
1
ponto muito útil que ajuda a condensar esse conceito abstrato.
Info5ek
18

Vamos começar com os decoradores de Python.

Um decorador Python é uma função que ajuda a adicionar algumas funcionalidades adicionais a uma função já definida.

No Python, tudo é um objeto. Funções em Python são objetos de primeira classe, o que significa que podem ser referenciadas por uma variável, adicionadas nas listas, passadas como argumentos para outra função etc.

Considere o seguinte trecho de código.

def decorator_func(fun):
    def wrapper_func():
        print("Wrapper function started")
        fun()
        print("Given function decorated")
        # Wrapper function add something to the passed function and decorator 
        # returns the wrapper function
    return wrapper_func

def say_bye():
    print("bye!!")

say_bye = decorator_func(say_bye)
say_bye()

# Output:
#  Wrapper function started
#  bye
#  Given function decorated

Aqui, podemos dizer que a função decorador modificou nossa função say_hello e acrescentou algumas linhas de código extras.

Sintaxe Python para decorador

def decorator_func(fun):
    def wrapper_func():
        print("Wrapper function started")
        fun()
        print("Given function decorated")
        # Wrapper function add something to the passed function and decorator 
        # returns the wrapper function
    return wrapper_func

@decorator_func
def say_bye():
    print("bye!!")

say_bye()

Vamos concluir tudo do que com um cenário de caso, mas antes disso vamos falar sobre alguns princípios do oops.

Getters e setters são usados ​​em muitas linguagens de programação orientadas a objetos para garantir o princípio do encapsulamento de dados (é visto como o agrupamento de dados com os métodos que operam nesses dados).

Esses métodos são, obviamente, o getter para recuperar os dados e o setter para alterar os dados.

De acordo com esse princípio, os atributos de uma classe são privados para ocultar e protegê-los de outro código.

Sim, @property é basicamente uma maneira pitônica de usar getters e setters.

O Python possui um ótimo conceito chamado property, que simplifica muito a vida de um programador orientado a objetos.

Vamos supor que você decida fazer uma aula que possa armazenar a temperatura em graus Celsius.

class Celsius:
def __init__(self, temperature = 0):
    self.set_temperature(temperature)

def to_fahrenheit(self):
    return (self.get_temperature() * 1.8) + 32

def get_temperature(self):
    return self._temperature

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    self._temperature = value

Código refatorado, veja como poderíamos ter conseguido isso com propriedade.

No Python, property () é uma função interna que cria e retorna um objeto de propriedade.

Um objeto de propriedade possui três métodos, getter (), setter () e delete ().

class Celsius:
def __init__(self, temperature = 0):
    self.temperature = temperature

def to_fahrenheit(self):
    return (self.temperature * 1.8) + 32

def get_temperature(self):
    print("Getting value")
    return self.temperature

def set_temperature(self, value):
    if value < -273:
        raise ValueError("Temperature below -273 is not possible")
    print("Setting value")
    self.temperature = value

temperature = property(get_temperature,set_temperature)

Aqui,

temperature = property(get_temperature,set_temperature)

poderia ter sido quebrado como,

# make empty property
temperature = property()
# assign fget
temperature = temperature.getter(get_temperature)
# assign fset
temperature = temperature.setter(set_temperature)

Ponto a observar:

  • get_temperature permanece uma propriedade em vez de um método.

Agora você pode acessar o valor da temperatura escrevendo.

C = Celsius()
C.temperature
# instead of writing C.get_temperature()

Podemos continuar adiante e não definir os nomes get_temperature e set_temperature , pois são desnecessários e poluir o namespace da classe.

A maneira pitônica de lidar com o problema acima é usar @property .

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature

    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

    @property
    def temperature(self):
        print("Getting value")
        return self.temperature

    @temperature.setter
    def temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        print("Setting value")
        self.temperature = value

Pontos a serem observados -

  1. Um método usado para obter um valor é decorado com "@property".
  2. O método que deve funcionar como setter é decorado com "@ temperature.setter". Se a função tivesse sido chamada "x", teríamos que decorá-la com "@ x.setter".
  3. Escrevemos "dois" métodos com o mesmo nome e um número diferente de parâmetros "def temperature (self)" e "def temperature (self, x)".

Como você pode ver, o código é definitivamente menos elegante.

Agora, vamos falar sobre um cenário prático da vida real.

Digamos que você tenha projetado uma classe da seguinte maneira:

class OurClass:

    def __init__(self, a):
        self.x = a


y = OurClass(10)
print(y.x)

Agora, vamos supor que nossa classe ficou popular entre os clientes e eles começaram a usá-la em seus programas. Eles fizeram todos os tipos de atribuições ao objeto.

E um dia fatídico, um cliente confiável veio até nós e sugeriu que "x" deve ser um valor entre 0 e 1000, esse é realmente um cenário horrível!

Devido às propriedades, é fácil: criamos uma versão de propriedade de "x".

class OurClass:

    def __init__(self,x):
        self.x = x

    @property
    def x(self):
        return self.__x

    @x.setter
    def x(self, x):
        if x < 0:
            self.__x = 0
        elif x > 1000:
            self.__x = 1000
        else:
            self.__x = x

Isso é ótimo, não é: você pode começar com a implementação mais simples que se possa imaginar e poderá migrar posteriormente para uma versão de propriedade sem precisar alterar a interface! Portanto, as propriedades não são apenas um substituto para getters e setter!

Você pode verificar esta implementação aqui

Divyanshu Rawat
fonte
2
Sua classe Celsius será repetida infinitamente ao definir (o que significa mediante instanciação).
Ted Petrou
1
@Ted Petrou Eu não te peguei? Como ele se repetirá infinitamente ao definir?
Divyanshu Rawat 23/09/18
Na verdade, isso não está claro ... as pessoas estão perguntando o porquê, mas o exemplo não é convincente ...
Sheng Bi
1
É apenas um comentário, minha opinião pessoal. Sua resposta pode ser muito boa. então deixe isso.
Sheng Bi
1
comparado com as principais respostas votadas, esta é projetada para humanos; obrigado.
Info5ek
6

property é uma classe por trás @property decorador.

Você sempre pode verificar isso:

print(property) #<class 'property'>

Reescrevi o exemplo de help(property)para mostrar que a @propertysintaxe

class C:
    def __init__(self):
        self._x=None

    @property 
    def x(self):
        return self._x

    @x.setter 
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

c = C()
c.x="a"
print(c.x)

é funcionalmente idêntico à property()sintaxe:

class C:
    def __init__(self):
        self._x=None

    def g(self):
        return self._x

    def s(self, v):
        self._x = v

    def d(self):
        del self._x

    prop = property(g,s,d)

c = C()
c.x="a"
print(c.x)

Não há diferença em como usamos a propriedade, como você pode ver.

Para responder à pergunta, o @propertydecorador é implementado via propertyclasse.


Então, a questão é explicar propertyum pouco a classe. Está linha:

prop = property(g,s,d)

Foi a inicialização. Podemos reescrevê-lo assim:

prop = property(fget=g,fset=s,fdel=d)

O significado de fget, fsetefdel :

 |    fget
 |      function to be used for getting an attribute value
 |    fset
 |      function to be used for setting an attribute value
 |    fdel
 |      function to be used for del'ing an attribute
 |    doc
 |      docstring

A próxima imagem mostra os trigêmeos que temos, da classe property:

insira a descrição da imagem aqui

__get__, __set__E __delete__estão lá para ser substituído . Esta é a implementação do padrão descritor em Python.

Em geral, um descritor é um atributo de objeto com "comportamento de ligação", aquele cujo acesso ao atributo foi substituído por métodos no protocolo do descritor.

Também pode usar a propriedade setter, gettere deletermétodos para ligar a função à propriedade. Veja o próximo exemplo. O método s2da classe Cdefinirá a propriedade duplicada .

class C:
    def __init__(self):
        self._x=None

    def g(self):
        return self._x

    def s(self, x):
        self._x = x

    def d(self):
        del self._x

    def s2(self,x):
        self._x=x+x


    x=property(g)
    x=x.setter(s)
    x=x.deleter(d)      


c = C()
c.x="a"
print(c.x) # outputs "a"

C.x=property(C.g, C.s2)
C.x=C.x.deleter(C.d)
c2 = C()
c2.x="a"
print(c2.x) # outputs "aa"
prosti
fonte
1

Uma propriedade pode ser declarada de duas maneiras.

  • Criando os métodos getter, setter para um atributo e passando-os como argumento para a propriedade função de
  • Usando o decorador @property .

Você pode dar uma olhada em alguns exemplos que escrevi sobre propriedades em python .

nvd
fonte
você pode atualizar sua resposta dizendo que propriedade é uma classe para que eu possa votar.
Prosti
1

A melhor explicação pode ser encontrada aqui: Python @Property Explained - Como usar e quando? (Exemplos completos) de Selva Prabhakaran | 5 de novembro de 2018

Isso me ajudou a entender POR QUE não apenas COMO.

https://www.machinelearningplus.com/python/python-property/

Victor Wang
fonte
0

Aqui está outro exemplo:

##
## Python Properties Example
##
class GetterSetterExample( object ):
    ## Set the default value for x ( we reference it using self.x, set a value using self.x = value )
    __x = None


##
## On Class Initialization - do something... if we want..
##
def __init__( self ):
    ## Set a value to __x through the getter / setter... Since __x is defined above, this doesn't need to be set...
    self.x = 1234

    return None


##
## Define x as a property, ie a getter - All getters should have a default value arg, so I added it - it will not be passed in when setting a value, so you need to set the default here so it will be used..
##
@property
def x( self, _default = None ):
    ## I added an optional default value argument as all getters should have this - set it to the default value you want to return...
    _value = ( self.__x, _default )[ self.__x == None ]

    ## Debugging - so you can see the order the calls are made...
    print( '[ Test Class ] Get x = ' + str( _value ) )

    ## Return the value - we are a getter afterall...
    return _value


##
## Define the setter function for x...
##
@x.setter
def x( self, _value = None ):
    ## Debugging - so you can see the order the calls are made...
    print( '[ Test Class ] Set x = ' + str( _value ) )

    ## This is to show the setter function works.... If the value is above 0, set it to a negative value... otherwise keep it as is ( 0 is the only non-negative number, it can't be negative or positive anyway )
    if ( _value > 0 ):
        self.__x = -_value
    else:
        self.__x = _value


##
## Define the deleter function for x...
##
@x.deleter
def x( self ):
    ## Unload the assignment / data for x
    if ( self.__x != None ):
        del self.__x


##
## To String / Output Function for the class - this will show the property value for each property we add...
##
def __str__( self ):
    ## Output the x property data...
    print( '[ x ] ' + str( self.x ) )


    ## Return a new line - technically we should return a string so it can be printed where we want it, instead of printed early if _data = str( C( ) ) is used....
    return '\n'

##
##
##
_test = GetterSetterExample( )
print( _test )

## For some reason the deleter isn't being called...
del _test.x

Basicamente, o mesmo que o exemplo C (objeto), exceto que estou usando x em vez disso ... Eu também não inicializo em __init - ... bem ... sim, mas ele pode ser removido porque __x é definido como parte da classe....

A saída é:

[ Test Class ] Set x = 1234
[ Test Class ] Get x = -1234
[ x ] -1234

e se eu comentar o self.x = 1234 no init , a saída é:

[ Test Class ] Get x = None
[ x ] None

e se eu definir o _default = None como _default = 0 na função getter (como todos os getters devem ter um valor padrão, mas não são passados ​​pelos valores de propriedade do que vi, para que você possa defini-lo aqui, e na verdade, não é ruim porque você pode definir o padrão uma vez e usá-lo em qualquer lugar), por exemplo: def x (self, _default = 0):

[ Test Class ] Get x = 0
[ x ] 0

Nota: A lógica getter existe apenas para que o valor seja manipulado por ele para garantir que ele seja manipulado por ele - o mesmo para as instruções de impressão ...

Nota: Estou acostumado a Lua e sou capaz de criar dinamicamente mais de 10 auxiliares quando chamo uma única função e criei algo semelhante para Python sem usar propriedades e funciona até certo ponto, mas, mesmo que as funções estejam sendo criadas antes sendo usado, ainda há problemas às vezes em serem chamados antes da criação, o que é estranho, pois não é codificado dessa maneira ... Prefiro a flexibilidade das meta-tabelas Lua e o fato de poder usar setters / getters reais em vez de acessar diretamente diretamente uma variável ... Eu gosto da rapidez com que algumas coisas podem ser criadas com o Python - por exemplo, programas de GUI. embora uma que estou projetando possa não ser possível sem muitas bibliotecas adicionais - se eu a codifico no AutoHotkey, posso acessar diretamente as chamadas de dll de que preciso, e o mesmo pode ser feito em Java, C #, C ++,

Nota: O código de saída deste fórum está quebrado - eu tive que adicionar espaços à primeira parte do código para que ele funcionasse - ao copiar / colar, assegure-se de converter todos os espaços em abas .... Eu uso abas para Python, porque em um arquivo com 10.000 linhas, o tamanho do arquivo pode ser de 512 KB a 1 MB com espaços e de 100 a 200 KB com guias, o que equivale a uma enorme diferença no tamanho do arquivo e redução no tempo de processamento ...

As guias também podem ser ajustadas por usuário - portanto, se você preferir 2 espaços de largura, 4, 8 ou o que for possível, significa que é útil para desenvolvedores com déficit de visão.

Nota: Todas as funções definidas na classe não são recuadas corretamente devido a um erro no software do fórum - certifique-se de recuar se você copiar / colar

Acecool
fonte
-3

Uma observação: para mim, para o Python 2.x, @propertynão funcionou como anunciado quando não herdei de object:

class A():
    pass

mas funcionou quando:

class A(object):
    pass

para Python 3, funcionou sempre.

Gyula Sámuel Karli
fonte
5
Isso ocorre porque, no Python 2, uma classe que não herda objecté uma classe de estilo antigo, e as classes de estilo antigo não suportam o protocolo do descritor (que é o que propertyimplementa para funcionar da maneira que funciona). No Python 3, as classes de estilo antigo não existem mais; todas as classes são o que chamamos de classes new-style em Python 2.
chepner