jueves, 27 de junio de 2024

MAV. OOo Basic


Creación de ítem de evaluación con Impress





También en Impress (servicio de creación de presentaciones de LibreOffice) es posible crear ítem de evaluación, y no me refiero al uso básico de las presentaciones como recurso para presentar la tarea, algo que ya [hemos visto] en este blog como soporte complementario de un docap. También hemos hablado de las peculiaridades del uso de  OOo Basic en Impress, por lo que [te remito a estas entradas] sobre estas cuestiones previas.


Lo que voy a tratar ahora es el proceso a seguir para transformar una diapositiva Impress en un recurso de evaluación. Se trata de una mera ilustración del modo de trabajo, en realidad de uno de los modos de trabajo, y en su forma más simple. La incorporación de recursos gráficos como los que proporciona Impress dentro de un docap complejo, requiere un desarrollo del contenido de esta entrada que posiblemente abordaré en otro momento.

Para contextualizar esta exposición, vamos a suponer una prueba de evaluación de un único ítem creado sobre presentación Impress, la cual diseñaremos para que el alumno responda directamente actuando sobre la pantalla. Se trata de que el funcionamiento del documento permita el uso directo por parte del alumno, aunque no resuelve todos los problemas que esto implica, sólo los más básicos.

Ya sabemos que Impress permite también (como todos los servicios LibreOffice) el uso de script (1), pero con ciertas limitaciones, incluyendo una determinante: no es posible utilizar la funcionalidad Grabar macro. También presenta ciertas peculiaridades que responden a los dos modos de trabajo con el servicio: en modo Diseño y en modo Presentación.
  • En modo Diseño podemos implementar controles de formulario, por ejemplo botones de comando, que asociados a script funcionan como se espera.
  • Pero algunos de esos controles, por ejemplo los citados botones de comando, no funcionan como desearíamos en el modo Presentación (2).
Estas limitaciones las podemos suplir implementando diálogos, pero también transformando los elementos gráficos en comandos, asociándolos a script. Esta será nuestra opción ahora. Paso a explicarte en detalle cómo hacerlo.

Te presento nuestra "presentación". Se trata de un ítem [tomando como modelo el test RIAS-MnV] que nos servirá de base para este trabajo.



Seguro que reconoces en este nuestro ejemplo el formato de presentación de los ítem del test, con lo que te será fácil trasladarte mentalmente a sus cuadernos de trabajo. Como puedes ver, nuestro ejemplo consta únicamente de dos diapositivas, aunque en un desarrollo real sería necesario repetir el mismo modelo tantas veces como ítem tiene la prueba: la primero contiene el estímulo y la segunda las opciones de respuesta. Utilizo la forma Flecha (Insertar | Forma | Flechas de bloques) para el desplazamiento por la presentación, si bien se podría utilizar el paso de diapositiva en función de un intervalo de tiempo (3)

Puedes comprobar que es posible implementar botones de comando [Ver | Barra de herramientas | Controles de formularioy también su correcto funcionamiento en el modo Diseño, (una vez que lo asocies a un script), pero cuando trates de activarlos desde el modo Presentación observarás que no funciona.

Después de hacer esta prueba (4), y antes de implementar nuestro "botones", deberemos habilitar nuestra presentación para que contenga script. Para ello procedemos como en el resto de los servicios LibreOffice:

Herramientas | Macros | Organizar macros | BASIC | Organizador ....

... para crear el módulo (o los módulos) en el que escribiremos nuestros script. Te recomiendo escribir estos antes de implementar los comandos, así que aquí tienes los que yo planteo:

Dim mRespuestas(43) As String
Dim mPuntos(43) As Integer

Sub cmd11

mRespuestas(0) = "A"
mPuntos(0) = 2

Info
End Sub

Sub cmd12

mRespuestas(0) = "B"
mPuntos(0) = 0

Info
End Sub

Sub cmd13

mRespuestas(0) = "C"
mPuntos(0) = 0

Info
End Sub

Sub cmd14

mRespuestas(0) = "D"
mPuntos(0) = 0

Info
End Sub

Sub cmd15

mRespuestas(0) = "E"
mPuntos(0) = 0

Info
End Sub

Sub cmd16

mRespuestas(0) = "F"
mPuntos(0) = 0

Info
End Sub

Sub Info
MsgBox "Respuesta: " & mRespuestas(0)
MsgBox "Puntuación " & mPuntos(0)
End Sub
 
A destacar:
  • Las matrices que contienen las respuestas (mRespuestas(43) - mPuntos(43) (5)) se declaran como públicas, fuera de los script, para que sean accesibles desde todos ellos.
  • Creo tantos script como botones necesito implementar en la diapositiva de respuesta (contiene 6 opciones), todos ellos con la misma estructura, por lo que el procedimiento, aunque parece costoso, en realidad es muy simple: asignación al primer elemento de cada una de las dos matrices un contenido. En el de la matriz mRespuestas(0) se asignará una letra que corresponde con la opción seleccionada, y en el de la matriz mPuntos(0), obviamente la puntuación (1 para la opción A y 0 para el resto).
Queda claro que no es un código precisamente muy elaborado, lo admito, pero en esta entrada de lo que se trata es de ver cómo podemos implementar controles de formulario (o similares) para asociarlos a OOo Basic y hacer que una presentación funcione como docap, así que, de momento, no nos detendremos en el código para centrar la atención sobre los procedimientos de acceso y la forma de implementar controles funcionales en la presentación. De eso va la parte que sigue de esta entrada.

Dado que el funcionamiento de los controles de formulario parece que no son de mucha ayuda (en el modo Presentación, que es el que nos interesa) (6), tendremos que buscar una alternativa. Afortunadamente la hay, y se llama uso de las formas básicas y su asociación a macros (script), posibilidad esta prevista LO-Impress como funcionalidad dentro de las opciones de funcionamiento que se asignan a esos objetos. Veamos cómo hacerlo.

En primer lugar tenemos que considerar dos posibles situaciones, ya que la forma de trabajo dependerá de con cual de las dos situaciones nos encontremos:
  • Que en la diapositiva de respuestas cada imagen sea un elemento diferente. En ese caso, nuestro caso tendíamos un total de seis elementos.
  • O que se trate de un dibujo único (una lámina con seis dibujos). Ahora sólo habrá un único elemento (7).
En el primer caso, directamente podremos convertir cada uno de los elementos en un comando que se asocia con un script. Para ello seguiremos estos pasos:
    1.  Seleccionamos el objeto-dibujo.
    2. Clic derecho sobre el objeto seleccionado
    3. En el menú de opciones que se despliega seleccionamos Interacción
    4. Activamos el desplegable de opciones (Acción al pulsar sobre el ratón)
    5. Y seleccionamos la opción Ejecutar macro
  • Una vez que aceptamos esta selección (botón inferior Aceptar) aparece un TextBox (Macro) que nos facilita acceder a IDE haciendo clic en el botón de comando Examinar.
  •  LO que tenemos que hacer a continuación es lo mismo que hacemos cuando queremos vincular un botón de comando a un script: seleccionar el directorio donde se encuentra el script, en caso de que el que se nos muestra no sea el que nos interesa (que normalmente lo será, con lo que nos ahorraremos este paso)
  •  Y hacer clic sobre el script que nos interese.

De este modo tan simple hemos convertido una imagen (o una forma Impress) en un comando que activa un script. Al contrario de lo que sucede con el control Botón de comando de formulario, nuestra imagen-comando no funciona como tal en el modo Diseño, pero sí en el modo Presentación, y la primera muestra de ello es que cuando pasamos el ratón por la imagen-comando, el puntero cambia de forma (Flecha -> Mano) y si hacemos clic sobre ella se desarrollará la secuencia de acciones que está diseñada en el script asociado.

Cuando se trata de una lámina con varias imágenes, tenemos que cambiar de estrategia, ya que ahora no existe un objeto individualizado sobre el que actuar; por eso deberemos utilizar las formas disponibles en Impress  (por ejemplo (8)), pero el proceso a seguir para transformar esas formas en comandos sigue siendo el mismo de antes.

Ahora bien, para resolver esta segunda situación (lámina única) tenemos dos opciones de configurar la presentación:

  • Crear una forma-comando añadida a cada elemento de la imagen, como es el caso de la imagen que te muestro a continuación, en la que bajo cada figura he situado una forma (pequeño círculo rojo), la cual transformo en comando siguiendo el método explicado antes...

  • O superponer a cada imagen de la lámina una forma transparente, siendo ésta la que se convierta en comando (siguiendo el mismo procedimiento. En este caso no se observa aparentemente nada diferente de lo que es la propia lámina, pero cada una de sus imágenes aparentemente se ha convertido en un comando; en realidad es la forma superpuesta la que actúa como comando, pero al ser transparente no se aprecia.

Veamos como proceder para desarrollar esta posibilidad:

  • Seleccionamos la figura que vamos a superponer a cada uno de los dibujos de la lámina, de modo que lo cubra por completo.
  • Seleccionamos y copiamos esta figura y la desplazamos hasta la imagen siguiente, así hasta cubrir todos las imágenes (o todas las que deseemos (9))
  • Ahora puedes convertir cada figura en comando, siguiendo el procedimiento ya explicado (10)
  • A continuación te recomiendo modificar la línea exterior de todas esas formar para hacer que desaparezca. Para ello, con la forma seleccionada hacemos clic derecho y en el menú desplegable elegimos la opción Línea y como estilo Ninguno.

  • Ahora es el momento de trabajar de nuevo con cada figura, pero no ya con la línea, sino con el Área 


... y dentro de las opciones posibles, con Transparencia, seleccionando la segunda opción (Con transparencia) y como valor el 100%.


La imagen que sigue muestra diferentes grados de transparencia para que aprecies mejor el proceso, aunque el objetivo final es que todas se presenten igual que la primera, sin línea exterior y totalmente transparentes.


De este modo, la imagen de la lámina queda a la vista, la figura superpuesta es invisible y el comando funciona sin problemas en el modo Presentación.  El usuario tendrá la sensación de hacer clic sobre el dibujo de su elección igual que si trabajáramos con imágenes individuales.

Documento

Desde [este enlace] puedes descargar la presentación que nos ha servido de ejemplo. Consta de cuatro diapositivas, el estímulo y tres más de respuesta, una con cada una de las opciones de trabajo he henos visto. 

Debes descargar el archivo, abrirlo desde LibreOffice y como se trata de un archivo con macros, habilitarlas.

El código que contiene es el mismo que te muestro en la entrada, pero te sugiero que accedas a él desde el IDE y que introduzcas los cambios y mejoras que te parezcan oportunos.

NOTAS

(1) En este momento nos limitamos al uso de OOo Basic como lenguaje de programación, pero ya sabes que en LibreOffice hay otras opciones, incluyendo Python.
(2) Otros controles se pueden implementar en el modo diseño, pero no funcionan ni en este modo ni en el modo presentación. Además, el funcionamiento de la presentación depende del modo en que ésta esté configurada. Todas estas cuestiones no serán tratadas en esta entrada, salvo lo que resulte de inmediato interés.
(3) En RIAS-MnV se plantean tiempos de presentación del estímulo (5") y de máximo de exposición de la lámina de respuesta: 20" en primera instancia y, en caso de fallo, 10" más. Estos requisitos se ajustan bien al uso de intervalos de tiempo como criterio de paso de las diapositivas; pero aquí asumo que es el profesional quien controla los tiempos de exposición, lo que, aunque es factible con un único monitor, se beneficia del uso de dos: el primero es controlado por el OE y en el segundo se muestra la presentación al alumno.
(4) No es obligatoria, ya te he contado yo lo que sucede, pero tampoco está de más comprobarlo personalmente.
(5) Su dimensión (43) se explica por contener RIAS-MnV 44 ítem.
(6) No puedo descartar que esto sea debido a muy escaso conocimiento del servicio Impress, pero en todo caso sí está claro que el comportamiento de los controles de formulario no es tan funcional y sencillo como en Calc o en Writer, lo que genera problemas que, hoy por hoy, no estoy en condiciones de resolver. De ahí la necesidad de trabajar desde otra perspectiva.
(7) También podemos importar imágenes si consideramos que se ajustan mejor a lo que nos interesa.
(8) Aunque según qué casos, ésta puede ser una solución menos "elegante" que presentar cada imagen de forma independiente, la lámina única ahorra mucho trabajo de preparación de materiales e incluso puede ser la mejor opción según para qué tareas, como la de localizar determinados elementos en un mapa, fotografía o dibujo.
(9) Para un soporte de evaluación como el que estamos creando es necesario que todas la posibles respuestas estén cubiertas por este tipo de comandos, ya que todas son opciones de respuesta. En otros casos se deberá valorar lo que resulta más fiable y conveniente, ya que, como dije, en el modo Presentación el cursor cambia de forma, lo que es una pista de cuáles son las imágenes que están asociadas a script.
(10) Aunque puedes convertir cada figura en comando cuando lo desees, yo te recomiendo que lo hagas bien en este momento o, como mucho, tras el siguiente (eliminar la línea externa de la forma), pero siempre antes de trabajar con la transparencia de la figura. De este modo te ahorras problemas para seleccionarla, ya que una vez que es transparente te va a costar que se active para convertirla en comando.  Cierto que puedes utilizar un truco, como seleccionar la lámina de fondo y desplazarla para que las figuras transparentes queden identificadas, pero tendrás que volver a colocar de nuevo la lámina en su sitio exacto sin poder ver cómo queda la superposición lámina-figuras, lo que no siempre es fácil.

domingo, 16 de junio de 2024

LibreOffice. Calc.

Borrar celdas


Cuando  trabajas con una Hc-Calc como soporte para, por ejemplo, crear un listado, si usas el mismo instrumento de forma sucesiva, puede ser un engorro tener que borrar el contenido introducido antes para introducir nuevo contenido. Este problema se acentúa cuando el número de celdas a borrar es elevado o las celdas están dispersas y te interesa que se borren unas pero no otros, por contener, por ejemplo, textos que te interesa mantener. 



En esos y otros casos similares, disponer de una macro o de un script que te ahorre trabajo no vendría nada mal, ¿no te parece?. Vamos a ver qué podemos hacer.

En primer lugar contamos con una solución basada en el código-macro: grabaremos una macro de borrado de celda y multiplicaremos esta acción tantas veces como celdas deseemos borrar.

Sub mcrBorrarCelda

dim document   as object
dim dispatcher as object

document   = ThisComponent.CurrentController.Frame
dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")

dim args1(0) as new com.sun.star.beans.PropertyValue
args1(0).Name = "ToPoint"
args1(0).Value = "$A$3"
dispatcher.executeDispatch(document, ".uno:GoToCell", "", 0, args1())

dispatcher.executeDispatch(document, ".uno:ClearContents", "", 0, Array())

End Sub

Eliminados los comentarios que ahora no son necesario, podemos ver cómo funciona esta macro:

  • Primero se crea las variables de la macro, ambas de tipo Object (document y dispatcher)
  • Después se asigna a ambos los objetos correspondientes, el objeto documento activo (document   = ThisComponent.CurrentController.Frame) y el objeto Dispatcher (dispatcher = createUnoService("com.sun.star.frame.DispatchHelper"))
  • A continuación se construye la estructura-matriz que identifica el objeto celda sobre el que actuamos y se concreta el código que identifica la celda-diana ($A$3") sobre la que se realizarán las acciones posteriores.
dim args1(0) as new com.sun.star.beans.PropertyValue
args1(0).Name = "ToPoint"
args1(0).Value = "$A$3"

  • ... y se ejecuta el salto a dicha celda (dispatcher.executeDispatch(document, ".uno:GoToCell", "", 0, args1()))
  • Por último se ejecuta la instrucción de borrado de la celda mediante una instrucción de sintaxis similar a la anterior (dispatcher.executeDispatch(document, ".uno:ClearContents", "", 0, Array()))
Para trabajar sobre cualquier otra celda es suficiente con sustituir el valor String que la identifica ("$A$3") por una variable a la que asignar el identificador o nombre (formato String) de la nueva celda.

Y para generalizar este procedimiento, sólo tenemos que repetirlo tantas veces como deseemos, aunque caben otras opciones, como veremos más abajo.

Supongamos que hemos creado un docap para gestionar la creación de una base de datos en la que se recogen los que constan en el expediente de resultados académicos del alumnado de EP. Para ello hemos creado una Hc-Gestor a modo de ficha personal como la que se ilustra (1)



Podemos ver que el número de celdas a borrar (manualmente) es suficientemente grande como para que ir de una en una o por pequeños grupos no sea una solución eficiente; además el posicionamiento de las celdas y la existencia de "etiquetas" nos impide optar por el "borrón y cuenta nueva". Estas mismas circunstancias hacen que tampoco sea funcional utilizar repetidamente una macro simple de borrado como la anterior.

En casos, una primera opción consiste en seleccionar las celdas sobre las que actuar (borrar contenido) y proceder posteriormente a su borrado. La orden de borrado es la misma que la que aplicamos a una celda individual (dispatcher.executeDispatch(document, ".uno:ClearContents", "", 0, Array()), pero la selección de las celdas puede realizarse de dos modos diferentes:
  • Desplazando el cursor de modo que se seleccione una a una cada una de las celdas de la tabla. Ver mcrBorrarTabla1 (en el IDE). En este se crea una matriz-base que identifica la celda origen (del proceso) y tantas matriz de selección de celdas como movimientos de selección se realicen, con la doble peculiaridad de que el args().Name se identifica como "By" y la acción-movimiento (dispatcher) como instrucción derecha-Izquierda-Arriba-Abajo (ver macro) (2) -> vg. ".uno:GoRightSel"
  • O arrastrando el cursos mediante el ratón desde la celda superior-izquierda hasta la celda inferior-derecha del espacio-tabla. Ver mcrBorrarTabla32. En este caso se omiten las matrices de selección celda-a-celda y se genera una en el primer argumento (args().Name) y la acción (dispatcher) son de posicionamiento ("ToPoint") y el segundo argumento (args().Value) contiene el intervalo de celdas sobre las que se actuará ("$G$6:$J$11").
La segunda opción  pasa por emplear código OOo Basic no procedente de Grabar macro. Este cambio nos permite simplificar el script y consiste, fundamentalmente en hacer uso de la instrucción (método) clearContents(), que recibe una clave numérica como parámetro, el cual identifica el tipo de dato a borrar:
  •  1 -> Borra datos numéricos, manteniendo el formato.
  • 4 -> Borra datos alfanuméricos (String), manteniendo el formato.
  • 1023 -> Borra todo tipo de contenido, incluido formato.
Este método lo aplicamos al objeto celda, referenciado por el nombre de su variable (oCelda). Un ejemplo:

Sub BorrarCelda

Dim oDoc As Object, oHoja As Object, oCelda As Object
Dim sNombreHoja As String

sNombreHoja = "Expediente"

oDoc = ThisComponent
oHoja = oDoc.getSheets.getByName(sNombreHoja)
oCelda = oHoja.getCellRangeByName("C6")

oCelda.clearContents(4)  'Borra contenido alfanumérico sin borrar formato

End Sub 

También podemos borrar un conjunto de celdas (rango) de una vez, sustituyendo el nombre de la celda por la referencia al rango sin hacer otra modificación (3). Así que el mismo script de antes, con algunos cambios, sirve también para borrar un rango de celdas (vg "G6:J11" borra el contenido de la tabla de calificaciones). Los cambios son de denominación de variable y sus implicaciones (4)...
oRango As Object en sustitución de oCelda As Object
... y otro de parámetro
oRango = oHoja.getCellRangeByName("G6:J11") en sustitución de oCelda = oHoja.getCellRangeByName("C6")

El uso de instrucciones OOo Basic, además de script de borrado directo de celdas o de rangos de celda, también nos permite trabajar con subrutinas, como en el ejemplo que muestro a continuación y que nos permite borrar todas las celdas de contenido de nuestro ejemplo:

Script

Sub BorrarCeldas

Dim mCeldas(3) As String
Dim sNombreHoja As String
Dim i As Integer

mCeldas = Array ("C6:D6","C10:D10","C12:D12","G6:J11")
sNombreHoja = "Expediente"

Borrar(sNombreHoja,mCeldas())

End Sub

Subrutina

Sub Borrar(nHoja As String, mC() As Variant)

Dim oHoja As Object, oCelda As Object
Dim i As Integer

oHoja = ThisComponent.getSheets.getByName(nHoja)

For i = LBound(mC()) To UBound(mC())
oCelda = oHoja.getCellRangeByName(mC(i))
oCelda.clearContents(4)
Next

End Sub

Documento. [BorrarCeldas.ods]


NOTAS

(1) No viene al caso ahora crear este docap ni explicar nada más sobre su funcionamiento y posibles usos. Dado que el objetivo de esta entrada es explicar cómo borrar celdas (y rangos del celdas) me limitaré a dar respuesta a este objetivo.
(2) Podemos reducir las líneas de código dando al parámetro args().Value el valor numérico que se ajuste a número de movimientos a realizar. Esto simplifica significativamente la macro. Ver mcrBorrarTabla2.
(3) Si te fijas, el método que se usa para identificar la celda es en realidad un método relativo al rango de celda única: getCellRangeByName()
(4) Con las implicaciones de denominación de la variable en el resto de la sintaxis del script en la instrucción de identificación del rango y en la llamada al método de borrado (oRango.clearContents(4))

viernes, 14 de junio de 2024

OOo Basic. Programación.

Definición de programación

Programar es ordenar una secuencia de pasos para conseguir el funcionamiento esperado en un ordenador. También se puede definir como la acción de crear programas o aplicaciones mediante el uso de un lenguaje de programación.


Existen múltiples lenguajes de programación, pero todos comparten características comunes, en su definición, en sus componentes y en la forma en que podemos esperar que funcionen.

Un lenguaje de programación es un lenguaje artificial que contiene signos, términos y estructuras, se rige por unas reglas sintácticas y produce unos resultados que son consecuencia del procesamiento de los datos con los que trabaja.

Los lenguajes de programación son lenguajes intermediarios entre el programador y el ordenador. Éste traduce a lo que se denomina "código máquina" las instrucciones que el programador escribe en determinado lenguaje de programación. El ordenador interpreta esas instrucciones, traduciéndolas a su lenguaje y ejecutándolas.

Lo interesante de la programación es que podemos adaptar el funcionamiento del ordenador a nuestras necesidades. Lenguajes como OOo Basic, por sus características especiales, nos permiten adaptar el funcionamiento de servicios de uso común en nuestro trabajo, como el procesador de texto, la hoja de cálculo o el programa de creación de presentaciones, para que se adapten a necesidades concretas y específicas de funcionamiento, lo que conlleva un ahorro de tiempo de trabajo.

No obstante también exige que aprendamos en qué consisten y cómo se usan esos lenguajes, lo que supone un tiempo de aprendizaje y una práctica. Pero podemos contar con la existencia de ciertas funcionalidades, como Crear macro, que nos facilitan el primer acercamiento al uso del lenguaje de programación. Gracias a ellas podemos empezar a "crear programas" sin otras exigencias de las que conlleva aprender a usar esas funcionalidades. No son suficientes para aprender a programar, pero ayudan a iniciar el camino.

Este blog está pensado para ayudar a que ese proceso de aprendizaje sea más llevadero, pero sobre todo que sea más funcional, al estar centrado en la solución de necesidades profesionales concretas. No es, por tanto, un blog para aprender a programar, ni tampoco para aprender a programar en OOo Basic; se trata (eso pretendo) de un blog para ayudar a los orientadores educativos en concreto, pero en general a los diferentes componentes de los SEO, a crear soluciones informáticas, funcionales y ajustadas a sus necesidades profesionales concretas. Y por tales entiendo el manejo de la documentación prescriptiva y la creación de la que resulta de la práctica profesional.

Otra forma de entender el para qué de este blog, es la de verlo como un recurso para el desarrollo de la competencia digital de los componentes de los SEO, la cual presenta ciertas particularidades que hace que, a mi juicio, las propuestas y los planes de la Administración educativa resulten demasiado genéricos ( o se se prefiere, poco específicos), dejando sin cubrir lo que deriva de la especificidad de estos (y otros) servicios educativos.

jueves, 13 de junio de 2024

POO

Métodos

Cuando hablamos de los métodos como componentes de una clase, asimilamos éstos con las funciones, llegando incluso a insinuar que eran denominaciones intercambiables. Así son considerados por muchos autores, aunque puede que no de forma explícita, y no faltan razones para ello, ya que básicamente un método es una función; no obstante, el ser parte constituyente de una clase otorga a los métodos algunas diferencias respecto a las funciones que es necesario conocer para entender el código de terceros y para trabajar adecuadamente con ambos.


En esta entrada vamos a exponer las diferencias existentes entre una función y un método, las cuales radican fundamentalmente en el uso de parámetros y en las implicaciones que tiene la pertenencia del método a la clase para su uso por parte de los objetos.

La primera diferencia que debemos señalar entre una función y un método es que en los métodos no vamos a encontrarnos nunca con el paréntesis vacío: como mínimo (y muy frecuentemente, como único) nos encontraremos con el parámetro self, que referencia a la clase y en su momento al objeto).

La segunda diferencia es que aunque el método utilice parámetros/atributos de la clase, no es necesario (y no se debe) establecerlos como tales parámetros, siendo suficiente con utilizar la formula self. nombre_atributo dentro de la función para que ésta utilice adecuadamente el valor que el parámetro tenga en el objeto.

La tercera diferencia es que para llamar al método desde el objeto no es necesario incluir valor alguno como argumento: ni para self (ya que es el propio objeto) ni para los argumentos (ya que están definidos previamente en la construcción del objeto.

Bien podría ser que el método en su formulación abstracta (como parte de la clase)  cuente con parámetros o con variables específicas (y privadas) no contempladas como atributos. En ese caso deberemos proceder del mismo modo que hacemos con las funciones tanto en la construcción del método como en el momento de llamarlo desde (y en función del) objeto.

Vamos a presentar un ejemplo en el que el método de una clase cuenta con un parámetro no incluido como atributo (aunque en realidad bien podría serlo) y una variable privada. Podemos comparar el funcionamiento de este código con el que se desarrolló en su momento para ver unas diferencias que explicaremos a continuación.

En el archivo actual, ni los argumentos ni el constructor de la clase se diferencia del archivo inicial... 

def __init__(self,nombre,edad):
        self.nombre = nombre
        self.edad = edad

... pero sí el método sentarse(). Observa:

Original: 

def sentarse(self):
        print(f"{self.nombre} se sienta cuando se lo ordeno (y quiere, claro).")

Actual:

 def sentarse(self,sexo):
        if sexo =="perrito":
            tratamiento = "educado"
        else:
            tratamiento= "educada"
        print(f"Mi {sexo} {self.nombre} tiene {self.edad} años. Está muy bien {tratamiento} y se sienta cuando se lo pido.")
  • Podemos identificar ambos como métodos (y no funciones) por el hecho de que ni en el primero ni en el segundo nombre y edad (ambos atributos identificados como tales en la construcción de la clase) son declarados como parámetros y por ser empleados dentro de print() mediante la auto-referencia (self.nombre, por ejemplo)
  • Además, el parámetro self presente en ambos métodos nos confirma que se trata de métodos, ya que este parámetro no se usa en funciones.
De hecho si formulásemos el método como función podríamos hacerlo como sigue (obviamente en este caso no se generan atributos ni se utiliza una función de inicialización):

def sentarse(nombre,edad,sexo):
    if sexo=="perrito":
        tratamiento = "educado"
    else:
        tratamiento = "educada"
    print(f"Mi {sexo} se llama {nombre} y tiene {edad} años. Es muy {tratamiento} ya que se sienta cuando se lo ordeno.")

Fíjate que, a consecuencia de ello, además de trabajar con los parámetros de una forma claramente diferente, en el print() de la función las variables incluidas como parte de la cadena f no se expresan mediante la sintaxis del punto, como sí hacemos en sus dos formulaciones como método.

Finalmente vamos a ver las diferencias en el modo de hacer la llamada a la función y al método.

Función (tras cuestionario asociado a variables/argumentos)

sentarse(perro_nombre,perro_edad,perro_sexo)

Método (formulación simple)

perrita = MiPerro("Atenea",10) -> Creación del objeto
perrita.sentarse() -> Llamada al método

Método (formulación similar a función, esto es: con cuestionario previo)

perro = MiPerro(perro_nombre,perro_edad) -> Creación del objeto
perro.sentarse(perro_sexo) -> Llamada al método

Al margen de lo que implica trabajar con clase-objeto, primero nos vamos a fijar en las similitudes y diferencias entre la llamada a la función y la llamada al método: en realidad llamar a la función equivale a desarrollar en una única instrucción el doble proceso de creación de objeto y llamada al método que se produce cunado trabajamos con clases y objetos.

La presencia necesaria de argumentos se concreta en la función en el momento de ser llamada, mientras que en el método se diferencia en función del doble proceso de creación del objeto, que se produce una vez y ya no tiene que volver a repetirse, lo que supone una ahorro importante de trabajo en caso de utilizar el objeto múltiples veces y con diferentes métodos, y la llamada al método propiamente dicho, que se resume como llamada a método sin más en su forma más simple, o que puede incluir algún parámetro en caso de haberse incluido alguno, como es el caso del segundo método.

Pero volvamos al análisis de la formulación del método en sus dos versiones: la más simple no incluye parámetros añadidos ni variables privadas, por lo que se presenta en su forma más simple, tanto en su definición como parte de la clase...

def sentarse(self):
        print(f"{self.nombre} se sienta cuando se lo ordeno (y quiere, claro).")

... como al ser llamada desde el objeto

perrita.sentarse()

La segunda formulación incluye el uso de un parámetro no establecido como  argumento en la definición de la clase (sexo), y una variable privada (tratamiento).

def sentarse(self,sexo):
        if sexo =="perrito":
            tratamiento = "educado"
        else:
            tratamiento= "educada"
        print(f"Mi {sexo} {self.nombre} tiene {self.edad} años. Está muy bien {tratamiento} y se sienta cuando se lo pido.")

Fíjate en las implicaciones de la presencia de este parámetro y de esta variable en cómo se escriben las variables en la cadena f: ninguno de los dos se formula con self., mientras  que sí se utiliza con las variables-argumentos.

Esta diferencia en el tratamiento se traslada después a la llamada al método, que incluye explicitar el argumento asociado al parámetro sexo:

perro.sentarse(perro_sexo)

Esta diferencia entre ambos métodos es más importante en lo que estamos tratando en la presente entrada que el hecho de que en la creación del objeto en una formulación introduzcamos directamente los valores que concretan los parámetros

perrita = MiPerro("Atenea",10)

Mientras que en la segunda asociemos dichos parámetros a sendas variables que nos permiten modificar de forma interactiva (input) el valor de los atributos.

perro_nombre = input("Nombre del perro: ")
perro_edad = eval(input("Edad del perro: "))
perro_sexo = input("perrito o perrita: ")

perro = MiPerro(perro_nombre,perro_edad)
perro.sentarse(perro_sexo)

Aunque esta formulación se asemeje aparentemente a la fórmula empleada en la función...

perro_nombre = input("Dime el nombre de tu perro: ")
perro_edad = eval(input("¿Cuántos años tiene? "))
perro_sexo = input("¿Es perrito o perrita? ")

sentarse(perro_nombre,perro_edad,perro_sexo)

... si te fijas bien hay una diferencia que revela que se trata de dos formas diferentes de abordar la cuestión: en la función empleamos los tres argumentos, mientras que en el objeto utilizamos dos argumentos para establecer el contenido de los parámetros en la construcción del objeto y utilizamos el tercero (perro_sexo) como parámetro del método, dado que (el método) cuenta con ese parámetro en su definición y lo utiliza para dar contenido a la variable privada que tiene el método (tratamiento)

Es posible que todo esto te haya parecido algo lioso, incluso una minucia poco relevante: no lo es, créeme: de no tenerse en cuenta se producirán errores que impiden que funcione todo correctamente y tú no comprenderás el código de terceros que empleen parámetros y variables dentro de los métodos.

Para ayudarte a comprender con ejemplos estas diferencias te dejo acceso a los tres archivos que me han servido de ejemplo:

POO

Atributos. Modificar valores


Una de las tareas que con más frecuencia debemos realizar al trabajar con objetos es dar valor a los atributos y modificar esos valores. En la entrada anterior en la que comparamos funciones con métodos ya estuvimos trabajando sobre ello, así como en la entrada referida a los atributos. En ésta nos vamos a centrar en esta cuestión específicamente.


Caben tres posibilidades u opciones para modificar el valor de un atributo:
  • Modificarlo directamente
  • Modificarlo mediante un método específico
  • O modificarlo de forma incremental, también mediante un método
Partiremos de una clase simple, en cuyos objetos no se tiene pensado modificar el contenidos de los atributos, para ir mostrando las tres opciones enunciadas antes.

class coche:
    def __init__(self,marca,modelo,anno):
        self.marca=marca
        self.modelo=modelo
        self.anno=anno

    def descripcion(self):
        coche_descripcion= f"Mi coche es un {self.marca} {self.modelo} de {self.anno}"
        return coche_descripcion

mi_coche=coche("Seat","Toledo",1996)
texto=mi_coche.descripcion()
print(texto)

La única novedad que presenta este script es que el método (descripcion(self)) devuelve (return) una cadena f que describe el objeto, por lo que su uso debe asociarse a una variable (texto). Por lo demás, presenta un funcionamiento de sobre conocido.

Para explicar las implicaciones de la modificación de atributo, deberemos incluir en la definición de atributos uno que no figure como parámetro, por ejemplo, el valor del cuentakilómetros, así como un método para mostrar el cuentakilómetros actual del vehículo. Esto modifica la definición de la clase como sigue:

class coche:
    def __init__(self,marca,modelo,anno):
        self.marca=marca
        self.modelo=modelo
        self.anno=anno
        self.cuentakilometros=0 

    def descripcion(self):
        coche_descripcion= f"Mi coche es un {self.marca} {self.modelo} de {self.anno}"
        return coche_descripcion

    def cuentaK(self):
        print(f"El cuentakilómetros de mi coche marca {self.cuentakilometros} kilómetros")

Procedamos ahora a introducir/modificar el valor inicial del parámetro cuentakilometros directamente en la definición del objeto mi_coche:

mi_coche=coche("Seat","Toledo",1996)
mi_coche.cuentakilometros = 1200
texto=mi_coche.descripcion()
print(texto)
mi_coche.cuentaK()

Una vez definido el objeto mi_coche (mi_coche=coche("Seat","Toledo",1996)) establecemos el valor del atributo no definido como parámetro (y en consecuencia no establecido ya como valor en la definición del objeto) mediante la expresión basada en la sintaxis del punto (mi_coche.cuentakilometros = 1200). Posteriormente llamaremos a este método de forma directa (mi_coche.cuentaK()), ya que originalmente no cuenta con la instrucción return.

La segunda opción consiste en modificar el atributo mediante un método específico, el siguiente:

    def cuentaK(self, km_actuales):
        self.cuentakilometros = km_actuales
   print(f"El cuentakilómetros de mi coche marca {self.cuentakilometros} kilómetros")

... que asocia el valor dado al atributo (km_actuales) al parámetro cuentakilometros que después se imprime por pantalla mediante la instrucción print()

mi_coche.cuentaK(1200)

Finalmente veamos cómo se concreta el método que permite incrementar el valor del parámetro, añadiéndolo como método nuevo.

class coche:
    def __init__(self,marca,modelo,anno):
        self.marca=marca
        self.modelo=modelo
        self.anno=anno
        self.cuentakilometros=0

    def descripcion(self):
        coche_descripcion= f"Mi coche es un {self.marca} {self.modelo} de {self.anno}"
        return coche_descripcion

    def cuentaK(self,kilometraje):
        self.cuentakilometros =kilometraje

    def leer_cuentaK(self):
        print(f"Este coche ha recorrido {self.cuentakilometros} Km")

    def incrementarKm(self,km):
        self.cuentakilometros += km

Observa que he eliminado la instrucción print() del método cuentaK() primitivo y he creado un método específico de escritura de los Km que lleva recorridos el coche (leer_cuentaK()). De este modo independizamos ambos procesos (dar valor al atributo cuentakilometros y escribir el resultado. El objetivo es reutilizar este método cuando incrementemos los kilómetros, que es lo que hacemos con el método incrementarKm()

Este es el método que nos permite incrementar los kilómetros inicialmente establecidos (mediante el método cuentaK()). Observa que incrementarKm() tiene dos parámetros: el obligatorio self y km, que serán los km que incrementemos cada vez que usemos el método. La instrucción de este método consiste en añadir km al dato que contiene el atributo cuentakilometros.

Desde el lado de la creación de un objeto (asociado a la variable mi_coche)...

mi_coche=coche("Seat","Toledo",1996)
texto=mi_coche.descripcion()
print(texto)
mi_coche.cuentaK(1200)
mi_coche.leer_cuentaK()
mi_coche.incrementarKm(100)
mi_coche.leer_cuentaK()

... podemos apreciar el uso de los cuatro métodos de la clase, incluyendo el uso repetido del método leer_cuentaK(): primero para informar de los km del cuentakilómetros y después para informar del resultante tras el incremento.