![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Después de haber usado algunos de los tipos incorporados de Python, estamos preparados para crear un tipo definido por el usuario: el Punto.
Considera el concepto de un punto matemático. Un punto son dos números (coordenadas) que son considerados conjuntamente como un único objeto en dos dimensiones. En notación matemática, los puntos se escriben a menudo entre paréntesis y con coma para separar las coordenadas. Por ejemplo, (0, 0) representa el origen y (x, y) representa el punto situado x unidades a la derecha e y unidades hacia arriba desde el origen.
Un modo normal de representar un punto en Python es a través de dos valores con coma decimal. El problema está, entonces, en cómo agrupar estos dos valores en un objeto compuesto. Una solución rápida y burda sería usar una lista o tupla, que podría ser la mejor opción para algunas aplicaciones.
Una alternativa puede ser establecer un nuevo tipo compuesto definido por el usuario, también llamado clase. Este procedimiento requiere un poco más de esfuerzo, pero cuenta con ventajas que pronto se harán evidentes.
Una definición de clase tiene este aspecto:
class Punto:
pass
Las definiciones de clase pueden aparecer en cualquier parte de un programa, pero normalmente se encuentran cerca del inicio (detrás de las sentencias import). Las reglas sintácticas para una definición de clase son las mismas que para otras sentencias compuestas (ver Sección 4.4).
Esta definición crea una nueva clase llamada Punto. La sentencia pass no tienen ningún efecto; solo es necesaria porque una sentencia compuesta debe tener algo en su cuerpo.
Al crear la clase Punto, hemos creado un tipo nuevo, también llamado Punto. A los miembros de este tipo se les llama instancias del tipo u objetos. A la creación de una nueva instancia se le llama instanciar. Para instanciar un objeto Punto, llamamos a una función denominada (lo has adivinado) Punto:
espacio = Punto()
A la variable espacio se le asigna una referencia un objeto Punto nuevo. A una función como Punto que crea objetos nuevos se le llama constructor.
Comentarios
Podemos añadir nuevos datos a una instancia usando la notación de punto:
>>> espacio.x = 3.0
>>> espacio.y = 4.0
La sintaxis es similar a la que se emplea para seleccionar una variable de un módulo, como math.pi o string.uppercase. Aunque en este caso estamos seleccionando una unidad de datos de una instancia. A estas unidades se les llama atributos.
El siguiente diagrama de estado muestra el resultado de estas asignaciones:

La variable espacio se refiere a un objeto Punto, que contiene dos atributos. Cada atributo se refiere a un número decimal.
Podemos leer el valor de un atributo usando la misma sintaxis:
>>> print espacio.y
4.0
>>> x = espacio.x
>>> print x
3.0
La expresión espacio.x significa, "Ve hasta el objeto al que se refiere espacio y toma el valor de x". En este caso, asignamos ese valor a una variable llamada x. No hay conflictos entre la variable x y el atributo x. El propósito de la notación de punto es identificar la variable a la que te refieres sin que haya ambigüedad.
Puedes usar la notación de punto como parte de cualquier expresión, de modo que las siguientes sentencias son correctas:
print '(' + str(espacio.x) + ', ' + str(espacio.y) + ')'
distanciaAlcuadrado = espacio.x * espacio.x + espacio.y * espacio.y
La primera línea da como resultado (3.0, 4.0); la segunda línea calcula el valor 25.0.
Podrías tener la tentación de mostrar en pantalla el propio valor de espacio:
>>> print espacio
<__main__.Punto instance at 80f8e70>
El resultado indica que espacio es una instancia de la clase Punto y está definida en __main__. 80f8e70 es el identificador único para este objeto, escrito en hexadecimal (base 16). Probablemente esta no es la manera más informativa de mostrar un objeto Punto. Podrás ver cómo cambiarla dentro de poco.
A modo de ejercicio, crea y muestra en pantalla un objetoPunto, y luego usa id para mostrar el identificador único del objeto. Convierte la forma hexadecimal en forma decimal y confirma que coinciden.Comentarios
Puedes usar una instancia como un parámetro como hemos hecho hasta ahora. Por ejemplo:
def printPunto(p):
print '(' + str(p.x) + ', ' + str(p.y) + ')'
printPunto toma un punto como argumento y lo muestra en formato estándar. Si llamas a printPunto(espacio), el resultado es (3.0, 4.0).
A modo de ejercicio, reescribe la función distancia de la Sección 5.2 que te dará dos Puntos como parámetros, en vez de cuatro números.Comentarios
El significado de la palabra "mismo" parece perfectamente claro hasta que piensas un poco en ello, y entonces te das cuenta de que significa más de lo que esperabas.
Por ejemplo, si tu dices, "Cristina y yo tenemos el mismo coche", puedes pensar que ambos tenemos el mismo modelo y la misma marca, pero son dos coches diferentes. Si dices "Cristina y yo tenemos la misma madre", puedes pensar que la madre de ambos es la misma persona. * Nota La idea de "mismo" es diferente dependiendo del contexto.
Cuando hablas sobre objetos, existe una ambigüedad similar. Por ejemplo, si dos Puntos son el mismo, ¿quiere decir esto que contienen la misma información (coordenadas) o que son realmente el mismo objeto?
Para averiguar si dos referencias hacen alusión al mismo objeto, usa el operador ==. Por ejemplo:
>>> p1 = Punto()
>>> p1.x = 3
>>> p1.y = 4
>>> p2 = Punto()
>>> p2.x = 3
>>> p2.y = 4
>>> p1 == p2
0
Incluso si p1 y p2 contienen las mismas coordenadas, no son el mismo objeto. Si asignamos p1 a p2, entonces las dos variables son alias del mismo objeto:
>>> p2 = p1
>>> p1 == p2
1
Este tipo de igualdad se llama igualdad superficial porque compara solo las referencias, y no, el contenido de los objetos.
Para comparar el contenido de los objetos igualdad profunda podemos escribir una función llamada mismoPunto:
def mismoPunto(p1, p2) :
return (p1.x == p2.x) y (p1.y == p2.y)
Si creamos ahora dos objetos diferentes que contengan la misma información, podemos usar mismoPunto para averiguar si representan el mismo punto.
>>> p1 = Punto()
>>> p1.x = 3
>>> p1.y = 4
>>> p2 = Punto()
>>> p2.x = 3
>>> p2.y = 4
>>> mismoPunto(p1, p2)
1
Por supuesto, si las dos variables se refieren al mismo objeto, tienen tanto igualdad superficial como profunda.
Comentarios
Pongamos que necesitamos una clase para representar un rectángulo. El problema es, ¿qué información necesitamos para especificar un rectángulo? Para tener las cosas claras, supón que el rectángulo está orientado vertical u horizontalmente, pero nunca inclinado.
Hay varias posibilidades: Podemos especificar el centro del rectángulo (dos coordenadas) y su tamaño (ancho y alto), o una de las esquinas y su tamaño, o dos esquinas opuestas. Una opción convencional es especificar la esquina superior izquierda del rectángulo y su tamaño.
De nuevo, definiremos una nueva clase:
class Rectangulo:
pass
Y la instanciaremos:
caja = Rectangulo()
caja.ancho = 100.0
caja.alto = 200.0
Este código crea un nuevo objeto Rectangulo con dos atributos con decimales. Para especificar la esquina superior izquierda, podemos incrustar un objeto dentro de otro.
caja.esquina = Punto()
caja.esquina.x = 0.0;
caja.esquina.y = 0.0;
El operador de punto compone. La expresión caja.esquina.x significa, "Ve hasta el objeto al que se refiere caja y selecciona el atributo llamado esquina; luego ve hasta el objeto y selecciona el atributo llamado x."
El gráfico muestra el estado de este objeto:

Las funciones pueden devolver instancias. Por ejemplo, encontrarCentro toma un Rectangulo como argumento y devuelve un Punto que contiene las coordenadas del centro del Rectangulo:
def encontrarCentro(caja):
p = Punto()
p.x = caja.esquina.x + caja.ancho/2.0
p.y = caja.esquina.y + caja.alto/2.0
return p
Para llamar a esta función, usa caja como argumento y asigna el resultado a una variable:
>>> centro = encontrarCentro(caja)
>>> printPunto(centro)
(50.0, 100.0)
Podemos cambiar el estado de un objeto realizando una asignación a uno de sus atributos. Por ejemplo, para cambiar el tamaño de un rectángulo sin cambiar su posición, podemos modificar los valores de ancho y alto:
caja.ancho = caja.ancho + 50
caja.alto = caja.alto + 100
Podríamos encapsular este código en un método y generalizarlo para que el rectángulo en cualquier cantidad:
def aumentarRect(caja, dancho, dalto):
caja.ancho = caja.ancho + dancho
caja.alto = caja.alto + dalto
Las variables dancho y dalto indican cuánto debería crecer el rectángulo en cada dirección. Este método provoca la modificación del Rectangulo que se usa como argumento.
Por ejemplo, podríamos crear un nuevo Rectangulo llamado pepe y usarlo con aumentarRect:
>>> pepe = Rectangulo()
>>> pepe.ancho = 100.0
>>> pepe.alto = 200.0
>>> pepe.esquina = Punto()
>>> pepe.esquina.x = 0.0;
>>> pepe.esquina.y = 0.0;
>>> aumentarRect(pepe, 50, 100)
Mientras aumentarRect se ejecuta, el parámetro caja es un alias de pepe. Los cambios que se hagan a caja también afectarán a pepe.
A modo de ejercicio, escribe una función llamada moverRect que tome un Rectangulo y dos parámetros llamados dx y dy. La posición del rectángulo debería cambiar al añadir dx a la coordenada x de esquina y al añadir dy a la coordenada y de esquina.Comentarios
Emplear un alias puede hacer que el programa sea difícil de leer, ya que los cambios realizados en una parte podrían tener efectos inesperados en otra. Es complicado llevar la cuenta de todas las variables que puedan referirse a un objeto dado.
Copiar un objeto es, a menudo, una alternativa a los alias. El módulo copy contiene una función llamada copy que puede duplicar cualquier objeto:
>>> import copy
>>> p1 = Punto()
>>> p1.x = 3
>>> p1.y = 4
>>> p2 = copy.copy(p1)
>>> p1 == p2
0
>>> mismoPunto(p1, p2)
1
Una vez que hayamos importado el módulo copy, podemos usar el método copy para crear un nuevo Punto. p1 y p2 no son el mismo punto, pero contienen la misma información.
Para copiar un objeto simple como un Punto, que no contiene objetos incrustados, basta con emplear copy. Esto es lo que se llama copia superficial.
Para un Rectangulo que contenga una referencia a un Punto, copy no resulta tan efectivo. Copia la referencia al objeto Punto, de modo que tanto el antiguo Rectangulo como el nuevo hagan referencia a un solo Punto.
Si creamos una caja, b1, como hemos hecho hasta el momento y luego hacemos una copia, b2, usando copy, el diagrama de estado resultante queda así:

Esto no es, casi con seguridad, lo que nosotros queríamos. En este caso, emplear aumentarRect en uno de los Rectangulos no afectaría al otro, pero moverRect en uno afectaría a ambos, ya que nos llevaría a errores.
Afortunadamente, el módulo copy contiene un método llamado deepcopy, que copia no solo el objeto sino también los objetos incrustados No te sorprenderá saber que esta operación se llama copia en profundidad.
>>> b2 = copy.deepcopy(b1)
Ahora b1 y b2 ya son dos objetos totalmente diferentes.
Podemos usar deepcopy para reescribir aumentarRect, de modo que, en lugar de modificar un Rectangulo ya existente, cree un nuevo Rectangulo que tenga la misma posición que el anterior pero con nuevas dimensiones:
def aumentarRect(caja, dancho, dalto):
import copy
nuevaCaja= copy.deepcopy(caja)
nuevaCaja.ancho = nuevaCaja.ancho + dancho
nuevaCaja.alto = nuevaCaja.alto + dalto
return nuevaCaja
A modo de ejercicio, vuelve a escribir moverRect de modo que cree y devuelva un nuevo Rectangulo en lugar de modificar el antiguo.Comentarios
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |