Prefiero propiedades . Para eso están allí.
La razón es que todos los atributos son públicos en Python. Iniciar nombres con un guión bajo o dos es solo una advertencia de que el atributo dado es un detalle de implementación que puede no ser el mismo en futuras versiones del código. No le impide realmente obtener o establecer ese atributo. Por lo tanto, el acceso a atributos estándar es la forma normal y pitónica de, bueno, acceder a los atributos.
La ventaja de las propiedades es que son sintácticamente idénticas al acceso a los atributos, por lo que puede cambiar de una a otra sin ningún cambio en el código del cliente. Incluso podría tener una versión de una clase que use propiedades (por ejemplo, para código por contrato o depuración) y una que no lo haga para producción, sin cambiar el código que la usa. Al mismo tiempo, no tiene que escribir getters y setters para todo en caso de que necesite controlar mejor el acceso más adelante.
En Python no usas getters o setters o propiedades solo por el gusto de hacerlo. Primero solo usa atributos y luego, solo si es necesario, eventualmente migra a una propiedad sin tener que cambiar el código usando sus clases.
De hecho, hay una gran cantidad de código con extensión .py que usa getters y setters y herencia y clases sin sentido en todas partes donde, por ejemplo, una simple tupla funcionaría, pero es código de personas que escriben en C ++ o Java usando Python.
Ese no es el código Python.
fuente
El uso de propiedades le permite comenzar con los accesos normales a los atributos y luego respaldarlos con getters y setters luego, según sea necesario .
fuente
La respuesta corta es: las propiedades ganan sin dudas . Siempre.
A veces hay necesidad de captadores y colocadores, pero aun así, los "escondería" al mundo exterior. Hay un montón de maneras de hacer esto en Python (
getattr
,setattr
,__getattribute__
, etc ..., pero muy concisa y limpia es:Aquí hay un breve artículo que presenta el tema de getters y setters en Python.
fuente
property
. ( Parece una tarea simple, pero llama a una función.) Por lo tanto, "Pitónico" es esencialmente un término sin sentido, excepto por la definición tautológica: "Las convenciones pitónicas son cosas que hemos definido como pitónicas".property
característica amenace con hacer que la idea de axiomas pitónicos sea casi inútil. Entonces, todo lo que nos queda es una lista de verificación.self
objeto , los establecedores explícitos pueden ser útiles. Por ejemplo,user.email = "..."
no parece que pueda generar una excepción porque solo parece establecer un atributo, mientras queuser.set_email("...")
deja en claro que podría haber efectos secundarios como excepciones.[ TL; DR? Puedes saltar al final para ver un ejemplo de código .]
De hecho, prefiero usar un idioma diferente, que es un poco complicado de usar como algo único, pero es bueno si tienes un caso de uso más complejo.
Un poco de historia primero.
Las propiedades son útiles porque nos permiten manejar tanto la configuración como la obtención de valores de una manera programática, pero aún permiten acceder a los atributos como atributos. Podemos convertir 'gets' en 'cálculos' (esencialmente) y podemos convertir 'sets' en 'eventos'. Digamos que tenemos la siguiente clase, que he codificado con getters y setters similares a Java.
Tal vez se pregunte por qué no llamé
defaultX
ydefaultY
en el__init__
método del objeto . La razón es que para nuestro caso quiero suponer que lossomeDefaultComputation
métodos devuelven valores que varían con el tiempo, digamos una marca de tiempo, y siempre quex
(oy
) no esté establecido (donde, para el propósito de este ejemplo, "no establecido" significa "establecido" a Ninguno ") Quiero el valor del cálculo predeterminado dex
'(oy
' s).Así que esto es lamentable por una serie de razones descritas anteriormente. Lo reescribiré usando propiedades:
¿Qué hemos ganado? Hemos ganado la capacidad de referirnos a estos atributos como atributos aunque, entre bastidores, terminamos ejecutando métodos.
Por supuesto, el poder real de las propiedades es que generalmente queremos que estos métodos hagan algo además de obtener y establecer valores (de lo contrario, no tiene sentido usar propiedades). Hice esto en mi ejemplo getter. Básicamente, estamos ejecutando un cuerpo de función para seleccionar un valor predeterminado cada vez que no se establece el valor. Este es un patrón muy común.
Pero, ¿qué estamos perdiendo y qué no podemos hacer?
La molestia principal, en mi opinión, es que si define un captador (como lo hacemos aquí) también debe definir un setter. [1] Eso es ruido extra que satura el código.
Otra molestia es que todavía tenemos que inicializar los valores
x
y . (Bueno, por supuesto, podríamos agregarlos usando, pero eso es más código adicional).y
__init__
setattr()
Tercero, a diferencia del ejemplo similar a Java, los captadores no pueden aceptar otros parámetros. Ahora puedo oírte decir, bueno, si está tomando parámetros, ¡no es un captador! En un sentido oficial, eso es cierto. Pero en un sentido práctico, no hay razón para que no podamos parametrizar un atributo con nombre, como
x
, y establecer su valor para algunos parámetros específicos.Sería bueno si pudiéramos hacer algo como:
por ejemplo. Lo más cerca que podemos llegar es anular la asignación para implicar una semántica especial:
y, por supuesto, asegúrese de que nuestro configurador sepa cómo extraer los primeros tres valores como clave para un diccionario y establecer su valor en un número o algo así.
Pero incluso si lo hiciéramos, aún no podríamos admitirlo con propiedades porque no hay forma de obtener el valor porque no podemos pasar parámetros en absoluto al getter. Así que hemos tenido que devolver todo, introduciendo una asimetría.
El getter / setter de estilo Java nos permite manejar esto, pero volvemos a necesitar getter / setter.
En mi opinión, lo que realmente queremos es algo que capture los siguientes requisitos:
Los usuarios definen solo un método para un atributo dado y pueden indicar allí si el atributo es de solo lectura o de lectura-escritura. Las propiedades fallan en esta prueba si el atributo se puede escribir.
No es necesario que el usuario defina una variable adicional subyacente a la función, por lo que no necesitamos el
__init__
osetattr
en el código. La variable simplemente existe por el hecho de que hemos creado este nuevo atributo de estilo.Cualquier código predeterminado para el atributo se ejecuta en el propio cuerpo del método.
Podemos establecer el atributo como un atributo y hacer referencia a él como un atributo.
Podemos parametrizar el atributo.
En términos de código, queremos una forma de escribir:
y luego poder hacer:
Etcétera.
También queremos una manera de hacer esto para el caso especial de un atributo parametrizable, pero aún así permitimos que funcione el caso de asignación predeterminado. Verá cómo abordé esto a continuación.
Ahora al punto (¡sí! ¡El punto!). La solución que encontré para esto es la siguiente.
Creamos un nuevo objeto para reemplazar la noción de una propiedad. El objeto está destinado a almacenar el valor de un conjunto de variables, pero también mantiene un identificador de código que sabe cómo calcular un valor predeterminado. Su trabajo es almacenar el conjunto
value
o ejecutarmethod
si ese valor no está establecido.Llamémoslo un
UberProperty
.Supongo que
method
aquí hay un método de clase,value
es el valor deUberProperty
, y he agregadoisSet
porqueNone
puede ser un valor real y esto nos permite una forma clara de declarar que realmente no hay "ningún valor". Otra forma es un centinela de algún tipo.Básicamente, esto nos da un objeto que puede hacer lo que queremos, pero ¿cómo lo ponemos realmente en nuestra clase? Bueno, las propiedades usan decoradores; porque no podemos Veamos cómo podría verse (de aquí en adelante me limitaré a usar solo un 'atributo'
x
).En realidad, esto aún no funciona, por supuesto. Tenemos que implementar
uberProperty
y asegurarnos de que maneja tanto get como sets.Comencemos con gets.
Mi primer intento fue simplemente crear un nuevo objeto UberProperty y devolverlo:
Rápidamente descubrí, por supuesto, que esto no funciona: Python nunca une lo invocable al objeto y necesito el objeto para llamar a la función. Incluso crear el decorador en la clase no funciona, ya que aunque ahora tenemos la clase, todavía no tenemos un objeto con el que trabajar.
Entonces vamos a necesitar poder hacer más aquí. Sabemos que un método solo necesita ser representado una vez, así que sigamos con nuestro decorador, pero modifiquemos
UberProperty
para almacenar solo lamethod
referencia:Tampoco es invocable, por lo que por el momento nada funciona.
¿Cómo completamos la imagen? Bueno, ¿con qué terminamos cuando creamos la clase de ejemplo usando nuestro nuevo decorador?
en ambos casos recuperamos lo
UberProperty
que, por supuesto, no es invocable, por lo que esto no es de mucha utilidad.Lo que necesitamos es alguna forma de vincular dinámicamente la
UberProperty
instancia creada por el decorador después de que la clase se haya creado a un objeto de la clase antes de que ese objeto se haya devuelto a ese usuario para su uso. Um, sí, es una__init__
llamada, amigo.Escribamos lo que queremos que nuestro resultado sea primero. Estamos vinculando
UberProperty
a una instancia, por lo que una cosa obvia para devolver sería una BoundUberProperty. Aquí es donde realmente mantendremos el estado delx
atributo.Ahora nosotros la representación; ¿Cómo llevar esto a un objeto? Hay algunos enfoques, pero el más fácil de explicar solo usa el
__init__
método para hacer ese mapeo. En el momento en que__init__
se llama, nuestros decoradores se han ejecutado, por lo que solo debemos mirar a través de los objetos__dict__
y actualizar los atributos donde el valor del atributo es de tipoUberProperty
.Ahora, las propiedades súper son geniales y probablemente querremos usarlas mucho, por lo que tiene sentido crear una clase base que haga esto para todas las subclases. Creo que sabes cómo se llamará la clase base.
Agregamos esto, cambiamos nuestro ejemplo para heredar de
UberObject
, y ...Después de modificar
x
para ser:Podemos ejecutar una prueba simple:
Y obtenemos la salida que queríamos:
(Gee, estoy trabajando hasta tarde)
Tenga en cuenta que he utilizado
getValue
,setValue
yclearValue
aquí. Esto se debe a que aún no he vinculado los medios para devolverlos automáticamente.Pero creo que este es un buen lugar para parar por ahora, porque me estoy cansando. También puede ver que la funcionalidad principal que queríamos está en su lugar; el resto es escaparatismo. Escaparate de usabilidad importante, pero eso puede esperar hasta que tenga un cambio para actualizar la publicación.
Terminaré el ejemplo en la próxima publicación abordando estas cosas:
Necesitamos asegurarnos de que UberObject's
__init__
siempre sea llamado por subclases.Necesitamos asegurarnos de manejar el caso común en el que alguien 'alias' una función a otra cosa, como:
Necesitamos
e.x
regresare.x.getValue()
por defecto.e.x.getValue()
. (Hacer esto es obvio, si aún no lo ha solucionado).Necesitamos apoyar la configuración
e.x directly
, como ene.x = <newvalue>
. También podemos hacer esto en la clase padre, pero necesitaremos actualizar nuestro__init__
código para manejarlo.Finalmente, agregaremos atributos parametrizados. Debería ser bastante obvio cómo haremos esto también.
Aquí está el código tal como existe hasta ahora:
[1] Puedo estar atrasado sobre si este sigue siendo el caso.
fuente
return self.x or self.defaultX()
este es un código peligroso. Lo que sucede cuandoself.x == 0
?__getitem__
método. Sin embargo, sería extraño, ya que entonces tendría una pitón completamente no estándar.Creo que ambos tienen su lugar. Un problema con el uso
@property
es que es difícil extender el comportamiento de los captadores o establecedores en subclases utilizando mecanismos de clase estándar. El problema es que las funciones getter / setter reales están ocultas en la propiedad.De hecho, puede obtener las funciones, por ejemplo, con
puede acceder a las funciones getter y setter como
C.p.fget
yC.p.fset
, pero no puede usar fácilmente las funciones de herencia del método normal (por ejemplo, super) para extenderlas. Después de algo de investigación en las complejidades de súper, se puede utilizar de hecho súper de esta manera:Sin embargo, usar super () es bastante complicado, ya que la propiedad debe redefinirse, y debe usar el mecanismo super (cls, cls) ligeramente contraintuitivo para obtener una copia no vinculada de p.
fuente
Usar propiedades es para mí más intuitivo y se adapta mejor a la mayoría de los códigos.
Comparando
vs.
es bastante obvio para mí lo que es más fácil de leer. También las propiedades permiten variables privadas mucho más fáciles.
fuente
Preferiría no usar ninguno en la mayoría de los casos. El problema con las propiedades es que hacen que la clase sea menos transparente. Especialmente, este es un problema si se plantea una excepción de un setter. Por ejemplo, si tiene una propiedad Account.email:
entonces el usuario de la clase no espera que asignar un valor a la propiedad pueda causar una excepción:
Como resultado, la excepción puede pasar desapercibida y propagarse demasiado alto en la cadena de llamadas para ser manejada adecuadamente, o dar como resultado que se presente un rastreo muy inútil al usuario del programa (que lamentablemente es demasiado común en el mundo de Python y Java )
También evitaría usar getters y setters:
En lugar de propiedades y captadores / establecedores, prefiero hacer la lógica compleja en lugares bien definidos, como en un método de validación:
o un método similar de Account.save.
Tenga en cuenta que no estoy tratando de decir que no hay casos en que las propiedades sean útiles, solo que puede estar mejor si puede hacer que sus clases sean lo suficientemente simples y transparentes como para no necesitarlas.
fuente
validate()
método en una clase. Una propiedad simplemente se usa cuando tiene una lógica compleja detrás de unaobj.x = y
asignación simple , y depende de cuál sea la lógica.Siento que las propiedades se tratan de permitirle obtener la sobrecarga de escribir getters y setters solo cuando realmente los necesita.
La cultura de programación de Java recomienda encarecidamente nunca dar acceso a las propiedades y, en su lugar, pasar por getters y setters, y solo aquellos que realmente se necesitan. Es un poco detallado escribir siempre estas piezas de código obvias y observar que el 70% de las veces nunca son reemplazadas por alguna lógica no trivial.
En Python, las personas realmente se preocupan por ese tipo de sobrecarga, para que pueda adoptar la siguiente práctica:
@property
para implementarlos sin cambiar la sintaxis del resto de su código.fuente
Me sorprende que nadie haya mencionado que las propiedades son métodos vinculados de una clase de descriptor, Adam Donohue y NeilenMarais captan exactamente esta idea en sus publicaciones: que los captadores y los establecedores son funciones y pueden usarse para:
Esto presenta una forma inteligente de ocultar los detalles de implementación y el código cruft como expresiones regulares, tipos de conversión, try .. excepto bloques, aserciones o valores calculados.
En general, hacer CRUD en un objeto a menudo puede ser bastante mundano, pero considere el ejemplo de datos que se conservarán en una base de datos relacional. Los ORM pueden ocultar detalles de implementación de vernáculos SQL particulares en los métodos vinculados a fget, fset, fdel definidos en una clase de propiedad que administrará las terribles si .. elif .. otras escaleras que son tan feas en el código OO - exponiendo lo simple y elegante
self.variable = something
y obvia los detalles para el desarrollador que usa el ORM.Si uno piensa en las propiedades solo como un vestigio lúgubre de un lenguaje de Bondage y Disciplina (es decir, Java), no tiene en cuenta los descriptores.
fuente
En proyectos complejos, prefiero usar propiedades de solo lectura (o captadores) con función de establecimiento explícito:
En proyectos de larga duración, la depuración y refactorización lleva más tiempo que escribir el código en sí. Hay varias desventajas para usar
@property.setter
que hacen que la depuración sea aún más difícil:1) Python permite crear nuevos atributos para un objeto existente. Esto hace que el siguiente error de imprenta sea muy difícil de rastrear:
Si su objeto es un algoritmo complicado, pasará bastante tiempo tratando de descubrir por qué no converge (observe una 't' adicional en la línea de arriba)
2) setter a veces puede evolucionar a un método complicado y lento (por ejemplo, golpear una base de datos). Sería bastante difícil para otro desarrollador descubrir por qué la siguiente función es muy lenta. Podría pasar mucho tiempo en el
do_something()
método de creación de perfiles , mientrasmy_object.my_attr = 4.
que en realidad es la causa de la desaceleración:fuente
Tanto
@property
los getters como los setters tradicionales tienen sus ventajas. Depende de su caso de uso.Ventajas de
@property
No tiene que cambiar la interfaz mientras cambia la implementación del acceso a datos. Cuando su proyecto es pequeño, probablemente desee utilizar el acceso directo a los atributos para acceder a un miembro de la clase. Por ejemplo, supongamos que tiene un objeto
foo
de tipoFoo
, que tiene un miembronum
. Entonces simplemente puede obtener este miembro connum = foo.num
. A medida que crece su proyecto, puede sentir que debe haber algunas comprobaciones o depuraciones en el acceso de atributo simple. Entonces puedes hacerlo con un@property
dentro de la clase. La interfaz de acceso a datos sigue siendo la misma, por lo que no es necesario modificar el código del cliente.Citado de PEP-8 :
El uso
@property
para el acceso a datos en Python se considera Pythonic :Puede fortalecer su autoidentificación como programador de Python (no Java).
Puede ayudar a su entrevista de trabajo si su entrevistador cree que los captadores y establecedores de estilo Java son antipatrones .
Ventajas de los captadores y colocadores tradicionales
Los captadores y establecedores tradicionales permiten un acceso a datos más complicado que el simple acceso a atributos. Por ejemplo, cuando configura un miembro de la clase, a veces necesita un indicador que indique dónde desea forzar esta operación, incluso si algo no parece perfecto. Si bien no es obvio cómo aumentar el acceso directo de un miembro
foo.num = num
, puede aumentar fácilmente su setter tradicional con unforce
parámetro adicional :Los captadores y establecedores tradicionales hacen explícito que el acceso de un miembro de la clase es a través de un método. Esto significa:
Lo que obtienes como resultado puede no ser lo mismo que lo que se almacena exactamente dentro de esa clase.
Incluso si el acceso parece un simple acceso de atributo, el rendimiento puede variar mucho de eso.
A menos que los usuarios de su clase esperen
@property
esconderse detrás de cada declaración de acceso a atributos, hacer explícitas estas cosas puede ayudar a minimizar las sorpresas de los usuarios de su clase.Como mencionó @NeilenMarais y en esta publicación , ampliar los captadores y definidores tradicionales en subclases es más fácil que extender las propiedades.
Los captadores y establecedores tradicionales han sido ampliamente utilizados durante mucho tiempo en diferentes idiomas. Si tienes personas de diferentes orígenes en tu equipo, te resultarán más familiares
@property
. Además, a medida que su proyecto crezca, si necesita migrar de Python a otro idioma que no lo tenga@property
, el uso de captadores y establecedores tradicionales facilitaría la migración.Advertencias
Ni
@property
los captadores y definidores tradicionales hacen que el miembro de la clase sea privado, incluso si usa un guión bajo doble antes de su nombre:fuente
Aquí hay un extracto de "Python eficaz: 90 formas específicas de escribir mejor Python" (libro increíble. Lo recomiendo encarecidamente).
fuente