¿Cómo funciona el decorador @property?

981

Me gustaría entender cómo funciona la función incorporada property. Lo que me confunde es queproperty también se puede usar como decorador, pero solo toma argumentos cuando se usa como una función incorporada y no cuando se usa como decorador.

Este ejemplo es de la documentación :

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

propertyargumentos 's son getx, setx,delx y una cadena de documentación.

En el siguiente código propertyse utiliza como decorador. El objeto de la misma es la xfunción, pero en el código anterior no hay lugar para una función de objeto en los 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

Y, ¿cómo son las x.settery x.deleterdecoradores creado? Estoy confundido.

como él
fuente
13
Ver también: ¿Cómo funcionan las propiedades de Python?
Martin Thoma
3
propertyen realidad es una clase (no una función), aunque probablemente sí llama al __init__()método cuando haces un objeto, por supuesto. Usar help(property)desde la terminal es perspicaz. helpTambién es una clase por alguna razón.
Brōtsyorfuzthrāx
Creo que este enlace proporciona un buen ejemplo: [propiedad] ( journaldev.com/14893/python-property-decorator )
Sheng Bi
44
@Shule hilo de 2 años, pero aún así: todo es una clase. Incluso clases.
Artemis todavía no confía en SE
2
Esto fue confuso para mí también. Finalmente encontré un artículo que fue capaz de desglosarme. Espero que esto ayude a alguien más. programiz.com/python-programming/property No estoy afiliado de ninguna manera con el sitio.
jjwdesign

Respuestas:

1012

La property()función devuelve un objeto descriptor especial :

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

Es este objeto el que tiene métodos adicionales :

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

Estos actúan como decoradores también . Devuelven un nuevo objeto de propiedad:

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

Es una copia del objeto antiguo, pero con una de las funciones reemplazadas.

Recuerde que la @decoratorsintaxis es solo azúcar sintáctica; la sintaxis:

@property
def foo(self): return self._foo

realmente significa lo mismo que

def foo(self): return self._foo
foo = property(foo)

entonces foola función se reemplaza por property(foo), que vimos arriba es un objeto especial. Luego, cuando usas @foo.setter(), lo que estás haciendo es llamar a esoproperty().setter método le mostré anteriormente, que devuelve una nueva copia de la propiedad, pero esta vez con la función setter reemplazada por el método decorado.

La siguiente secuencia también crea una propiedad completa, utilizando esos métodos decoradores.

Primero creamos algunas funciones y un propertyobjeto con solo un 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

Luego usamos el .setter()método para agregar un setter:

>>> prop = prop.setter(setter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is None
True

Por último, agregamos un eliminador con el .deleter()método:

>>> prop = prop.deleter(deleter)
>>> prop.fget is getter
True
>>> prop.fset is setter
True
>>> prop.fdel is deleter
True

Por último, pero no menos importante, el propertyobjeto actúa como un objeto descriptor , por lo que tiene .__get__(), .__set__()y .__delete__()métodos para enganchar en el atributo de instancia obtener, configurar y eliminar:

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

El Descriptor Howto incluye una implementación de muestra pura de Python del 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
fuente
10
Muy bien. Podría agregar el hecho de que después Foo.prop = proppuede hacerlo Foo().prop = 5; pront Foo().prop; del Foo().propcon el resultado deseado.
glglgl
12
Los objetos de método se crean sobre la marcha y pueden reutilizar la misma ubicación de memoria si está disponible.
Martijn Pieters
1
@MarkusMeskanen: Prefiero usar type()como acceso a atributos y métodos dunder están destinados a ser utilizados como puntos de extensión por las funciones y operadores estándar.
Martijn Pieters
2
@MarkusMeskanen: porque el objeto es inmutable, y si lo mutaste en su lugar no podrías especializarlo en una subclase.
Martijn Pieters
55
@MarkusMeskanen: vea Python anulando getter sin setter ; Si se @human.name.gettermodifica el propertyobjeto en el lugar en lugar de devolver un nuevo, el human.nameatributo se alterará, cambiando el comportamiento de esa superclase.
Martijn Pieters
202

La documentación dice que es solo un atajo para crear propiedades de solo lectura. Entonces

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

es equivalente a

def getx(self):
    return self._x
x = property(getx)
J0HN
fuente
20
El contexto completo (la respuesta más votada) es bueno, pero esta respuesta fue prácticamente útil para descubrir por qué alguien más había usado @propertycomo decorador en su clase.
ijoseph
1
@property también se puede usar cuando desea agregar un atributo a una clase y necesita mantener la compatibilidad con los objetos creados previamente de esa clase (por ejemplo, que podrían guardarse en un archivo pickle).
AndyP
112

Aquí hay un ejemplo mínimo de cómo @propertyse puede implementar:

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

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

De lo contrario, wordsigue siendo un método en lugar de una propiedad.

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

>>> print( Thing('ok').word() )
'ok'
AlexG
fuente
1
¿Cómo se vería este ejemplo si la función / propiedad word () tuviera que definirse en init ?
JJ
55
¿Alguien puede explicar por qué crearía un decorador de propiedades aquí, en lugar de simplemente tener self.word = my_word, que luego funcionaría de la misma maneraprint( Thing('ok').word ) = 'ok'
SilverSlash
1
@SilverSlash Este es solo un ejemplo simple, un caso de uso real implicaría un método más complicado
AlexG
¿podría explicarme cómo la impresión Thing('ok').wordllama a la función internamente en tiempo de ejecución?
Vicrobot
83

La primera parte es simple:

@property
def x(self): ...

es lo mismo que

def x(self): ...
x = property(x)
  • que, a su vez, es la sintaxis simplificada para crear un propertycon solo un captador.

El siguiente paso sería ampliar esta propiedad con un establecedor y un eliminador. Y esto sucede con los métodos apropiados:

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

devuelve una nueva propiedad que hereda todo del anterior xmás el configurador dado.

x.deleter Funciona de la misma manera.

glglgl
fuente
49

Este siguiente:

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

Es lo mismo 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.")

Es lo mismo 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)

Es lo mismo 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)

Que es lo mismo 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
fuente
44
El primer y último ejemplo de código son los mismos (textualmente).
Adomas Baliuka
47

A continuación se muestra otro ejemplo de cómo @propertypuede ayudar cuando uno tiene que refactorizar el código que se toma de aquí (solo lo resumen a continuación):

Imagina que creaste una clase Moneycomo esta:

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

y un usuario crea una biblioteca dependiendo de esta clase donde él / ella usa, por ejemplo

money = Money(27, 12)

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

Ahora supongamos que decide cambiar su Moneyclase y deshacerse de los atributos dollarsy, centssino que decide rastrear solo la cantidad total de centavos:

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

Si el usuario mencionado ahora intenta ejecutar su biblioteca como antes

money = Money(27, 12)

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

resultará en un error

AttributeError: el objeto 'Money' no tiene el atributo 'dollars'

Eso significa que ahora todos los que confían en su Moneyclase original tendrían que cambiar todas las líneas de código donde dollarsy centsse usan, lo que puede ser muy doloroso ... Entonces, ¿cómo podría evitarse esto? Al usar @property!

Así es como:

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

cuando ahora llamamos desde nuestra 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 se esperaba y no tuvimos que cambiar una sola línea de código en nuestra biblioteca! De hecho, ni siquiera tendríamos que saber que la biblioteca de la que dependemos cambió.

También setterfunciona bien:

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.

Puede usar @propertytambién en clases abstractas; Doy un ejemplo mínimo aquí .

Cleb
fuente
su resumen es muy bueno, el ejemplo que toma el sitio web es un poco extraño ... Un principiante preguntaría ... ¿por qué no podemos seguir con el self.dollar = dollars? Hemos hecho mucho con @property, pero parece que no se agregó ninguna funcionalidad de extracción.
Sheng Bi
1
@ShengBi: No se centre tanto en el ejemplo real, sino más en el principio subyacente: si, por alguna razón, tiene que refactorizar el código, puede hacerlo sin afectar el código de nadie más.
Cleb
21

Leí todas las publicaciones aquí y me di cuenta de que podemos necesitar un ejemplo de la vida real. ¿Por qué, en realidad, tenemos @property? Por lo tanto, considere una aplicación Flask donde use el sistema de autenticación. Usted declara un usuario modelo en 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)

En este código, hemos "ocultado" el atributo passwordmediante el uso de @propertyla AttributeErroraserción de disparadores cuando intenta acceder a él directamente, mientras que usamos @ property.setter para establecer la variable de instancia real password_hash.

Ahora auth/views.pypodemos instanciar a un Usuario con:

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

Observe el atributo passwordque proviene de un formulario de registro cuando un usuario llena el formulario. La confirmación de contraseña ocurre en el front-end con EqualTo('password', message='Passwords must match')(en caso de que se lo pregunte, pero es un tema diferente relacionado con los formularios de Flask).

Espero que este ejemplo sea útil

Leo Skhrnkv
fuente
18

Muchas personas allá arriba aclararon este punto, pero aquí hay un punto directo que estaba buscando. Esto es lo que creo que es importante comenzar con el decorador @property. p.ej:-

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

La llamada de la función "get_config ()" funcionará así.

util = UtilityMixin()
print(util.get_config)

Si observa que no he usado corchetes "()" para llamar a la función. Esto es lo básico que estaba buscando para el decorador @property. Para que pueda usar su función como una variable.

Devendra Bhat
fuente
1
punto muy útil que ayuda a condensar este concepto abstracto.
Info5ek
18

Comencemos con los decoradores de Python.

Un decorador de Python es una función que ayuda a agregar algunas funcionalidades adicionales a una función ya definida.

En Python, todo es un objeto. Las funciones en Python son objetos de primera clase, lo que significa que pueden ser referenciadas por una variable, agregadas en las listas, pasadas como argumentos a otra función, etc.

Considere el siguiente fragmento 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

Aquí, podemos decir que la función decoradora modificó nuestra función say_hello y agregó algunas líneas de código adicionales.

Sintaxis de 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()

Concluimos todo más que con un escenario de caso, pero antes de eso hablemos sobre algunos principios de Uy.

Los getters y setters se utilizan en muchos lenguajes de programación orientados a objetos para garantizar el principio de la encapsulación de datos (se ve como la agrupación de datos con los métodos que operan en estos datos).

Estos métodos son, por supuesto, el getter para recuperar los datos y el setter para cambiar los datos.

De acuerdo con este principio, los atributos de una clase se hacen privados para ocultarlos y protegerlos de otro código.

Sí, @property es básicamente una forma pitónica de usar getters y setters.

Python tiene un gran concepto llamado propiedad que hace que la vida de un programador orientado a objetos sea mucho más simple.

Supongamos que decide hacer una clase que pueda almacenar la temperatura en grados 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 refactorizado, así es como podríamos haberlo logrado con la propiedad

En Python, property () es una función integrada que crea y devuelve un objeto de propiedad.

Un objeto de propiedad tiene tres métodos, getter (), setter () y 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)

Aquí,

temperature = property(get_temperature,set_temperature)

podría haberse desglosado como,

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

Punto a tener en cuenta:

  • get_temperature sigue siendo una propiedad en lugar de un método.

Ahora puede acceder al valor de la temperatura escribiendo.

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

Podemos continuar y no definir los nombres get_temperature y set_temperature ya que son innecesarios y contaminan el espacio de nombres de la clase.

La forma pitónica de tratar el problema anterior es utilizar @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

Puntos a tener en cuenta:

  1. Un método que se utiliza para obtener un valor está decorado con "@property".
  2. El método que tiene que funcionar como setter está decorado con "@ temperature.setter". Si la función se hubiera llamado "x", tendríamos que decorarla con "@ x.setter".
  3. Escribimos "dos" métodos con el mismo nombre y un número diferente de parámetros "def temperature (self)" y "def temperature (self, x)".

Como puede ver, el código es definitivamente menos elegante.

Ahora, hablemos de un escenario práctico de la vida real.

Digamos que ha diseñado una clase de la siguiente manera:

class OurClass:

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


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

Ahora, supongamos que nuestra clase se hizo popular entre los clientes y comenzaron a usarla en sus programas. Hicieron todo tipo de tareas para el objeto.

Y un día fatídico, un cliente de confianza vino a nosotros y sugirió que "x" tiene que ser un valor entre 0 y 1000, ¡este es realmente un escenario horrible!

Debido a las propiedades es fácil: creamos una versión de propiedad 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

Esto es genial, ¿no es así: puede comenzar con la implementación más simple imaginable, y luego puede migrar a una versión de propiedad sin tener que cambiar la interfaz! ¡Entonces las propiedades no son solo un reemplazo para captadores y colocadores!

Puedes consultar esta Implementación aquí

Divyanshu Rawat
fuente
2
Su clase Celsius se repetirá infinitamente al configurar (lo que significa una instanciación).
Ted Petrou
1
@Ted Petrou ¿No te entendí? ¿Cómo se repetirá infinitamente al configurar?
Divyanshu Rawat
En realidad, esto no está claro ... la gente pregunta por qué, pero el ejemplo no es convincente ...
Sheng Bi
1
Es solo un comentario, mi opinión personal. Tu respuesta podría ser realmente buena. así que déjalo.
Sheng Bi
1
en comparación con las respuestas más votadas, esta está diseñada para humanos; Gracias.
Info5ek
6

propertyes una clase detrás de @propertydecorador.

Siempre puedes verificar esto:

print(property) #<class 'property'>

Reescribí el ejemplo de help(property)para mostrar que la @propertysintaxis

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)

es funcionalmente idéntico a la property()sintaxis:

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)

No hay diferencia en cómo usamos la propiedad como puede ver.

Para responder a la pregunta, el @propertydecorador se implementa a través de la propertyclase.


Entonces, la pregunta es explicar propertyun poco la clase. Esta línea:

prop = property(g,s,d)

Fue la inicialización. Podemos reescribirlo así:

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

El significado de fget, fsety fdel:

 |    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

La siguiente imagen muestra los trillizos que tenemos, de la clase property:

ingrese la descripción de la imagen aquí

__get__, __set__y __delete__están para ser anulados . Esta es la implementación del patrón descriptor en Python.

En general, un descriptor es un atributo de objeto con "comportamiento de enlace", uno cuyo acceso al atributo ha sido anulado por los métodos en el protocolo del descriptor.

También podemos usar la propiedad setter, gettery deletermétodos para obligar a la función a la propiedad. Mira el siguiente ejemplo. El método s2de la clase Cestablecerá la propiedad 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
fuente
1

Una propiedad puede declararse de dos maneras.

  • Creando los métodos getter, setter para un atributo y luego pasándolos como argumento a la función de propiedad
  • Usando el decorador @property .

Puedes echar un vistazo a algunos ejemplos que he escrito sobre las propiedades en Python .

nvd
fuente
¿Puedes actualizar tu respuesta diciendo que la propiedad es una clase para que yo pueda votar?
prosti
0

Aquí hay otro ejemplo:

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

Básicamente, lo mismo que el ejemplo C (objeto), excepto que estoy usando x en su lugar ... Tampoco inicializo en __init - ... bueno ... lo hago, pero se puede eliminar porque __x se define como parte de la clase....

El resultado es:

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

y si comento self.x = 1234 en init , la salida es:

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

y si configuro _default = None en _default = 0 en la función getter (ya que todos los getters deberían tener un valor predeterminado pero los valores de propiedad no lo pasan de lo que he visto para que pueda definirlo aquí, y en realidad no está mal porque puede definir el valor predeterminado una vez y usarlo en todas partes) es decir: def x (self, _default = 0):

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

Nota: La lógica del captador está ahí solo para que el valor sea manipulado por él para garantizar que sea manipulado por él, lo mismo para las declaraciones de impresión ...

Nota: estoy acostumbrado a Lua y puedo crear dinámicamente más de 10 ayudantes cuando llamo a una sola función e hice algo similar para Python sin usar propiedades y funciona hasta cierto punto, pero, aunque las funciones se están creando antes Al usarlo, todavía hay problemas a veces con que se los llame antes de crearlos, lo cual es extraño ya que no está codificado de esa manera ... Prefiero la flexibilidad de las metatablas Lua y el hecho de que puedo usar setters / getters reales en lugar de esencialmente acceder directamente a una variable ... Sin embargo, me gusta la rapidez con la que se pueden construir algunas cosas con Python, por ejemplo, programas gui. aunque uno que estoy diseñando puede no ser posible sin muchas bibliotecas adicionales: si lo codifico en AutoHotkey puedo acceder directamente a las llamadas dll que necesito, y lo mismo se puede hacer en Java, C #, C ++,

Nota: El código de salida en este foro está roto; tuve que agregar espacios a la primera parte del código para que funcione, cuando copie / pegue asegúrese de convertir todos los espacios en pestañas ... Uso pestañas para Python porque en un archivo que tiene 10,000 líneas, el tamaño del archivo puede ser de 512 KB a 1 MB con espacios y de 100 a 200 KB con pestañas, lo que equivale a una gran diferencia para el tamaño del archivo y una reducción en el tiempo de procesamiento ...

Las pestañas también se pueden ajustar por usuario, por lo que si prefiere 2 espacios de ancho, 4, 8 o lo que sea que pueda hacer, significa que es útil para los desarrolladores con déficit de visión.

Nota: Todas las funciones definidas en la clase no se sangran correctamente debido a un error en el software del foro; asegúrese de sangrarlo si copia / pega

Acecool
fuente
-3

Una observación: para mí, para Python 2.x, @propertyno funcionó como se anunció cuando no heredé de object:

class A():
    pass

pero funcionó cuando:

class A(object):
    pass

para Python 3, funcionó siempre.

Gyula Sámuel Karli
fuente
55
Eso es porque en Python 2, una clase que no hereda objectes una clase de estilo antiguo, y las clases de estilo antiguo no admiten el protocolo descriptor (que es lo que propertyimplementa para funcionar de la manera que lo hace). En Python 3, las clases de estilo antiguo ya no existen; todas las clases son lo que llamamos clases de estilo nuevo en Python 2.
chepner