2009-10-12 10 views
61

Me parece que a excepción de un poco de azúcar sintáctico, la propiedad() no hace nada bien.Cuándo y cómo usar la propiedad de función incorporada() en python

Claro, es bueno ser capaz de escribir a.b=2 en lugar de a.setB(2), pero ocultando el hecho de que ab = 2 no es una tarea sencilla se parece a una receta para problemas, ya sea debido a algún resultado inesperado puede suceder, como a.b=2 en realidad causa que a.b sea 1. O una excepción se plantea. O un problema de rendimiento. O simplemente ser confuso

¿Puede darme un ejemplo concreto para un buen uso de la misma? (usarlo para parchear código problemático no cuenta ;-)

+11

Lo entendiste mal. .setB() es un parche para idiomas que no tiene propiedades reales. Python, C# y algunos otros tienen propiedades, y la solución es la misma. – liori

+4

He visto a chicos de Java algo peor, que es escribir getters y setters para cada atributo, por si acaso, porque es un esfuerzo refaccionarlo todo más tarde. –

+4

@gnibbler, en lenguajes que no admiten propiedades (o al menos posibilidades algo equivalentes como '__getattr__' de Python' '' '' '' '' '' '' '' '' 'heredadas''), no hay mucho más" tipos de Java "(en ninguno de esos idiomas, incluyendo C++! -) posiblemente, para preservar la encapsulación. –

Respuesta

113

En los lenguajes que se basan en getters y setters, como Java, no se supone ni se espera que hagan nada más que lo que dicen. Sería sorprendente que x.getB() hiciera algo más que devolver el valor actual del atributo lógico b, o si x.setB(2) hizo algo más que cualquier pequeña cantidad de trabajo interno que se necesita para hacer x.getB() return 2.

Sin embargo, no hay garantías lingüísticas impuestas acerca de este comportamiento esperado, es decir, las limitaciones del compilador-forzada en el cuerpo de métodos cuyos nombres comienzan con get o set: más bien, se deja hasta el sentido común, las convenciones sociales , "guías de estilo" y pruebas.

El comportamiento de x.b accesos y las asignaciones tales como x.b = 2, en los idiomas que sí tienen propiedades (un conjunto de idiomas que incluye pero no se limita a Python) es exactamente el mismo que para los métodos getter y setter en, por ejemplo, Java: las mismas expectativas, la misma falta de garantías impuestas por el lenguaje.

El primer triunfo de las propiedades es la sintaxis y la legibilidad. Tener que escribir, por ejemplo,

x.setB(x.getB() + 1) 

en lugar de lo obvio

x.b += 1 

clama venganza a los dioses. En los idiomas que admiten propiedades, no hay absolutamente ninguna buena razón para obligar a los usuarios de la clase a pasar por los giros de dicho modelo bizantino, lo que afecta la legibilidad de su código sin ninguna ventaja.

En Python específicamente, hay una ventaja adicional al usar propiedades (u otras descripciones) en lugar de getters y setters: si reorganiza su clase para que el setter y getter subyacentes ya no sean necesarios, puede (sin romper la API publicada de la clase) simplemente elimine esos métodos y la propiedad que se basa en ellos, haciendo que b sea un atributo "almacenado" normal de la clase x en lugar de uno "lógico" obtenido y establecido computacionalmente.

En Python, hacer cosas directamente (cuando sea factible) en lugar de mediante métodos es una optimización importante, y el uso sistemático de propiedades le permite realizar esta optimización siempre que sea posible (siempre exponiendo "atributos almacenados normales" directamente, y solo necesita cálculo en el acceso y/o configuración a través de métodos y propiedades).

Por lo tanto, si se utiliza captadores y definidores en lugar de propiedades, más allá de afectar la legibilidad del código de sus usuarios, que son también gratuitamente malgastar recursos de la máquina (y la energía que va a su equipo durante los ciclos ;-) , otra vez sin ningún motivo justificado.

Su único argumento en contra de las propiedades es, p. que "un usuario externo no esperaría ningún efecto secundario como resultado de una asignación, por lo general"; pero te pierdes el hecho de que el mismo usuario (en un lenguaje como Java donde getters y setters son omnipresentes) no esperaría (observables) "efectos secundarios" como resultado de llamar a un setter, tampoco (y aún menos por un getter) ;-). Son expectativas razonables y depende de usted, como autor de la clase, intentar acomodarlas, ya sea que su setter y getter se usen directamente o a través de una propiedad, no hace ninguna diferencia. Si tiene métodos con importantes efectos secundarios observables, haga no nómbrelos getThis, setThat, y no los utilice a través de propiedades.

La queja de que las propiedades "esconden la implementación" es totalmente injustificada: más todo de programación orientada a objetos es sobre la implementación de ocultación de información - haciendo una clase responsable de presentar una interfaz lógica con el mundo exterior e implementar internamente como mejor poder. Getters y setters, exactamente como propiedades, son herramientas para alcanzar este objetivo. Las propiedades solo hacen un mejor trabajo (en los idiomas que los admiten ;-).

+4

Además, echa un vistazo a la sobrecarga del operador de C++ . el mismo argumento "un usuario externo no esperaría ..." se aplica. Hay algunos casos en los que simplemente no desea que el usuario lo sepa. En esos casos, depende de usted asegurarse de no sorprender al usuario. –

+4

@Oren, buen punto, y la sobrecarga del operador de Python es muy parecida a la de C++ (la asignación no puede sobrecargarse ya que no es un operador, & c, pero el concepto general es similar). Depende de la cortesía, el discipulado y el sentido común del codificador evitar, p. '__add__' cambiarse a sí mismo y/o hacer algo que no tiene nada que ver con la suma, eso no significa que la sobrecarga del operador sea mala cuando se usa con buen gusto y sensatez (¡aunque los diseñadores de Java no están de acuerdo ya que deliberadamente lo dejaron fuera de su idioma! -). –

+0

"... puedes (sin romper la API publicada de la clase) simplemente eliminar esos métodos y la propiedad que se basa en ellos, haciendo b un atributo" almacenado "normal de la clase de x en lugar de uno" lógico "obtenido y establecido computacionalmente". - ??? No entiendo esto, entiendo que si cambia los atributos internos subyacentes que componen la propiedad, la "interfaz" pública proporcionada por la propiedad no tiene que cambiar, pero esto también es cierto para getters/setters. Ambas son solo una capa de abstracción por encima de un valor que debería ser interno al objeto. ¿Qué estás tratando de decir aquí? –

30

La idea es que te permita evitar tener que escribir getters y setters hasta que realmente los necesites.

Así que, para empezar a escribir:

class MyClass(object): 
    def __init__(self): 
     self.myval = 4 

Obviamente se puede escribir ahora myobj.myval = 5.

Pero más adelante, usted decide que necesita un colocador, ya que quiere hacer algo inteligente al mismo tiempo. Pero no desea tener que cambiar todo el código que usa su clase, por lo que ajusta el colocador en el decorador @property, y todo funciona.

+1

Su ejemplo es exactamente lo que quise decir con "usarlo para parchear código problemático" ... bueno, tal vez "problemático" no es la palabra correcta para usar aquí, pero es un parche de OMI. – olamundo

+0

Volví a pensar en @olamundo: con demasiada frecuencia he visto que las variables se han convertido en '@ property' para agregar algo de lógica en el setter/getter y no romper la compatibilidad. Esto intencionalmente da los efectos secundarios '@ property', y es confuso para los programadores futuros en el mismo código. –

5

Una razón básica es simplemente que se ve mejor. Es más pitónico. Especialmente para bibliotecas. something.getValue() parece menos agradable que something.value

En plone (un CMS bastante grande), solía tener document.setTitle() que hace muchas cosas como almacenar el valor, indexarlo de nuevo y así . Solo hacer document.title = 'algo' es mejor. Sabes que mucho está sucediendo detrás de escena de todos modos.

14

pero ocultando el hecho de que a.b = 2 no es una tarea sencilla parece una receta para problemas

No está ocultando el hecho de que; ese hecho nunca estuvo allí para comenzar. Esto es python, un lenguaje de alto nivel; no asamblea. Algunas de las afirmaciones "simples" se reducen a instrucciones de CPU únicas. Leer la simplicidad en una tarea es leer cosas que no están allí.

Cuando dices x.b = c, probablemente todo lo que debes pensar es que "lo que sea que acabe de suceder, x.b ahora debería ser c".

+0

Estoy de acuerdo con lo que dijo, pero todavía lo considero como "ocultar" el funcionamiento interno en el sentido de que un usuario externo no esperaría ningún efecto secundario como resultado de una tarea, por lo general. – olamundo

+5

Eso es verdadero noam. Por otro lado, OOP se trata de tener objetos que son cajas negras, a excepción de las interfaces que presentan, por lo que uno probablemente debería suponer que cosas mágicas y extrañas están sucediendo debajo de la superficie;) –

+2

ab = 2 es apenas menos de una tarea que x = 2. Si se implementan de manera diferente, que así sea, el objetivo de OOP es ocultar los detalles de implementación. Si a.b = 2 establece algo en 490, entonces es un desagradable efecto secundario que de todos modos no sería resuelto por a.setB (2). Si implícitamente piensas que ab = 2 debería ser una operación muy rápida cuando la ves, entonces debes darte cuenta de que estás en Python y que ya es al menos un orden de magnitud más lento que C. – Clueless

2

Aquí hay un viejo ejemplo mío. Envolví una biblioteca C que tenía funciones como "void dt_setcharge (int atom_handle, int new_charge)" y "int dt_getcharge (int atom_handle)". Quería en el nivel de Python hacer "atom.charge = atom.charge + 1".

El decorador "propiedad" lo hace fácil. Algo así como: hace

class Atom(object): 
    def __init__(self, handle): 
     self.handle = handle 
    def _get_charge(self): 
     return dt_getcharge(self.handle) 
    def _set_charge(self, charge): 
     dt_setcharge(self.handle, charge) 
    charge = property(_get_charge, _set_charge) 

10 años, cuando escribí este paquete, tuve que usar __getattr__ y __setattr__ que hizo posible, pero la aplicación era propenso mucho más error.

class Atom: 
    def __init__(self, handle): 
     self.handle = handle 
    def __getattr__(self, name): 
     if name == "charge": 
      return dt_getcharge(self.handle) 
     raise AttributeError(name) 
    def __setattr__(self, name, value): 
     if name == "charge": 
      dt_setcharge(self.handle, value) 
     else: 
      self.__dict__[name] = value 
3

Tiene usted razón, es simplemente azúcar sintáctica. Puede ser que no haya un buen uso de la misma dependiendo de su definición de código problemático.

Considere que tiene una clase Foo que se usa ampliamente en su aplicación. Ahora esta aplicación es bastante grande y además digamos que es una aplicación web que se ha vuelto muy popular.

Identifica que Foo está causando un cuello de botella. Quizás es posible agregar un poco de almacenamiento en caché a Foo para acelerarlo. El uso de propiedades le permitirá hacerlo sin cambiar ningún código o prueba fuera de Foo.

Sí, por supuesto, este es un código problemático, pero acabas de guardar una gran cantidad de $$ solucionándolo rápidamente.

¿Qué pasa si Foo está en una biblioteca para la que tiene cientos o miles de usuarios? Bueno, se ahorró tener que decirles que hagan un refactorio costoso cuando actualicen a la versión más nueva de Foo.

Las notas de la versión tienen un elemento de línea sobre Foo en lugar de una guía de portación de párrafo.

Los programadores experimentados de Python no esperan mucho de a.b=2 que no sea a.b==2, pero saben incluso que puede no ser cierto. Lo que sucede dentro de la clase es su propio negocio.

0

getters y setters son necesarios para muchos propósitos, y son muy útiles porque son transparentes para el código. Al tener el objeto Something como la altura de la propiedad, le asigna un valor como Something.height = 10, pero si height tiene un getter y setter, al momento de asignar ese valor puede hacer muchas cosas en los procedimientos, como validar un mínimo o máximo valor, como desencadenar un evento porque la altura cambió, estableciendo automáticamente otros valores en función del nuevo valor de altura, todo lo que puede ocurrir en el momento en que se asignó el valor Something.height. Recuerde, no necesita llamarlos en su código, se ejecutan automáticamente en el momento en que lee o escribe el valor de la propiedad. De alguna manera, son como procedimientos de eventos, cuando la propiedad X cambia de valor y cuando se lee el valor X de la propiedad.

Cuestiones relacionadas