![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Una vez utilizados algunos de los tipos internos de Python, estamos listos para crear un tipo definido por el usuario: el Punto.
Piense en el concepto de un punto matem�tico. En dos dimensiones, un punto es dos n�meros (coordenadas) que se tratan colectivamente como un solo objeto. En notaci�n matem�tica, los puntos suelen escribirse entre par�ntesis con una coma separando las coordenadas. Por ejemplo, (0, 0) representa el origen, y (x, y) representa el punto x unidades a la derecha e y unidades hacia arriba desde el origen.
Una forma natural de representar un punto en Python es con dos valores en coma flotante. La cuesti�n es, entonces, c�mo agrupar esos dos valores en un objeto compuesto. La soluci�n r�pida y burda es utilizar una lista o tupla, y para algunas aplicaciones esa podr�a ser la mejor opci�n.
Una alternativa es que el usuario defina un nuevo tipo compuesto, tambi�n llamado una clase. Esta aproximaci�n exige un poco m�s de esfuerzo, pero tiene sus ventajas que pronto se har�n evidentes.
Una definici�n de clase se parece a esto:
class Punto:
pass
Las definiciones de clase pueden aparecer en cualquier lugar de un programa, pero normalmente est�n al principio (tras las sentencias import). Las reglas sint�cticas de la definici�n de clases son las mismas que para cualesquiera otras sentencias compuestas. (ver la Secci�n ).
Esta definici�n crea una nueva clase llamada Punto. La sentencia pass no tiene efectos; s�lo es necesaria porque una sentencia compuesta debe tener algo en su cuerpo.
Al crear la clase Punto hemos creado un nuevo tipo, que tambi�n se llama Punto. Los miembros de este tipo se llaman instancias del tipo u objetos. La creaci�n de una nueva instancia se llama instanciaci�n. Para instanciar un objeto Punto ejecutamos una funci�n que se llama (lo ha adivinado) Punto:
blanco = Punto()
A la variable blanco se le asigna una referencia a un nuevo objeto Punto. A una funci�n como Punto que crea un objeto nuevo se le llama constructor.
Podemos a�adir nuevos datos a una instancia utilizando la notaci�n de punto:
>>> blanco.x = 3.0
>>> blanco.y = 4.0
Esta sintaxis es similar a la sintaxis para seleccionar una variable de un m�dulo, como math.pi o string.uppercase. En este caso, sin embargo, estamos seleccionando un dato de una instancia. Estos �temes con nombre se llaman atributos.
El diagrama de estados que sigue muestra el resultado de esas asignaciones:
La variable blanco apunta a un objeto Punto, que contiene dos atributos. Cada atributo apunta a un n�mero en coma flotante.
Podemos leer el valor de un atributo utilizando la misma sintaxis:
>>> print blanco.y
4.0
>>> x = blanco.x
>>> print x
3.0
La expresi�n blanco.x significa, "ve al objeto al que apunta blancoy toma el valor de x". En este caso, asignamos ese valor a una variable llamada x. No hay conflicto entre la variable x y el atributo x. El prop�sito de la notaci�n de punto es identificar de forma inequ�voca a qu� variable se refiere.
Puede usted usar la notaci�n de punto como parte de cualquier expresi�n. As�, las sentencias que siguen son correctas:
print '(' + str(blanco.x) + ', ' + str(blanco.y) + ')'
distanciaAlCuadrado = blanco.x * blanco.x + blanco.y * blanco.y
La primera l�nea presenta (3.0, 4.0); la segunda l�nea calcula el valor 25.0.
Puede tentarle imprimir el propio valor de blanco:
>>> print blanco
<__main__.Punto instance at 80f8e70>
El resultado indica que blanco es una instancia de la clase Punto que se defini� en __main__. 80f8e70 es el identificador �nico de este objeto, escrito en hexadecimal. Probablemente no es esta la manera m�s clara de mostrar un objeto Punto. En breve ver� c�mo cambiarlo.
Como ejercicio, cree e imprima un objeto Punto y luego use id para imprimir el identificador �nico del objeto. Traduzca el n�mero hexadecimal a decimal y aseg�rese de que coinciden.
Puede usted pasar una instancia como par�metro de la forma habitual. Por ejemplo:
def imprimePunto(p):
print '(' + str(p.x) + ', ' + str(p.y) + ')'
imprimePunto acepta un punto como argumento y lo muestra en formato est�ndar. Si llama a imprimePunto(blanco), el resultado es (3.0, 4.0).
Como ejercicio, reescriba la funci�n distancia de la Secci�n de forma que acepte dos Puntos como par�metros en lugar de cuatro n�meros.
El significado de la palabra "mismo" parece totalmente claro hasta que uno se para un poco a pensarlo, y entonces se da cuenta de que hay algo m�s de lo que supon�a.
Por ejemplo, si dice "Pepe y yo tenemos la misma moto", lo que quiere decir es que su moto y la de usted son de la misma marca y modelo, pero que son dos motos distintas. Si dice "Pepe y yo tenemos la misma madre", quiere decir que su madre y la de usted son la misma persona * Note. As� que la idea de "identidad" es diferente seg�n el contexto.
Cuando habla de objetos, hay una ambig�edad parecida. Por ejemplo, si dos Puntos son el mismo, �significa que contienen los mismos datos (coordenadas) o que son de verdad el mismo objeto?
Para averiguar si dos referencias se refieren al mismo objeto, utilice el operador ==. Por ejemplo:
>>> p1 = Punto()
>>> p1.x = 3
>>> p1.y = 4
>>> p2 = Punto()
>>> p2.x = 3
>>> p2.y = 4
>>> p1 == p2
0
Aunque p1 y p2 contienen las mismas coordenadas, no son el mismo objeto. Si asignamos p1 a p2, las dos variables son alias del mismo objeto:
>>> p2 = p1
>>> p1 == p2
1
Este tipo de igualdad se llama igualdad superficial porque s�lo compara las referencias, pero no el contenido de los objetos.
Para comparar los contenidos de los objetos (igualdad profunda) podemos escribir una funci�n llamada mismoPunto:
def mismoPunto(p1, p2) :
return (p1.x == p2.x) and (p1.y == p2.y)
Si ahora creamos dos objetos diferentes que contienen los mismos datos podremos 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 apuntan al mismo objeto mismoPuntodevuelve verdadero.
Digamos que queremos una clase que represente un rect�ngulo. La pregunta es, �qu� informaci�n tenemos que proporcionar para definir un rect�ngulo? Para simplificar las cosas, supongamos que el rect�ngulo est� orientado vertical u horizontalmente, nunca en diagonal.
Tenemos varias posibilidades: podemos se�alar el centro del rect�ngulo (dos coordenadas) y su tama�o (anchura y altura); o podemos se�alar una de las esquinas y el tama�o; o podemos se�alar dos esquinas opuestas. Un modo convencional es se�alar la esquina superior izquierda del rect�ngulo y el tama�o.
De nuevo, definiremos una nueva clase:
class Rectangulo: # Prohibidos los acentos fuera de las cadenas!
pass
Y la instanciaremos:
caja = Rectangulo()
caja.anchura = 100.0
caja.altura = 200.0
Este c�digo crea un nuevo objeto Rectangulo con dos atributos en coma flotante. �Para se�alar 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 punto compone. La expresi�n caja.esquina.x significa "ve al objeto al que se refiere caja y selecciona el atributo llamado esquina; entonces ve a ese objeto y selecciona el atributo llamado x".
La figura muestra el estado de este objeto:
Las funciones pueden devolver instancias. Por ejemplo, encuentraCentroacepta un Rectangulo como argumento y devuelve un Punto que contiene las coordenadas del centro del Rectangulo:
def encuentraCentro(caja):
p = Punto()
p.x = caja.esquina.x + caja.anchura/2.0
p.y = caja.esquina.y + caja.altura/2.0
return p
Para llamar a esta funci�n, pase caja como argumento y asigne el resultado a una variable:
>>> centro = encuentraCentro(caja)
>>> imprimePunto(centro)
(50.0, 100.0)
Podemos cambiar el estado de un objeto efectuando una asignaci�n sobre uno de sus atributos. Por ejemplo, para cambiar el tama�o de un rect�ngulo sin cambiar su posici�n, podemos cambiar los valores de anchura y altura:
caja.anchura = caja.anchura + 50
caja.altura = caja.altura + 100
Podemos encapsular este c�digo en un m�todo y generalizarlo para agrandar el rect�ngulo en cualquier cantidad:
def agrandaRect(caja, danchura, daltura) :
caja.anchura = caja.anchura + danchura
caja.altura = caja.altura + daltura
Las variables danchura y daltura indican cu�nto debe agrandarse el rect�ngulo en cada direcci�n. Invocar este m�todo tiene el efecto de modificar el Rectangulo que se pasa como argumento.
Por ejemplo, podemos crear un nuevo Rectangulo llamado boby pas�rselo a agrandaRect:
>>> bob = Rectangulo()
>>> bob.anchura = 100.0
>>> bob.altura = 200.0
>>> bob.esquina = Punto()
>>> bob.esquina.x = 0.0;
>>> bob.esquina.y = 0.0;
>>> agrandaRect(bob, 50, 100)
Mientras agrandaRect se est� ejecutando, el par�metro caja es un alias de bob. Cualquier cambio que haga a caja afectar� tambi�n a bob.
A modo de ejercicio, escriba una funci�n llamada mueveRect que tome un Rectangulo y dos par�metros llamados dx y dy. Tiene que cambiar la posici�n del rect�ngulo a�adiendo dx a la coordenada x de esquina y a�adiendo dy a la coordenada y de esquina.
El uso de alias puede hacer que un programa sea dif�cil de leer, porque los cambios hechos en un lugar pueden tener efectos inesperados en otro lugar. Es dif�cil estar al tanto de todas las variables a las que puede apuntar un objeto dado.
Copiar un objeto es, muchas veces, una alternativa a la creaci�n de un 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 hemos importado el m�dulo copy, podemos usar el m�todo copypara hacer un nuevo Punto. p1 y p2 no son el mismo punto, pero contienen los mismos datos.
Para copiar un objeto simple como un Punto, que no contiene objetos incrustados, copy es suficiente. Esto se llama copiado superficial.
Para algo como un Rectangulo, que contiene una referencia a un Punto, copy no lo hace del todo bien. Copia la referencia al objeto Punto, de modo que tanto el Rectangulo viejo como el nuevo apuntan a un �nico Punto.
Si creamos una caja, b1, de la forma habitual y entonces hacemos una copia, b2, usando copy, el diagrama de estados resultante se ve as�:
Es casi seguro que esto no es lo que queremos. En este caso, la invocaci�n de agrandaRect sobre uno de los Rectangulos no afectar�a al otro, �pero la invocaci�n de mueveRect sobre cualquiera afectaria a ambos! Este comportamiento es confuso y propicia los errores.
Afortunadamente, el m�dulo copy contiene un m�todo llamado deepcopy que copia no s�lo el objeto sino tambi�n cualesquiera objetos incrustados. No le sorprender� saber que esta operaci�n se llama copia profunda (deep copy).
>>> b2 = copy.deepcopy(b1)
Ahora b1 y b2 son objetos totalmente independientes.
Podemos usar deepcopy para reescribir agrandaRect de modo que en lugar de modificar un Rectangulo existente, cree un nuevo Rectangulo que tiene la misma localizaci�n que el viejo pero nuevas dimensiones:
def agrandaRect(caja, danchura, daltura) :
import copy
nuevaCaja = copy.deepcopy(caja)
nuevaCaja.anchura = nuevaCaja.anchura + danchura
nuevaCaja.altura = nuevaCaja.altura + daltura
return nuevaCaja
Como ejercicio, resscriba mueveRect de modo que cree y devuelva un nuevo Rectangulo en lugar de modificar el viejo.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |