CURSO PYTHON CAPITULO 6
EL ENFOQUE A OBJETOS
Cada clase es como una receta que se puede usar cuando quieres crear un objeto útil.
Puedes producir tantos objetos como necesites para resolver tu problema.
Cada objeto tiene un conjunto de rasgos (se denominan propiedades o atributos; usaremos ambas
palabras como sinónimos) y es capaz de realizar un conjunto de actividades (que se denominan
métodos).
Las recetas pueden modificarse si son inadecuadas para fines específicos y, en efecto, pueden
crearse nuevas clases. Estas nuevas clases heredan propiedades y métodos de los originales, y
generalmente agregan algunos nuevos, creando nuevas herramientas más específicas.
Jerarquías de clase
La palabra clases tiene muchos significados, pero no todos son compatibles con las ideas que
queremos discutir aquí. La clase que nos concierne es como una categoría, como resultado de
similitudes definidas con precisión.
Intentaremos señalar algunas clases que son buenos ejemplos de este concepto.
Veamos por un momento los vehículos. Todos los vehículos existentes (y los que aún no existen)
estan relacionados por una sola característica importante: la capacidad de moverse. Puedes
argumentar que un perro también se mueve; ¿Es un perro un vehículo? No lo es. Tenemos que
mejorar la definición, es decir, enriquecerla con otros criterios, distinguir los vehículos de otros seres
y crear una conexión más fuerte. Consideremos las siguientes circunstancias: los vehículos son
entidades creadas artificialmente que se utilizan para el transporte, movidos por fuerzas de la
naturaleza y dirigidos (conducidos) por humanos.
Según esta definición, un perro no es un vehículo.
La clase vehículos es muy amplia. Tenemos que definir clases especializadas. Las clases
especializadas son las subclases. La clase vehículos será una superclase para todas ellas.
Hemos identificado las siguientes subclases:
Mamíferos salvajes.
Mamíferos domesticados.
Intenta extender la jerarquía de la forma que quieras y encuentra el lugar adecuado para los
humanos.
¿Qué es un objeto?
Una clase (entre otras definiciones) es un conjunto de objetos. Un objeto es un ser perteneciente
a una clase.
Un objeto es una encarnación de los requisitos, rasgos y cualidades asignados a una clase
específica. Esto puede sonar simple, pero ten en cuenta las siguientes circunstancias importantes.
Las clases forman una jerarquía. Esto puede significar que un objeto que pertenece a una clase
específica pertenece a todas las superclases al mismo tiempo. También puede significar que
cualquier objeto perteneciente a una superclase puede no pertenecer a ninguna de sus subclases.
Por ejemplo: cualquier automóvil personal es un objeto que pertenece a la clase vehículos
terrestres. También significa que el mismo automóvil pertenece a todas las superclases de su clase
local; por lo tanto, también es miembro de la clase vehículos. Tu perro (o tu gato) es un objeto
incluido en la clase Mamíferos domesticados, lo que significa explícitamente que también está
incluido en la clase animales.
Cada subclase es más especializada (o más específica) que su superclase. Por el contrario,
cada superclase es más general (más abstracta) que cualquiera de sus subclases. Ten en cuenta
que hemos supuesto que una clase solo puede tener una superclase; esto no siempre es cierto,
pero discutiremos este tema más adelante.
Herencia
Definamos uno de los conceptos fundamentales de la programación de objetos, llamado herencia.
Cualquier objeto vinculado a un nivel específico de una jerarquía de clases hereda todos los
rasgos (así como los requisitos y cualidades) definidos dentro de cualquiera de las
superclases.
La clase de inicio del objeto puede definir nuevos rasgos (así como requisitos y cualidades) que
serán heredados por cualquiera de sus superclases.
No deberías tener ningún problema para hacer coincidir esta regla con ejemplos específicos, ya sea
que se aplique a animales o vehículos.
¿Qué contiene un objeto?
La programación orientada a objetos supone que cada objeto existente puede estar equipado
con tres grupos de atributos:
Un objeto tiene un nombre que lo identifica de forma exclusiva dentro de su namespace
(aunque también puede haber algunos objetos anónimos).
Un objeto tiene un conjunto de propiedades individuales que lo hacen original, único o
sobresaliente (aunque es posible que algunos objetos no tengan propiedades).
Un objeto tiene un conjunto de habilidades para realizar actividades específicas, capaz
de cambiar el objeto en sí, o algunos de los otros objetos.
Hay una pista (aunque esto no siempre funciona) que te puede ayudar a identificar cualquiera de las
tres esferas anteriores. Cada vez que se describe un objeto y se usa:
Un sustantivo: probablemente se este definiendo el nombre del objeto.
Un adjetivo: probablemente se este definiendo una propiedad del objeto.
Un verbo: probablemente se este definiendo una actividad del objeto.
Dos ejemplos deberían servir como un buen ejemplo:
Max es un gato grande que duerme todo el día.
Nombre del objeto = Max
Clase de inicio = Gato
Propiedad = Tamaño (grande)
Actividad = Dormir (todo el día)
Un Cadillac rosa pasó rápidamente.
Nombre del objeto = Cadillac
Clase de inicio = Vehículo terrestre
Propiedad = Color (rosa)
Actividad = Pasar (rápidamente)
No hay obstáculo para definir nuevas subclases más precisas. Heredarán todo de su superclase,
por lo que el trabajo que se utilizó para su creación no se desperdicia.
La nueva clase puede agregar nuevas propiedades y nuevas actividades y, por lo tanto, puede ser
más útil en aplicaciones específicas. Obviamente, se puede usar como una superclase para
cualquier número de subclases recién creadas.
El proceso no necesita tener un final. Puedes crear tantas clases como necesites.
La clase que se define no tiene nada que ver con el objeto: la existencia de una clase no significa
que ninguno de los objetos compatibles se creará automáticamente. La clase en sí misma no
puede crear un objeto: debes crearlo tu mismo y Python te permite hacerlo.
Es hora de definir la clase más simple y crear un objeto. Echa un vistazo al siguiente ejemplo:
class ClaseSimple:
pass
Hemos definido una clase. La clase es bastante pobre: no contiene propiedades ni actividades.
Esta vacía, pero eso no importa por ahora. Cuanto más simple sea la clase, mejor para nuestros
propósitos.
La definición comienza con la palabra clave reservada class . La palabra clave reservada es
seguida por un identificador que nombrará la clase (nota: no lo confundas con el nombre del
objeto: estas son dos cosas diferentes).
A continuación, se agregan dos puntos:), como clases, como funciones, forman su propio bloque
anidado. El contenido dentro del bloque define todas las propiedades y actividades de la clase.
La palabra clave reservada pass llena la clase con nada. No contiene ningún método ni
propiedades.
Tu primer objeto
La clase recién definida se convierte en una herramienta que puede crear nuevos objetos. La
herramienta debe usarse explícitamente, bajo demanda.
Imagina que deseas crear un objeto (exactamente uno) de la clase ClaseSimple .
Para hacer esto, debes asignar una variable para almacenar el objeto recién creado de esa clase y
crear un objeto al mismo tiempo.
Se hace de la siguiente manera:
miPrimerObjeto = ClaseSimple()
Nota:
El nombre de la clase intenta fingir que es una función, ¿puedes ver esto? Lo discutiremos
pronto.
El objeto recién creado está equipado con todo lo que trae la clase; Como esta clase está
completamente vacía, el objeto también está vacío.
El acto de crear un objeto de la clase seleccionada también se llama instanciación (ya que el objeto se
convierte en una instancia de la clase).
¿Qué es una pila?
Una pila es una estructura desarrollada para almacenar datos de una manera muy
específica.. Imagina una pila de monedas. No puedes poner una moneda en ningún otro lugar sino
en la parte superior de la pila. Del mismo modo, no puedes sacar una moneda de la pila desde
ningún lugar que no sea la parte superior de la pila. Si deseas obtener la moneda que se encuentra
en la parte inferior, debes eliminar todas las monedas de los niveles superiores.
El nombre alternativo para una pila (pero solo en la terminología de TI) es UEPS (LIFO son sus
siglas en íngles). Es una abreviatura para una descripción muy clara del comportamiento de la
pila: Último en Entrar - Primero en Salir (Last In - First Out). La moneda que quedó en último
lugar en la pila saldrá primero.
Una pila es un objeto con dos operaciones elementales, denominadas
convencionalmente push (cuando un nuevo elemento se coloca en la parte superior) y pop (cuando
un elemento existente se retira de la parte superior).
Las pilas se usan muy a menudo en muchos algoritmos clásicos, y es difícil imaginar la
implementación de muchas herramientas ampliamente utilizadas sin el uso de pilas.
Implementemos una pila en Python. Esta será una pila muy simple, y te mostraremos cómo hacerlo
en dos enfoques independientes: de manera procedimental y orientado a objetos.
Comencemos con el primero.
La pila: el enfoque procedimental
Primero, debes decidir cómo almacenar los valores que llegarán a la pila. Sugerimos utilizar el
método más simple, y emplear una lista para esta tarea. Supongamos que el tamaño de la pila no
está limitado de ninguna manera. Supongamos también que el último elemento de la lista almacena
el elemento superior.
La pila en sí ya está creada:
pila = []
Estamos listos para definir una función que pone un valor en la pila. Aquí están las
presuposiciones para ello:
El nombre para la función es push .
La función obtiene un parámetro (este es el valor que se debe colocar en la pila).
La función no devuelve nada.
La función agrega el valor del parámetro al final de la pila.
Así es como lo hemos hecho, echa un vistazo:
def push(val):
pila.append(val)
Ahora es tiempo de que una función quite un valor de la pila. Así es como puedes hacerlo:
El nombre de la función es pop.
La función no obtiene ningún parámetro.
La función devuelve el valor tomado de la pila.
La función lee el valor de la parte superior de la pila y lo elimina.
La función esta aqui:
def pop():
val = pila[-1]
del pila[-1]
return val
Nota: la función no verifica si hay algún elemento en la pila.
Armemos todas las piezas juntas para poner la pila en movimiento. El programa completo empuja
(push) tres números a la pila, los saca e imprime sus valores en pantalla. Puedes verlo en la
ventana del editor
pila = []
def push(val):
pila.append(val)
def pop():
val = pila[-1]
del pila[-1]
return val
push(3)
push(2)
push(1)
print(pop())
print(pop())
print(pop())
El programa muestra el siguiente texto en pantalla:
1
2
3
Pruébalo.
La pila: el enfoque procedimental frente al enfoque orientado
a objetos
La pila procedimental está lista. Por supuesto, hay algunas debilidades, y la implementación podría
mejorarse de muchas maneras (aprovechar las excepciones es una buena idea), pero en general la
pila está completamente implementada, y puedes usarla si lo necesitas.
Pero cuanto más la uses, más desventajas encontrarás. Éstas son algunas de ellas:
La variable esencial (la lista de la pila) es altamente vulnerable; cualquiera puede
modificarla de forma incontrolable, destruyendo la pila; esto no significa que se haya hecho
de manera maliciosa; por el contrario, puede ocurrir como resultado de un descuido, por
ejemplo, cuando alguien confunde nombres de variables; imagina que accidentalmente has
escrito algo como esto:
pila[0] = 0
El funcionamiento de la pila estará completamente desorganizado.
También puede suceder que un día necesites más de una pila; tendrás que crear otra lista
para el almacenamiento de la pila, y probablemente otras funciones push y pop .
También puede suceder que no solo necesites funciones push y pop , pero también algunas
otras funciones; ciertamente podrías implementarlas, pero intenta imaginar qué sucedería si
tuvieras docenas de pilas implementadas por separado.
El enfoque orientado a objetos ofrece soluciones para cada uno de los problemas anteriores.
Vamos a nombrarlos primero:
La capacidad de ocultar (proteger) los valores seleccionados contra el acceso no autorizado
se llama encapsulamiento; no se puede acceder a los valores encapsulados ni
modificarlos si deseas utilizarlos exclusivamente.
Cuando tienes una clase que implementa todos los comportamientos de pila necesarios,
puedes producir tantas pilas como desees; no necesitas copiar ni replicar ninguna parte del
código.
La capacidad de enriquecer la pila con nuevas funciones proviene de la herencia; puedes
crear una nueva clase (una subclase) que herede todos los rasgos existentes de la
superclase y agregue algunos nuevos.
Ahora escribamos una nueva implementación de pila desde cero. Esta vez, utilizaremos el enfoque
orientado a objetos, que te guiará paso a paso en el mundo de la programación de objetos.
La pila - el enfoque orientado a objetos
Por supuesto, la idea principal sigue siendo la misma. Usaremos una lista como almacenamiento de
la pila. Solo tenemos que saber cómo poner la lista en la clase.
Comencemos desde el principio: así es como comienza la pila de orientada a objetos:
class Pila:
Ahora, esperamos dos cosas de la clase:
Queremos que la clase tenga una propiedad como el almacenamiento de la pila -
tenemos que "instalar" una lista dentro de cada objeto de la clase (nota: cada objeto
debe tener su propia lista; la lista no debe compartirse entre diferentes pilas).
Despues, queremos que la lista esté oculta de la vista de los usuarios de la clase.
¿Cómo se hace esto?
A diferencia de otros lenguajes de programación, Python no tiene medios para permitirte declarar
una propiedad como esa.
En su lugar, debes agregar una instrucción específica. Las propiedades deben agregarse a la clase
manualmente.
¿Cómo garantizar que dicha actividad tiene lugar cada vez que se crea una nueva pila?
Hay una manera simple de hacerlo - tienes que equipar a la clase con una función específica:
Tiene que ser nombrada de forma estricta.
Se invoca implícitamente cuando se crea el nuevo objeto.
Tal función es llamada el constructor, ya que su propósito general es construir un nuevo objeto.
El constructor debe saber todo acerca de la estructura del objeto y debe realizar todas las
inicializaciones necesarias.
Agreguemos un constructor muy simple a la nueva clase. Echa un vistazo al código:
class Pila:
def __init__(self):
print("¡Hola!")
objetoPila = Pila()
Expliquemos más a detalle:
El nombre del constructor es siempre __init__ .
Tiene que tener al menos un parámetro (discutiremos esto más tarde); el parámetro se usa
para representar el objeto recién creado: puedes usar el parámetro para manipular el objeto
y enriquecerlo con las propiedades necesarias; harás uso de esto pronto.
Nota: el parámetro obligatorio generalmente se denomina self - es solo una sugerencía,
pero deberías seguirla - simplifica el proceso de lectura y comprensión de tu código.
El código está en el editor. Ejecútalo ahora.
Aquí está su salida:
¡Hola!
Nota: no hay rastro de la invocación del constructor dentro del código. Ha sido invocado implícita y
automáticamente. Hagamos uso de eso ahora.
class Pila: # define la clase Pila
def __init__(self): # define la función del constructor
print("¡Hola!")
objetoPila = Pila() # instanciando el objeto
La pila - el enfoque orientado a objetos: continuación
Cualquier cambio que realices dentro del constructor que modifique el estado del
parámetro self se verá reflejado en el objeto recien creado.
Esto significa que puedes agregar cualquier propiedad al objeto y la propiedad permanecerá allí
hasta que el objeto termine su vida o la propiedad se elimine explícitamente.
Ahora agreguemos solo una propiedad al nuevo objeto - una lista para la pila. La
nombraremos listaPila .
Justo como aqui:
class Pila:
def __init__(self):
self.listaPila = []
objetoPila = Pila()
print(len(objetoPila.listaPila))
Nota:
Hemos usado la notación punteada, al igual que cuando se invocan métodos. Esta es la
manera general para acceder a las propiedades de un objeto: debes nombrar el objeto,
poner un punto ( . ) después de el, y especificar el nombre de la propiedad deseada, ¡no
uses paréntesis! No deseas invocar un método, deseas acceder a una propiedad.
Si estableces el valor de una propiedad por primera vez (como en el constructor), lo estás
creando; a partir de ese momento, el objeto tiene la propiedad y está listo para usar su
valor.
Hemos hecho algo más en el código: hemos intentado acceder a la
propiedad listaPila desde fuera de la clase inmediatamente después de que se haya
creado el objeto; queremos verificar la longitud actual de la pila, ¿lo hemos logrado?
Sí, por supuesto: el código produce el siguiente resultado:
Esto no es lo que queremos de la pila. Nosotros queremos que listaPila este escondida del
mundo exterior. ¿Es eso posible?
Sí, y es simple, pero no muy intuitivo.
La pila - el enfoque orientado a objetos: continuación
Echa un vistazo: hemos agregado dos guiones bajos antes del nombre listaPila - nada mas:
class Pila:
def __init__(self):
self.__listaPila = []
objetoPila = Pila()
print(len(objetoPila.__listaPila))
El cambio invalida el programa..
¿Por qué?
Cuando cualquier componente de la clase tiene un nombre que comienza con dos guiones bajos
( __ ), se vuelve privado - esto significa que solo se puede acceder desde la clase.
No puedes verlo desde el mundo exterior. Así es como Python implementa el concepto
de encapsulación.
Ejecuta el programa para probar nuestras suposiciones: una excepción AttributeError debe ser
lanzada.
El enfoque orientado a objetos: una pila desde cero
Ahora es el momento de que las dos funciones (métodos) implementen las operaciones push y pop.
Python supone que una función de este tipo debería estar inmersa dentro del cuerpo de la clase -
como el constructor.
Queremos invocar estas funciones para agregar (push) y quitar (pop) valores de la pila. Esto
significa que ambos deben ser accesibles para el usuario de la clase (en contraste con la lista
previamente construida, que está oculta para los usuarios de la clase ordinaria).
Tal componente es llamado publico, por ello no puede comenzar su nombre con dos (o más)
guiones bajos. Hay un requisito más - el nombre no debe tener más de un guión bajo.
Las funciones en sí son simples. Echa un vistazo:
class Pila:
def __init__(self):
self.__listaPila = []
def push(self, val):
self.__listaPila.append(val)
def pop(self):
val = self.__listaPila[-1]
del self.__listaPila[-1]
return val
objetoPila = Pila()
objetoPila.push(3)
objetoPila.push(2)
objetoPila.push(1)
print(objetoPila.pop())
print(objetoPila.pop())
print(objetoPila.pop())
Sin embargo, hay algo realmente extraño en el código. Las funciones parecen familiares, pero
tienen más parámetros que sus contrapartes procedimentales.
Aquí, ambas funciones tienen un parámetro llamado self en la primera posición de la lista de
parámetros.
¿Es necesario? Si, lo es.
Todos los métodos deben tener este parámetro. Desempeña el mismo papel que el primer
parámetro constructor.
Permite que el método acceda a entidades (propiedades y actividades / métodos) del objeto.
No puedes omitirlo. Cada vez que Python invoca un método, envía implícitamente el objeto actual
como el primer argumento.
Esto significa que el método está obligado a tener al menos un parámetro, que Python mismo
utiliza - no tienes ninguna influencia sobre el.
Si tu método no necesita ningún parámetro, este debe especificarse de todos modos. Si está
diseñado para procesar solo un parámetro, debes especificar dos, ya que la función del primero
sigue siendo la misma.
Hay una cosa más que requiere explicación: la forma en que se invocan los métodos desde la
variable __listaPila .
Afortunadamente, es mucho más simple de lo que parece:
La primera etapa entrega el objeto como un todo → self .
A continuación, debes llegar a la lista __listaPila → self.__listaPila .
Con __listaPila lista para ser usada, puedes realizar el tercer y último paso
→ self.__listaPila.append(val) .
La declaración de la clase está completa y se han enumerado todos sus componentes. La clase
está lista para usarse.
El enfoque orientado a objetos: una pila desde cero
Tener tal clase abre nuevas posibilidades. Por ejemplo, ahora puedes hacer que más de una pila se
comporte de la misma manera. Cada pila tendrá su propia copia de datos privados, pero utilizará el
mismo conjunto de métodos.
Esto es exactamente lo que queremos para este ejemplo.
Analiza el código:
class Pila:
def __init__(self):
self.__listaPila = []
def push(self, val):
self.__listaPila.append(val)
def pop(self):
val = self.__listaPila[-1]
del self.__listaPila[-1]
return val
objetoPila1 = Pila()
objetoPila2 = Pila()
objetoPila1.push(3)
objetoPila2.push(objetoPila1.pop())
print(objetoPila2.pop())
Existen dos pilas creadas a partir de la misma clase base. Trabajan independientemente.
Puedes crear más si quieres.
Ejecuta el código en el editor y ve qué sucede. Realiza tus propios experimentos.
El enfoque orientado a objetos: una pila desde cero
(continuación)
Analiza el fragmento a continuación: hemos creado tres objetos de la clase Pila . Después, hemos
hecho malabarismos. Intenta predecir el valor que se muestra en la pantalla.
class Pila:
def __init__(self):
self.__listaPila = []
def push(self, val):
self.__listaPila.append(val)
def pop(self):
val = self.__listaPila[-1]
del self.__listaPila[-1]
return val
pequeñaPila = Pila()
otraPila = Pila()
graciosaPila = Pila()
pequeñaPila.push(1)
otraPila.push(pequeñaPila.pop() + 1)
graciosaPila.push(otraPila.pop() - 2)
print(graciosaPila.pop())
Output: 0
SUBCLASES:
class Pila:
def __init__(self):
self.__listaPila = []
def push(self, val):
self.__listaPila.append(val)
def pop(self):
val = self.__listaPila[-1]
del self.__listaPila[-1]
return val
class SumarPila(Pila):
def __init__(self):
Pila.__init__(self)
self.__sum = 0
Nota: generalmente es una práctica recomendada invocar al constructor de la superclase antes de
cualquier otra inicialización que desees realizar dentro de la subclase. Esta es la regla que hemos
seguido en el código.
METODOS EN LA SUBCLASE
class Pila:
def __init__(self):
self.__listaPila = []
def push(self, val):
self.__listaPila.append(val)
def pop(self):
val = self.__listaPila[-1]
del self.__listaPila[-1]
return val
class SumarPila(Pila):
def __init__(self):
Pila.__init__(self)
self.__sum = 0
# ingresa código aquí
def push(self, val):
self.__sum += val
Pila.push(self, val)
Se dice que el método push ha sido anulado - el mismo nombre que en la superclase ahora
representa una funcionalidad diferente.
El enfoque orientado a objetos: una pila desde cero
(continuación)
Esta es la nueva función pop :
def pop(self):
val = Pila.pop(self)
self.__sum -= val
return val
Hasta ahora, hemos definido la variable __sum , pero no hemos proporcionado un método para
obtener su valor. Parece estar escondido. ¿Cómo podemos mostrarlo y que al mismo tiempo se
proteja de modificaciones?
Tenemos que definir un nuevo método. Lo nombraremos getSuma . Su única tarea será devolver el
valor de __sum .
Aquí está:
def getSuma(self):
return self.__sum
Entonces, veamos el programa en el editor. El código completo de la clase está ahí. Podemos
ahora verificar su funcionamiento, y lo hacemos con la ayuda de unas pocas líneas de código
adicionales.
Como puedes ver, agregamos cinco valores subsiguientes en la pila, imprimimos su suma y los
sacamos todos de la pila.
Bien, esta ha sido una breve introducción a la programación de orientada a objetos de Python.
Pronto te contaremos todo con más detalle.
class Pila:
def __init__(self):
self.__listaPila = []
def push(self, val):
self.__listaPila.append(val)
def pop(self):
val = self.__listaPila[-1]
del self.__listaPila[-1]
return val
class SumarPila(Pila):
def __init__(self):
Pila.__init__(self)
self.__sum = 0
def getSuma(self):
return self.__sum
def push(self, val):
self.__sum += val
Pila.push(self, val)
def pop(self):
val = Pila.pop(self)
self.__sum -= val
return val
objetoPila = SumarPila()
for i in range(5):
objetoPila.push(i)
print(objetoPila.getSuma())
for i in range(5):
print(objetoPila.pop())
Variables de instancia
En general, una clase puede equiparse con dos tipos diferentes de datos para formar las
propiedades de una clase. Ya viste uno de ellos cuando estábamos estudiando pilas.
Este tipo de propiedad existe solo cuando se crea explícitamente y se agrega a un objeto. Como ya
sabes, esto se puede hacer durante la inicialización del objeto, realizada por el constructor.
Además, se puede hacer en cualquier momento de la vida del objeto. Es importante mencionar
también que cualquier propiedad existente se puede eliminar en cualquier momento.
Tal enfoque tiene algunas consecuencias importantes:
Diferentes objetos de la misma clase pueden poseer diferentes conjuntos de
propiedades.
Debe haber una manera de verificar con seguridad si un objeto específico posee la
propiedad que deseas utilizar (a menos que quieras provocar una excepción, siempre vale
la pena considerarlo).
Cada objeto lleva su propio conjunto de propiedades - no interfieren entre sí de ninguna
manera.
Tales variables (propiedades) se llaman variables de instancia.
La palabra instancia sugiere que están estrechamente conectadas a los objetos (que son instancias
de clase), no a las clases mismas. Echemos un vistazo más de cerca a ellas.
Aquí hay un ejemplo:
class ClaseEjemplo:
def __init__(self, val = 1):
self.primera = val
def setSegunda(self, val):
self.segunda = val
objetoEjemplo1 = ClaseEjemplo()
objetoEjemplo2 = ClaseEjemplo(2)
objetoEjemplo2.setSegunda(3)
objetoEjemplo3 = ClaseEjemplo(4)
objetoEjemplo3.tercera = 5
print(objetoEjemplo1.__dict__)
print(objetoEjemplo2.__dict__)
print(objetoEjemplo3.__dict__)
Se necesita una explicación adicional antes de entrar en más detalles. Echa un vistazo a las últimas
tres líneas del código.
Los objetos de Python, cuando se crean, están dotados de un pequeño conjunto de propiedades
y métodos predefinidos. Cada objeto los tiene, los quieras o no. Uno de ellos es una variable
llamada __dict__ (es un diccionario).
La variable contiene los nombres y valores de todas las propiedades (variables) que el objeto
contiene actualmente. Vamos a usarla para presentar de forma segura el contenido de un objeto.
Vamos a sumergirnos en el código ahora:
La clase llamada ClaseEjemplo tiene un constructor, el cual crea incondicionalmente
una variable de instancia llamada primera , y le asigna el valor pasado a través del primer
argumento (desde la perspectiva del usuario de la clase) o el segundo argumento (desde la
perspectiva del constructor); ten en cuenta el valor predeterminado del parámetro: cualquier
cosa que puedas hacer con un parámetro de función regular también se puede aplicar a los
métodos.
La clase también tiene un método que crea otra variable de instancia, llamada segunda .
Hemos creado tres objetos de la clase ClaseEjemplo , pero todas estas instancias difieren:
o objetoEjemplo1 solo tiene una propiedad llamada primera .
o objetoEjemplo2 tiene dos propiedades: primera y segunda .
o objetoEjemplo3 ha sido enriquecido sobre la marcha con una propiedad
llamada tercera , fuera del código de la clase: esto es posible y totalmente permisible.
La salida del programa muestra claramente que nuestras suposiciones son correctas: aquí están:
{'primera': 1}
{'primera': 2, 'segunda': 3}
{'primera': 4, 'tercera': 5}
Hay una conclusión adicional que debería mencionarse aquí: el modificar una variable de
instancia de cualquier objeto no tiene impacto en todos los objetos restantes. Las variables
de instancia están perfectamente aisladas unas de otras.
Variables de instancia: continuación
Observa el ejemplo modificado en el editor.
Es casi lo mismo que el anterior. La única diferencia está en los nombres de las propiedades.
Hemos agregado dos guiones bajos ( __ ) en frente de ellos.
Como sabes, tal adición hace que la variable de instancia sea privada - se vuelve inaccesible
desde el mundo exterior.
El comportamiento real de estos nombres es un poco más complicado, así que ejecutemos el
programa. Esta es la salida:
{'_ClaseEjemplo__primera': 1}
{'_ClaseEjemplo__primera': 2, '_ClaseEjemplo__segunda': 3}
{'_ClaseEjemplo__primera': 4, '__tercera': 5}
¿Puedes ver estos nombres extraños llenos de guiones bajos? ¿De dónde provienen?
Cuando Python ve que deseas agregar una variable de instancia a un objeto y lo vas a hacer dentro
de cualquiera de los métodos del objeto, maneja la operación de la siguiente manera:
Coloca un nombre de clase antes de tu nombre.
Coloca un guión bajo adicional al principio.
Es por ello que __primera se convierte en _ClaseEjemplo__primera .
El nombre ahora es completamente accesible desde fuera de la clase. Puedes ejecutar un
código como este:
print(objetoEjemplo1._ClaseEjemplo__primera)
y obtendrás un resultado válido sin errores ni excepciones.
Como puedes ver, hacer que una propiedad sea privada es limitado.
No funcionará si agregas una variable de instancia fuera del código de clase. En este caso, se
comportará como cualquier otra propiedad ordinaria.
class ClaseEjemplo:
def __init__(self, val = 1):
self.__primera = val
def setSegunda(self, val):
self.__segunda = val
objetoEjemplo1 = ClaseEjemplo()
objetoEjemplo2 = ClaseEjemplo(2)
objetoEjemplo2.setSegunda(3)
objetoEjemplo3 = ClaseEjemplo(4)
objetoEjemplo3.__tercera = 5
print(objetoEjemplo1.__dict__)
print(objetoEjemplo2.__dict__)
print(objetoEjemplo3.__dict__)
OUTPUT
{'_ClaseEjemplo__primera': 1}
{'_ClaseEjemplo__primera': 2, '_ClaseEjemplo__segunda': 3}
{'_ClaseEjemplo__primera': 4, '__tercera': 5}
Variables de Clase
Una variable de clase es una propiedad que existe en una sola copia y se almacena fuera de
cualquier objeto.
Nota: no existe una variable de instancia si no hay ningún objeto en la clase; existe una variable de
clase en una copia, incluso si no hay objetos en la clase.
Las variables de clase se crean de manera diferente. El ejemplo te dirá más:
class ClaseEjemplo:
contador = 0
def __init__(self, val = 1):
self.__primera = val
ClaseEjemplo.contador += 1
objetoEjemplo1 = ClaseEjemplo()
objetoEjemplo2 = ClaseEjemplo(2)
objetoEjemplo3 = ClaseEjemplo(4)
print(objetoEjemplo1.__dict__, objetoEjemplo1.contador)
print(objetoEjemplo2.__dict__, objetoEjemplo2.contador)
print(objetoEjemplo3.__dict__, objetoEjemplo3.contador)
Observa:
Hay una asignación en la primera linea de la definición de clase: establece la variable
denominada contador a 0; inicializando la variable dentro de la clase pero fuera de
cualquiera de sus métodos hace que la variable sea una variable de clase.
El acceder a dicha variable tiene el mismo aspecto que acceder a cualquier atributo de
instancia; está en el cuerpo del constructor; como puedes ver, el constructor incrementa la
variable en uno; en efecto, la variable cuenta todos los objetos creados.
Ejecutar el código causará el siguiente resultado:
{'_ClaseEjemplo__primera': 1} 3
{'_ClaseEjemplo__primera': 2} 3
{'_ClaseEjemplo__primera': 4} 3
Dos conclusiones importantes provienen del ejemplo:
Las variables de clase no se muestran en el diccionario de un objeto __dict__ (esto es
natural ya que las variables de clase no son partes de un objeto), pero siempre puedes
intentar buscar en la variable del mismo nombre, pero a nivel de clase, te mostraremos esto
muy pronto.
Una variable de clase siempre presenta el mismo valor en todas las instancias de clase
(objetos).
class ClaseEjemplo:
__contador = 0
def __init__(self, val = 1):
self.__primera = val
ClaseEjemplo.__contador += 1
objetoEjemplo1 = ClaseEjemplo()
objetoEjemplo2 = ClaseEjemplo(2)
objetoEjemplo3 = ClaseEjemplo(4)
print(objetoEjemplo1.__dict__, objetoEjemplo1._ClaseEjemplo__contador)
print(objetoEjemplo2.__dict__, objetoEjemplo2._ClaseEjemplo__contador)
print(objetoEjemplo3.__dict__, objetoEjemplo3._ClaseEjemplo__contador)
OUTPUT (3 OBJETOS CREADOS)
{'_ClaseEjemplo__primera': 1} 3
{'_ClaseEjemplo__primera': 2} 3
{'_ClaseEjemplo__primera': 4} 3
Variables de Clase: continuación
Hemos dicho antes que las variables de clase existen incluso cuando no se creó ninguna instancia
de clase (objeto).
Ahora aprovecharemos la oportunidad para mostrarte la diferencia entre estas dos
variables __dict__ , la de la clase y la del objeto.
Observa el código en el editor. La prueba está ahí.
Echemos un vistazo más de cerca:
Definimos una clase llamada ClaseEjemplo .
La clase define una variable de clase llamada varia .
El constructor de la clase establece la variable con el valor del parámetro.
Nombrar la variable es el aspecto más importante del ejemplo porque:
o El cambiar la asignación a self.varia = val crearía una variable de instancia con el
mismo nombre que la clase.
o El cambiar la asignación a varia = val operaría en la variable local de un método; (te
recomendamos probar los dos casos anteriores; esto te facilitará recordar la diferencia).
La primera línea del código fuera de la clase imprime el valor del
atributo ClaseEjemplo.varia . Nota: utilizamos el valor antes de instanciar el primer objeto
de la clase.
Ejecuta el código en el editor y verifica su salida.
Como puedes ver __dict__ contiene muchos más datos que la contraparte de su objeto. La
mayoría de ellos son inútiles ahora - el que queremos que verifiques cuidadosamente muestra el
valor actual de varia .
Nota que el __dict__ del objeto está vacío - el objeto no tiene variables de instancia.
Comprobando la existencia de un atributo
La actitud de Python hacia la instanciación de objetos plantea una cuestión importante: en contraste
con otros lenguajes de programación, es posible que no esperes que todos los objetos de la
misma clase tengan los mismos conjuntos de propiedades.
Justo como en el ejemplo en el editor. Míralo cuidadosamente.
El objeto creado por el constructor solo puede tener uno de los dos atributos posibles: a o b .
La ejecución del código producirá el siguiente resultado:
Traceback (most recent call last):
File ".main.py", line 11, in
print(objetoEjemplo.b)
AttributeError: 'ClaseEjemplo' object has no attribute 'b'
Como puedes ver, acceder a un atributo de objeto (clase) no existente provoca una
excepción AttributeError.
Comprobando la existencia de un atributo
La actitud de Python hacia la instanciación de objetos plantea una cuestión importante: en contraste
con otros lenguajes de programación, es posible que no esperes que todos los objetos de la
misma clase tengan los mismos conjuntos de propiedades.
Justo como en el ejemplo en el editor. Míralo cuidadosamente.
El objeto creado por el constructor solo puede tener uno de los dos atributos posibles: a o b .
La ejecución del código producirá el siguiente resultado:
Traceback (most recent call last):
File ".main.py", line 11, in
print(objetoEjemplo.b)
AttributeError: 'ClaseEjemplo' object has no attribute 'b'
Como puedes ver, acceder a un atributo de objeto (clase) no existente provoca una
excepción AttributeError.
class ClaseEjemplo:
def __init__(self, val):
if val % 2 != 0:
self.a = 1
else:
self.b = 1
objetoEjemplo = ClaseEjemplo(1)
print(objetoEjemplo.a)
print(objetoEjemplo.b)
Comprobando la existencia de un atributo: continuación
La instrucción try-except te brinda la oportunidad de evitar problemas con propiedades
inexistentes.
Es fácil: mira el código en el editor.
class ClaseEjemplo:
def __init__(self, val):
if val % 2 != 0:
self.a = 1
else:
self.b = 1
objetoEjemplo = ClaseEjemplo(1)
print(objetoEjemplo.a)
try:
print(objetoEjemplo.b)
except AttributeError:
pass
Como puedes ver, esta acción no es muy sofisticada. Esencialmente, acabamos de barrer el tema
debajo de la alfombra.
Afortunadamente, hay una forma más de hacer frente al problema.
Python proporciona una función que puede verificar con seguridad si algún objeto / clase
contiene una propiedad específica. La función se llama hasattr , y espera que le pasen dos
argumentos:
La clase o el objeto que se verifica.
El nombre de la propiedad cuya existencia se debe informar (Nota: debe ser una cadena
que contenga el nombre del atributo).
La función retorna True o False.
Así es como puedes utilizarla:
class ClaseEjemplo:
def __init__(self, val):
if val % 2 != 0:
self.a = 1
else:
self.b = 1
objetoEjemplo = ClaseEjemplo(1)
print(objetoEjemplo.a)
if hasattr(objetoEjemplo, 'b'):
print(objetoEjemplo.b)
Comprobando la existencia de un atributo: continuación
No olvides que la función hasattr() también puede operar en clases. Puedes usarlo para
averiguar si una variable de clase está disponible, como en el ejemplo en el editor.
La función devuelve True si la clase especificada contiene un atributo dado, y False de lo
contrario.
¿Puedes adivinar la salida del código? Ejecútalo para verificar tus conjeturas.
Un ejemplo más: analiza el código a continuación e intenta predecir su salida:
class ClaseEjemplo:
a = 1
def __init__(self):
self.b = 2
objetoEjemplo = ClaseEjemplo()
print(hasattr(objetoEjemplo, 'b'))
print(hasattr(objetoEjemplo, 'a'))
print(hasattr(ClaseEjemplo, 'b'))
print(hasattr(ClaseEjemplo, 'a'))
¿Tuviste éxito? Ejecuta el código para verificar tus predicciones.
OUTPUT
True
True
False
True
Bien, hemos llegado al final de esta sección. En la siguiente sección vamos a hablar sobre los
métodos, ya que los métodos dirigen los objetos y los activan.
Métodos a detalle
Resumamos todos los hechos relacionados con el uso de métodos en las clases de Python.
Como ya sabes, un método es una función que está dentro de una clase.
Hay un requisito fundamental: un método está obligado a tener al menos un parámetro (no
existen métodos sin parámetros; un método puede invocarse sin un argumento, pero no puede
declararse sin parámetros).
El primer (o único) parámetro generalmente se denomina self . Te sugerimos que lo sigas
nombrando de esta manera, darle otros nombres puede causar sorpresas inesperadas.
El nombre self sugiere el propósito del parámetro - identifica el objeto para el cual se invoca el
método.
Si vas a invocar un método, no debes pasar el argumento para el parámetro self - Python lo
configurará por ti.
El ejemplo en el editor muestra la diferencia.
El código da como salida:
método
Toma en cuenta la forma en que hemos creado el objeto - hemos tratado el nombre de la clase
como una función, y devuelve un objeto recién instanciado de la clase.
Si deseas que el método acepte parámetros distintos a self , debes:
Colocarlos después de self en la definición del método.
Pasarlos como argumentos durante la invocación sin especificar self .
Justo como aqui:
class conClase:
def metodo(self, par):
print("método:", par)
obj = conClase()
obj.metodo(1)
obj.metodo(2)
obj.metodo(3)
El código da como salida:
método: 1
método: 2
método: 3