Python – Buscando a Wally.txt

Objetivo: mostrar cómo recorrer recursivamente el sistema de archivos en Python.

Wally.txt es un fichero juguetón, caprichoso y escurridizo. A la primera de cambio, en cuanto le das la espalda, se esconde en el sistema de ficheros y ¡ponte entonces a buscarlo!

Lo que Wally.txt ignora es que somos programadores en Python, de modo que un simple juego como el escondite puede resultarnos incluso aburrido de lo trivial que es. Pero, si lo que quiere es jugar, adelante: contemos hasta 30 y que se esconda donde quiera.

Dentro del virtualmente infinito maletín de herramientas de que dispone Python hay un módulo que tenemos la obligación de dominar y que hoy presentaremos con una demostración básica. Nos referimos al módulo os, que proporciona una serie de utilidades para acceder a la funcionalidad del sistema operativo subyacente. Y lo elegante de Python es que lo hace de un modo portable, es decir, facilitando una interfaz común tanto si lo que hay por debajo es un Linux, Mac o Windows, pudiendo ejecutar, sin ninguna (o poca) modificación, el mismo código en cualquier plataforma.

Abrimos la caja de herramientas con una sencilla línea:

import os

Para recorrer el arbol de ficheros invocaremos al generador os.walk(). A grandes rasgos, un generador no es más que una función especial que nos va a devolver una serie de valores sobre los que podremos iterar. En esencia, os.walk() funciona del siguiente modo:

La función recorre, paso a paso, como un incansable turista, todo el sistema de archivos recursivamente, a la par que va tomando fotos cada vez que se detiene en un directorio.

Cada foto que toma consiste en una tupla compuesta de tres elementos:

– la ruta del directorio en el que se halla (un string)
– los subdirectorios que cuelgan de ahí (una lista de strings)
– los ficheros que ve en ese nivel (otra lista de strings)

Una vez tomada esa foto, se sumerge en un nuevo subdirectorio, tomando una nueva instantánea de lo que ve. Y así sucesivamente hasta haber recorrido exhaustivamente el sistema de archivos a partir de donde comenzó su trabajo.

De modo que ya podemos esbozar una solución a nuestro problema: por cada tupla devuelta, consultemos el último elemento (el tercero, que tiene por índice 2) y comprobemos si en esa lista está Wally.txt. En caso afirmativo devolvemos el directorio desde el que se tomó la foto, contenido en el primer elemento de la tupla (de índice 0).

La iteración a lo largo y ancho del generador es simple:

for foto in os.walk('c:\\'):

Como argumento, facilitamos el directorio desde el que comenzará la búsqueda. Hemos necesitado escapar la barra inclinada hacia atrás (el backslash) precediéndola de otra igual, pues ese símbolo tiene un significado especial para Python. Si estás en un Linux o Mac, con la barra hacia delante, /, no necesitas tomar esa precación, obviamente.

Una vez hemos tomado cada foto, echamos un vistazo a ver si en ella está Wally. En foto[2] se almacena una lista con cada uno de los ficheros que hay en ese nivel. Buscar a Wally.txt es inmediato:

if 'Wally.txt' in foto[2]:

Si encontramos a Wally.txt el programa debe devolver el directorio en el que se encuentra, que es donde ha sido tomada la foto, es decir, el string almacenado en foto[0]:

print(foto[0])
break

Con el break interrumpimos la iteración para que no siga buscando más una vez el no tan huidizo, como quería hacernos creer, Wally.txt ha sido localizado.

El código hasta el momento:

import os

for foto in os.walk('c:\\'):
     if 'Wally.txt' in foto[2]:
          print(foto[0])
          break

Como remate, podemos unir la ruta donde ha sido localizado con el nombre del fichero. Para esto bastaría una simple concatenación:

print(foto[0] + '\\' + 'Wally.txt')

Pero no sería una solución elegante, pues presupone que estamos ante un Windows. En aras de la portabilidad, dejemos entonces que Python decida cuál es el símbolo apropiado según en qué plataforma se ejecute:

print(os.path.join(foto[0], 'Wally.txt'))

La función join() del módulo os.path se encarga de unir inteligentemente ambos términos.

Contamos: 1, 2, 3, …, 30.

import os

for foto in os.walk('c:\\'):
     if 'Wally.txt' in foto[2]:
          print(os.path.join(foto[0], 'Wally.txt'))
          break

Que, en mi caso particular, devuelve:

>>>
c:\aqui\nadie\me\encontrara\Wally.txt

(Que te creías tú eso…)

Asómate a la documentación oficial de Python y echa un vistazo a la funcionalidad del módulo os. Esta solo ha sido una visita de cortesía, aunque prometo repetirla de cuando en cuando.

Javier Montero Gabarró


http://elclubdelautodidacta.es/wp/2016/02/python-buscando-a-wally-txt/


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Safe Creative #1602036431538

Python – Troceando desde el lado izquierdo

Objetivo: mostrar algunas técnicas avanzadas de slicing de listas in situ.

Todo pythonista que se precie debe estar familiarizado con los mecanismos básicos de slicing de secuencias y sus usos idiomáticos más comunes. Si aún no tienes claro qué significan expresiones como secuencia1[2:5] o secuencia2[::-1], puedes encontrar en el blog diversos artículos que te ayudarán a aclarar este concepto.

Dentro de la gama de secuencias de Python se encuentran las magníficas listas, que tienen una interesante propiedad que las diferencia claramente del resto de secuencias: su mutabilidad, la capacidad de ser modificadas in situ.

Si observamos, con mucha paciencia y dedicación, cómo se comportan las listas en su hábitat natural, podremos ser testigos excepcionales de conductas raramente presenciadas por el ser humano: el slicing en el lado izquierdo de una instrucción de asignación, algo que otras secuencias, debido a su inmutabilidad, no pueden exhibir.

Consideremos, por ejemplo, la lista siguiente:

lista = ['a', 'b', 'c', 'd', 'e']

Ya sabemos que la operación de trocear cualquier secuencia crea un nuevo objeto, pero no afecta al objeto original:

>>> lista[2:4]   # una rebanada de longitud 2
['c', 'd']
>>> lista
['a', 'b', 'c', 'd', 'e']

>>> lista [::-1]    #invirtiendo la lista
['e', 'd', 'c', 'b', 'a']
>>> lista
['a', 'b', 'c', 'd', 'e']

Si lo que queremos es modificar la lista original para que tome como valor el resultado del slicing, simplemente reasignamos la lista para que, a partir de ese momento, referencie al nuevo objeto:

>>> lista = lista[2:4]
>>> lista
['c', 'd']

Este es el procedimiento general para modificar cualquier tipo de secuencia, sea mutable (listas) o inmutable (tuplas y strings). Nótese que en ningún caso hay modificación in situ del objeto inicial, tan sólo referenciamos una dirección de memoria completamente diferente en la que se halla ubicada la rebanada resultante.

Rebanadas en el lado izquierdo

Como hemos dicho, las listas son unas entidades especiales; su valorada mutabilidad permite una operación muy peculiar: trocear en el lado izquierdo de una instrucción de asignación.

Presta mucha atención a lo siguiente

>>> lista = ['a', 'b', 'c', 'd', 'e']
>>> id(lista)
45699960
>>> lista[2:4] = ['C', 'D']

Hemos generado una rebanada de longitud 2 a la cual asignamos otra lista de longitud también 2. Previamente hemos recuperado la dirección de memoria referenciada por lista simplemente a título informativo. ¿Qué crees que habrá sucedido con la lista original?

>>> lista
['a', 'b', 'C', 'D', 'e']
>>> id(lista)
45699960

Hemos modificado la lista original directamente in situ. Observa que se mantiene la misma referencia de memoria.

Guarda bien esta técnica para cuando necesites modificar con una sola operación un bloque contiguo de elementos en una lista.

¿Qué ocurre cuando la longitud de la rebanada difiere de la longitud de la lista que asignamos en el lado derecho? Supongamos que queremos meter tres elementos en un hueco de sólo dos:

>>> lista = ['a', 'b', 'c', 'd', 'e']
>>> lista[2:4] = ['C', 'D', 'E']
>>> lista
['a', 'b', 'C', 'D', 'E', 'e']

La lista acomoda perfectamente en el hueco los tres elementos, sustituyendo los apropiados y empujando los elementos restantes, aumentando el tamaño de la lista, sin sobreescribirlos.

Es fácil entender entonces la situación contraria, cuando la longitud de la lista del lado derecho es inferior a la del troceo:

>>> lista = ['a', 'b', 'c', 'd', 'e']
>>> lista[2:4] = ['C']
>>> lista
['a', 'b', 'C', 'e']

Llevado al extremo, si en el lado derecho ponemos una lista nula, de longitud cero, encontramos una técnica interesante que nos permite eliminar de un plumazo un bloque contiguo de elementos en una lista:

>>> lista[2:4] = []
>>> lista
['a', 'b', 'e']

En todos estos ejemplos, los dos elementos cortados originales han desaparecido. En su lugar aparecen los nuevos, extendiendo o acortando la lista según sea el caso.

Rebanadas de longitud cero

Un paso particularmente interesante resulta cuando provocamos rebanadas de longitud cero en el lado izquierdo. Una rebanada de longitud cero es aquella que devuelve una lista sin elementos. Observa un ejemplo:

>>> lista = ['a', 'b', 'c', 'd', 'e']
>>> lista[2:2]
[]

El slicing anterior retorna todos los elementos entre el índice 2 y, sin incluir, el índice 2 también, es decir, todos los elementos comprendidos entre el índice 2 y el 1. Naturalmente, no se devuelve nada, pues estamos cortando hacia delante y no podemos retroceder. El mismo resultado se obtendría, evidentemente, con lista[2:1] o lista [2:0].

Veamos cómo se comporta en este caso la operación de asignación:

>>> lista = ['a', 'b', 'c', 'd', 'e']
>>> lista[2:2] = ['C']
>>> lista
['a', 'b', 'C', 'c', 'd', 'e']

Como no hay nada que sustituir, el slicing nulo simplemente cumple su función de inserción en el punto indicado por el valor a la izquierda de los dos puntos.

Un error típico conceptual hubiera sido intentar algo como esto:

lista[2:2] = 'C'

La operación de slicing devuelve una lista; por lo tanto, en el lado derecho de la igualdad debe figurar una lista también:

lista[2:2] = ['C']

Naturalmente, obtendríamos el mismo resultado con el método insert:

>>> lista = ['a', 'b', 'c', 'd', 'e']
>>> lista.insert(2, 'C')
>>> lista
['a', 'b', 'C', 'c', 'd', 'e']

Sin embargo, la técnica del slicing permite, a diferencia del método insert, insertar más de un elemento en la misma operación. El objeto list no dispone de ningún método que sea capaz de hacer esto:

>>> lista = ['a', 'b', 'c', 'd', 'e']
>>> lista[2:2] = ['C', 'D', 'E']
>>> lista
['a', 'b', 'C', 'D', 'E', 'c', 'd', 'e']

Podemos situar también el cursor de inserción justo después del último elemento, lo que provocará que la lista se extienda por la derecha. ¿Cómo lo hacemos?

En nuestra lista de ejemplo de 5 elementos, el último tiene por índice 4, ya que el conteo empieza por cero. Para situarnos más allá de él, fuera ya de la lista, elegimos el valor siguiente, 5, que es precisamente la longitud de la lista. Es decir, de forma genérica, el valor que sitúa el cursor más allá de todos los elementos es len(lista).

Para provocar la rebanada nula en ese índice, podemos elegir, como segundo valor del slice, cualquier número entero, sin importar el que sea, pues la lista no existe a partir de ese índice. Todos estos trozos devuelven la rebanada nula a partir del último elemento:

lista[len(lista):0]
lista[len(lista):3]
lista[len(lista):5]
lista[len(lista):100]

También puede, sencillamente, obviarse, pues su omisión hace referencia, cuando recortamos hacia la derecha, al supuesto índice que habría más allá del último real:

lista[len(lista):]

Resulta más fácil ahora entender esta asignación, que agrega un nuevo elemento al final de la lista:

>>> lista = ['a', 'b', 'c', 'd', 'e']
>>> lista[len(lista):] = ['F']
>>> lista
['a', 'b', 'c', 'd', 'e', 'F']

Esto es equivalente a ejecutar el método append:

>>> lista = ['a', 'b', 'c', 'd', 'e']
>>> lista.append('F')
>>> lista
['a', 'b', 'c', 'd', 'e', 'F']

Curiosamente, cualquier valor mayor o igual que len(lista) generaría el mismo resultado:

>>> lista[100:] = ['F']
>>> lista
['a', 'b', 'c', 'd', 'e', 'F']

Podemos extender, por supuesto, la lista con más elementos de una sola vez, no sólo de uno en uno:

>>> lista = ['a', 'b', 'c', 'd', 'e']
>>> lista[len(lista):] = ['F', 'G']
>>> lista
['a', 'b', 'c', 'd', 'e', 'F', 'G']

El método extend realiza exactamente lo mismo:

>>> lista = ['a', 'b', 'c', 'd', 'e']
>>> lista.extend(['F', 'G'])
>>> lista
['a', 'b', 'c', 'd', 'e', 'F', 'G']

Hemos aplicado al slicing de listas en Python su misma medicina y lo hemos dejado bien troceado en conceptos simples, pero poderosos. Utilízalos a discreción y, ante todo, cuida tus dedos

Javier Montero Gabarró


http://elclubdelautodidacta.es/wp/2015/12/python-troceando-desde-el-lado-izquierdo/


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Safe Creative #1512166040787

Comparando objetos en Python

Objetivo: aprender a implementar los operadores de comparación en nuestras clases.

En el artículo anterior mostramos los cimientos de la sobrecarga de operadores en Python. Aprendimos a dar significado a la suma de objetos redefiniendo un método mágico, __add__ (dos símbolos de subrayado al comienzo y otros dos al final), en cuyo interior describíamos el funcionamiento del «+».

Hay, desde luego, más operadores que podemos sobrecargar y hoy nos ocuparemos de un conjunto específico de ellos: los de comparación.

¿Cuándo decimos que un objeto es mayor que otro? La respuesta, como programador, la tienes tú y sólo tú.

Imagina que defines una clase Persona entre cuyos atributos se encuentran su nombre, edad, altura y peso. Si pedro y luis (permíteme las minúsculas) son dos instancias de esa clase, ¿cuándo decimos que pedro es mayor que luis?

pedro > luis

Parece natural responder a esta comparación atendiendo a la edad de cada uno, pero no necesariamente tendría que ser así. Quizás prefieras basarte en su tamaño y optar por parámetros como la altura o el peso.

Comencemos creando una sencilla clase Persona:

class Persona():

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

Es preferible que la clase la crees en un fichero aparte, pues la tendremos que editar a menudo. Agrega también la creación de algunas instancias de la clase, así no habrá que volver a introducirlas cada vez que modifiquemos el código:

pedro = Persona("Pedro", 62, 1.65, 71)
luis = Persona("Luis", 40, 1.80, 82)
carmen = Persona("Carmen", 62, 1.70, 60)

Ejecuta el código completo para seguir a continuación desde el modo interactivo.

Obviamente, la comparación directa aún no tiene sentido:

>>> pedro > luis
Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    pedro > luis
TypeError: unorderable types: Persona() > Persona()

Python sabe cómo ordenar valores numéricos, o incluso cadenas de caracteres u otras secuencias, pero no tiene ni idea de cómo apañárselas ante objetos de otra índole.

Efectuamos la comparación accediendo a los atributos de cada instancia:

>>> pedro.edad > luis.edad
True
>>> pedro.peso > luis.peso
False

Para que pedro > luis tenga sentido necesitamos ampliar la clase Persona definiendo un nuevo método mágico, __gt__, en el que incluiremos la funcionalidad deseada. Las letras gt se corresponden a greater than, «mayor que», en inglés.

Si queremos que «mayor que» signifique «de más edad que», la clase Persona habría de redefinirse del siguiente modo:

class Persona():

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

    def __gt__(self, persona):
        return self.edad > persona.edad 

Con esta nueva definición, la comparación directa ya es posible:

>>> pedro > luis
True

La clave de todo esto es entender que

pedro > luis

es equivalente a

pedro.__gt__(luis)

Es decir, el método __gt__ se aplica sobre el objeto a la izquierda del símbolo >, tomando como argumento el objeto de la derecha, tal como ya explicamos al sobrecargar la suma.

El código es bien simple:

    def __gt__(self, persona):
        return self.edad > persona.edad 

self es el objeto sobre el que actúa el método (pedro) y persona el que se facilita como argumento (luis). La función simplemente compara ambas edades, devolviendo el resultado de la comparación.

Habiendo definido ya un operador de comparación, nuestra clase quedaría un tanto coja si no implementáramos los restantes: <, >=, <=, == y !=.

He aquí los métodos mágicos asociados a cada operador de comparación:

>     __gt__
<     __lt__
>=    __ge__
<=    __le__
==    __eq__
!=    __ne__

De modo que esta sería la implementación completa de la comparación en la clase:

class Persona():

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

    def __gt__(self, persona):
        return self.edad > persona.edad

    def __lt__(self, persona):
        return self.edad < persona.edad

    def __ge__(self, persona):
        return self.edad >= persona.edad

    def __le__(self, persona):
        return self.edad <= persona.edad

    def __eq__(self, persona):
        return self.edad == persona.edad

    def __ne__(self, persona):
        return self.edad != persona.edad

Ejecuta el código completo de nuevo (incluyendo la creacion de las tres instancias) para poner en práctica la totalidad de las comparaciones:

>>> luis > carmen
False
>>> pedro < carmen
False
>>> carmen >= pedro
True
>>> luis <= carmen
True
>>> carmen == pedro
True
>>> carmen != luis
True

Python dispone de un truco que evita tener que sobrecargar los seis operadores. Dado que a > b también equivale, leído de derecha izquierda, a b < a, Python es suficientemente inteligente para interpretar que, si no incluyes una declaración específica del método __lt__, el <, «menor que», no es más que un __gt__ intercambiando los argumentos. Se dice que los operadores > y < actúan «en espejo». Lo mismo sucede con los pares >= y <=, así como con == y !=.

De modo que, tan sólo definiendo tres métodos, uno por cada par de operadores «espejo», podríamos obtener la misma funcionalidad:

class Persona():

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

    def __gt__(self, persona):
        return self.edad > persona.edad

    def __ge__(self, persona):
        return self.edad >= persona.edad

    def __eq__(self, persona):
        return self.edad == persona.edad

Los operadores binarios (con dos operandos) no son los únicos susceptibles de ser sobrecargados en Python. Los unarios (como el signo negativo) o los extendidos (como el +=) también pueden servirnos para realizar curiosos trucos de magia. Pero esos los dejaremos para otro artículo…

Javier Montero Gabarró


http://elclubdelautodidacta.es/wp/2015/06/comparando-objetos-en-python/


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Safe Creative #1506254454136

Python – Sumando objetos

Objetivo: Introducción a la sobrecarga de operadores en Python.

Consideremos una operación aritmética primordial, la primera con la que nos atrevimos a coquetear, siendo unos críos, una vez aprendimos a utilizar los números para contar los objetos de nuestro mundo alrededor: la suma.

Todos sabemos sumar. Python, por supuesto, también:

>>> 2 + 3
5

Tal vez nunca te hayas parado a pensar que Python no sólo sabe sumar números:

>>> 'caperucita' + 'roja'
'caperucitaroja'

Pues sí, Python también sabe «sumar» cadenas de caracteres: es lo que se denomina concatenación.

Observa, simplemente como nota, que en este último ejemplo la operación suma no cumple la propiedad conmutativa: no es lo mismo sumar ‘caperucita’ + ‘roja’, que ‘roja’ + ‘caperucita’.

Python también sabe que hay cosas que no puede sumar. Si tratamos de engañarle con código como éste, se queja con razón:

>>> 'caperucita' + 5
Traceback (most recent call last):
  File "<pyshell#46>", line 1, in <module>
    'caperucita' + 5
TypeError: Can't convert 'int' object to str implicitly

En efecto, Python no sabe qué hacer cuando le dices que sume un número a una cadena de caracteres. Si lo que pretendíamos era concatenar ambos era preciso convertir previamente el número a string.

La suma también aparece definida para otros tipo de objetos, como por ejemplo las listas:

>>> [1, 2] + [3, 4]
[1, 2, 3, 4]

Sin embargo, Python no admite la suma de conjuntos:

>>> {1, 2} + {3, 4}
Traceback (most recent call last):
  File "<pyshell#48>", line 1, in <module>
    {1, 2} + {3, 4}
TypeError: unsupported operand type(s) for +: 'set' and 'set'

Donde quiero ir a parar es que te fijes que, para un mismo operador, el ‘+’, Python sabe cómo actuar dependiendo del tipo de objetos, los operandos, que coloquemos a la izquierda y a la derecha del operador.

Así pues, si los operandos son números, efectuará una suma aritmética; si son cadenas de caracteres las concatenará y otro tanto de lo mismo si son listas. Además, Python sabe qué «sumas» no están permitidas.

Se podía haber elegido un operador diferente a ‘+’ para la concatenación de strings, pero no sería muy adecuado, pues intuitivamente tiene pleno significado para nosotros el uso del ‘+’ para una concatenación. Sería mucho más incómodo tener que recordar un operador distinto. Y tanto más cuántos más tipos de objetos diferentes quisiéramos sumar.

Llegamos al quid de la cuestión: un mismo operador, pero muchas maneras de comportarse en función del tipo de operandos que empleemos.

A esta exquisita filosofía de diseño se le denomina sobrecarga de operadores, reflejando el hecho de que un mismo operador (en el ejemplo de hoy, la suma), «carga sobre sus espaldas» código para saber cómo responder ante operandos de distinta naturaleza.

La sobrecarga de operadores, unida a la sobrecarga de métodos, tema que abordaremos en otra ocasión, está relacionada con una de las características más refinadas de la POO y uno de sus pilares: el polimorfismo, ser capaz de comportarse de múltiples formas diferentes presentando una misma interfaz común al programador.

¿Podríamos sobrecargar aún más nuestro ya de por sí sobrecargado operador suma? Planteando la cuestión de otro modo: ¿podríamos implementar una suma de objetos pertenecientes a clases creadas por nosotros?

La respuesta es afirmativa y aprenderemos a hacerlo a continuación.

Comencemos por un ejemplo sencillo. Imaginemos que creamos una clase que representa a vectores libres en el plano caracterizados por sus dos componentes x e y. Sería intuitivamente práctico poder definir una operación suma de dos vectores.

Por ejemplo, si tenemos un vector v1 = (1, 1) y otro v2 = (5, -3), queremos definir la suma de vectores de tal modo que v1 + v2 sea el esperado (6, -2).

Esta sería nuestra eventual clase Vector:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

No me he complicado mucho y únicamente he incluido el inicializador, que se ocupa de crear las variables miembro x e y referenciando a los valores facilitados durante la creación del objeto.

Para que esta clase sobrecargue el operador ‘+’ y podamos hablar de suma de dos vectores necesitamos incluir en la clase un método especial:__add__. Al igual que __init__, observa que comienza y termina con dos guiones bajos, indicando que es un nombre con un significado especial para Python.

El código que incluyamos en ese método «mágico» especial determinará cómo se comportará la clase ante la operación suma.

Es importante tener muy claro algo antes. La suma objeto1 + objeto2 es equivalente a aplicar el método __add__ sobre objeto1, recibiendo como argumento objeto2. Es decir:

objeto1.__add__(objeto2)

Asegúrate de entender bien esto, pues es todo el misterio que tiene la sobrecarga de operadores: objeto1, el operando de la izquierda del +, debe tener un método __add__ que recibirá como argumento el operando de la derecha, objeto2. Esto se manifestará crucial en el ejemplo que pondremos un poco más adelante.

La clase Vector modificada sobrecargando la suma queda así:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __add__(self, v):
        sumax = self.x + v.x
        sumay = self.y + v.y
        vectorsuma = Vector(sumax, sumay)
        return vectorsuma

Analicemos con cuidado el código del método __add__:

Aparte de self, presente en todos los métodos como primer parámetro, y que referenciará al objeto actual cuando se cree (el de la izquierda del +), observa que toma otro parámetro, que hemos denominado v, que no es otra cosa sino el vector a la derecha del +. En el código de la función distinguirás como, por un lado, nos referimos a las componentes x e y del objeto propio como self.x y self.y, mientras que las del segundo operando, facilitado como argumento, son v.x y v.y.

El código suma las x con las x y las y con las y:

sumax = self.x + v.x
sumay = self.y + v.y

A continuación, invoca al constructor de la clase Vector para crear un nuevo vector, devuelto como vectorsuma, de componentes con el resultado de estas sumas:

vectorsuma = Vector(sumax, sumay)
return vectorsuma

Veamos, ahora sí, la clase al completo y un ejemplo de su utilización:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __add__(self, v):
        sumax = self.x + v.x
        sumay = self.y + v.y
        vectorsuma = Vector(sumax, sumay)
        return vectorsuma

>>> v1 = Vector(1, 1)
>>> v2 = Vector (5, -3)
>>> vectorsuma = v1 + v2
>>> vectorsuma.x
6
>>> vectorsuma.y
-2

El ejemplo que figura a continuación ilustra la aplicación de la sobrecarga del operador suma en el caso de objetos de diferentes clases.

Consideremos, por ejemplo, una clase denominada Camiseta que mantiene información sobre el color, talla y tipo de manga de ciertas camisetas deportivas. Por otro lado, la clase Pantalon registra el color y talla de pantalones de deporte.

Podríamos imaginarnos algo así:

camiseta + pantalón = un equipamiento deportivo completo

Es decir, aparecería en escena una tercera clase, Equipo, que tendría como constituyentes un objeto del tipo Camiseta y otro del tipo Pantalon.

Estas son nuestras tres clases:

class Camiseta:
    def __init__(self, color, talla, manga):
        self.color = color
        self.talla = talla
        self.manga = manga

class Pantalon:
       def __init__(self, color, talla):
        self.color = color
        self.talla = talla

class Equipo:
    def __init__(self, camiseta, pantalon):
        self.camiseta = camiseta
        self.pantalon = pantalon

Y ahora la clave. Debemos dotar de sentido a la expresión camiseta + pantalon. Para ello, recuerda lo que dijimos más arriba:

camiseta + pantalon = camiseta.__add__(pantalon)

El objeto de la izquierda del más debe contar con un método especial, __add__ que recibirá como argumento el objeto de la derecha del +.

Por lo tanto, la clase Camiseta, que es la presente a la izquierda del +, precisa definir el método __add__. Veámoslo:

def __add__(self, pantalon):
    return Equipo(self, pantalon)

Simplemente invoca al constructor de la clase Equipo para construir un nuevo objeto con la camiseta (self)y pantalón facilitados.

Veamos las clases al completo junto a un ejemplo de utilización:

class Camiseta:
    def __init__(self, color, talla, manga):
        self.color = color
        self.talla = talla
        self.manga = manga

    def __add__(self, pantalon):
        return Equipo(self, pantalon)

class Pantalon:
       def __init__(self, color, talla):
        self.color = color
        self.talla = talla

class Equipo:
    def __init__(self, camiseta, pantalon):
        self.camiseta = camiseta
        self.pantalon = pantalon

>>> camiseta1 = Camiseta('blanca', 'L', 'corta')
>>> pantalon1 = Pantalon('azul', 'XL')
>>> equipo1 = camiseta1 + pantalon1
>>> equipo1.camiseta.color
'blanca'
>>> equipo1.pantalon.talla
'XL'

Observa la doble notación punto para acceder a los miembros de un objeto que están, a su vez, dentro de otro objeto.

Fíjate muy bien que sólo hemos definido el método __add__ en la clase Camiseta. Por lo tanto, si invirtiéramos el orden de los sumandos, la aplicación no funcionaría. En efecto:

>>> equipo2 = pantalon1 + camiseta1
Traceback (most recent call last):
  File "<pyshell#31>", line 1, in <module>
    equipo2 = pantalon1 + camiseta1
TypeError: unsupported operand type(s) for +: 'Pantalon' and 'Camiseta'

Esto es así porque pantalon1 + camiseta1 es equivalente a:

pantalon1.__add__(camiseta1)

Y la clase Pantalon no tiene definido ningún método __add__.

Si, además de sumar camisetas más pantalones, deseas sumar pantalones más camisetas, necesitas definir también un método __add__en la clase Pantalon (te lo dejo como ejercicio). No presupongas nunca que se cumple la propiedad conmutativa salvo que expresamente la definas.

Existen muchos más operadores que se pueden sobrecargar, pero la mecánica es similar a la aplicada aquí, salvo con otros métodos «mágicos» diferentes. Los veremos en otros artículos. Pero mi consejo es que no esperes y los busques por ti mismo, no te será difícil encontrarlos. Si has entendido bien este artículo no deberá suponerte el mínimo esfuerzo comprender su funcionamiento.

Javier Montero Gabarró


Python – Sumando objetos


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Safe Creative #1505044013911

Python – Una tortuga de brocha fina

Objetivo: presentar el módulo turtle para la creación de gráficos en Python.

Python no sólo es un lenguaje versátil, potente y elegante. Programar en Python es, desde mi opinión, una labor usualmente más gratificante y divertida que hacerlo con otros lenguajes. El artículo de hoy explora una de sus facetas particularmente lúdica, pero no por ello menos interesante y práctica.

He aprovechado para matar dos pájaros de un tiro: por un lado, tenía previsto que la siguiente entrada del blog estuviera dedicada a Python, a la par que estoy empezando a preparar otras, relacionadas con la música, que expliquen el concepto y utilidades del círculo de quintas. Para ello, requería hacer uso del siguiente gráfico:

circulo-quintas-python

Al final del artículo presentaré el código Python que genera este diagrama.

Vamos a introducirnos en el modo gráfico presentando una sencilla herramienta: una tortuga artista que podemos manejar a nuestra conveniencia y que va dejando rastro allá por donde pisa.

El módulo turtle es, además, un excelente medio para explicar a un niño en qué consiste la programación de ordenadores, pues propone una forma visual y divertida de comprobar las relaciones causa-efecto que suceden a lo largo de un programa.

Para introducir en escena a nuestra tortuguita de brocha fina necesitamos invocar, en primer lugar, al módulo turtle:

>>> import turtle

A continuación le pedimos que se muestre en la pantalla:

>>> turtle.showturtle()

Aparece una nueva ventana gráfica en la que, posicionado en su centro, se muestra un cursor con forma de punta de flecha: la tortuga pintadora.

turtle-1

Ciertamente, este cursor no se asemeja mucho a una tortuga, pero quizás este otro sí:

>>> turtle.shape("turtle")

turtle-2

Graciosa, ¿verdad? A un niño le encantaría.

Si ya has dejado atrás tu lado infantil (qué triste es decir eso), puedes restablecer su aspecto nuevamente, más discreto y menos aparatoso:

>>> turtle.shape("classic")

Lo curioso de esta tortuga es que lleva una brocha encima con la que va dejando rastro allá donde se mueve.

Para ilustrar esto, mostremos, por ejemplo, la función forward(), encargada de desplazar la tortuga hacia adelante el número de píxeles que le indiquemos, en el sentido en que apunta la flecha:

>>> turtle.forward(150)

turtle-3

Observa que hemos desplazado la tortuga 150 píxeles a la derecha (que es hacia donde apunta la flecha). En su camino, ha trazado una recta.

Una función similar, backward(), permite a la tortuga andar hacia atrás, de espaldas.

Podemos modificar la dirección en la que apunta. Las funciones left() y right(), respectivamente, giran la tortuga hacia la izquierda o la derecha el ángulo en grados que indiquemos:

>>> turtle.left(120)
>>> turtle.forward(200)

turtle-4

Hemos combinado un giro de 120 grados con un avance de 200 pixeles, provocando el trazado del segundo tramo.

Los giros de left() y right() son siempre relativos, en relación a la orientación actual. Por ejemplo, giremos otros noventa grados a la izquierda y avancemos 50 pixels en esa dirección:

>>> turtle.left(90)
>>> turtle.forward(75)

turtle-5

También podemos indicar el ángulo que forma la tortuga de una manera absoluta, es decir, no relativa a la posición actual. Para ello disponemos de la función setheading():

>>> turtle.setheading(-180)
>>> turtle.forward(100)

turtle-6

Si lo que queremos es desplazar la tortuga a un punto determinado de la ventana, utilizamos la función goto(), a la que facilitamos las coordenadas en píxeles del destino (horizontal, vertical). Ten en cuenta que el punto (0, 0), ubicado en el centro de la ventana, lugar donde se posiciona inicialmente la tortuga, representa el origen de coordenadas:

>>> turtle.goto(-25, 100)

turtle-7

Observa que la tortuga se ha desplazado directamene al punto especificado. Presta atención a la punta de la flecha: continúa apuntando a donde lo hacía antes del último tramo.

Para regresar al lugar de origen puedes hacer un goto(0, 0) o decirle directamente que se marche a casa:

>>> turtle.home()

turtle-8

La función home(), a diferencia de goto(), restablece además el sentido original con el que comenzó la tortuga su andadura.

Si estamos usando turtle para pintar interactivamente un gráfico, hay dos funciones que te vendrán de maravilla:

turtle.undo() deshace la última maniobra efectuada.

turtle.reset() limpia la ventana y restablece la tortuga a su posición de origen.

Para trazar círculos disponemos del comando circle():

>>> turtle.reset()
>>> turtle.circle(100)

turtle-9

Hemos limpiado previamente la ventana, regresando a las condiciones iniciales.

Pintar círculos tiene su pequeño truco. Es importante comprender dónde se localiza el centro de la circunferencia que dibuja la tortuga. Se halla justo al oeste de donde está mirando, es decir, noventa grados a la izquierda, y a una distancia determinada por el radio que indiquemos en la llamada a la función. En el ejemplo, con la tortuga ubicada en (0,0) y mirando exactamente hacia la derecha, es fácil entender que su centro se halla en el punto (0, 100). En ocasiones, según donde esté ubicada y dependiendo de hacia dónde mire, puede ser necesario algún sencillo cálculo trigonométrico si necesitamos determinar el centro.

La tortuguita no nos sería de mucha utilidad práctica si siempre que se moviera estuviera pintando. Necesitamos un modo de lograr que «levante» la brocha de cuando en cuando para poder desplazarla al lugar que deseemos sin emborronar la pantalla.

Para levantar la brocha de la pantalla disponemos de la función penup(). Una vez ejecutada, los movimientos de la tortuga no dejarán trazos en la ventana. Si queremos volver a pintar, basta con bajarla de nuevo con la función pendown().

Observa en el siguiente ejemplo cómo desplazamos la tortuga 100 píxeles, sin el trazo oportuno, antes de dibujar la segunda circunferencia:

>>> turtle.penup()
>>> turtle.forward(100)
>>> turtle.pendown()
>>> turtle.circle(100)

turtle-10

Podemos insertar también textos en nuestro gráfico. La función write(), a la que pasamos una cadena de caracteres, imprime texto en la posición en la que se halla la tortuga, que no se ve afectada por esta impresión, permaneciendo en el mismo lugar:

>>> turtle.reset()
>>> turtle.left(45)
>>> turtle.forward(100)
>>> turtle.write("La casa de la pradera")

turtle-11

Disponemos ya de los conocimientos necesarios para dibujar el círculo de quintas que presentamos al comienzo del artículo. Ejecuta el siguiente programa Python y observa a la tortuga dicharachera trazarlo con gracilidad:

import turtle as t

radio = 150
quintas = ("C", "G", "D", "A", "E", "B",
           "F#/Gb", "Db", "Ab", "Eb", "Bb", "F")
correccion = (20, 20, 21, 25, 29, 31,
              31, 36, 33, 31, 25, 20)

t.penup()
t.goto(0, -radio) # para que el círculo quede centrado en (0, 0)
t.pendown()
t.circle(radio)
t.penup()
t.goto(0, 0) # regresamos al centro
t.left(90)
t.pendown()

for quinta in range(12):
    t.forward(radio)
    t.penup()
    t.forward(correccion[quinta]) # separamos del círculo el punto de escritura
    t.write(quintas[quinta], font=("Arial", 10, "bold")) # negrita
    t.goto(0, 0)
    t.right(30) # giramos 30 grados (360 dividido entre 12)
    t.pendown()

t.hideturtle() #para que no se vea la tortuga en la imagen final

El primer bloque de código, justo debajo de la declaración de las tuplas, simplemente traza una circunferencia centrada en el origen de coordenadas y deja la tortuga mirando al norte.

El bucle for se ocupa, en cada iteración, de trazar cada radio, escribir la nota correspondiente, regresar al origen y girar 30 grados, dejando la tortuga en la orientación oportuna para el dibujo de otro radio en la siguiente iteración.

Las tupla quintas contiene todas las notas musicales ordenadas por quintas justas, en la secuencia en la que deberán ser pintadas por la tortuga en cada iteración. La tupla correccion no es más que una separación adicional para que el texto de cada nota quede decentemente separado del círculo a una distancia visualmente parecida. Inicialmente partí de 20 píxeles para todas las notas, reajustando a simple vista después una a una para obtener una mejor presentación. Observa también un uso más avanzado de la función write(), indicando, además del texto, las características del tipo de letra (familia, tamaño y aspecto).

Finalmente, escondemos la tortuga para poder realizar una captura de pantalla sin que aparezca incordiando entre medias.

Esta ha sido sólo una pequeña introducción a la tortuga de Python. En otros artículos exploraremos otras funciones que nos permitirán nuevas posibilidades creativas. Que no te confunda su sencillez y aspecto lúdico: puedes dibujar con ella virtualmente cualquier cosa y, con los algoritmos adecuados, puedes realizar gráficos sumamente sofisticados con muy poco código.

Javier Montero Gabarró


Python – Una tortuga de brocha fina


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Python – El poderoso inicializador


Objetivo: presentar el inicializador de objetos en Python, invocado automáticamente por el constructor para facilitar las características iniciales del objeto.

Hemos comenzado nuestra andadura en el mundo de la POO en Python presentando los conceptos esenciales de clase y objeto. La clase representa el modelo, la plantilla con la que después crearemos los objetos concretos.

Regresemos a la sencilla clase Coche con la que hemos estado trabajando en las últimas entregas:

class Coche:
    
    marca = ''
    modelo = ''
    color = ''
    numero_de_puertas = 0
    cuenta_kilometros = 0
    velocidad = 0
    arrancado = False

    def arrancar(self):
        if not self.arrancado:
            print('Roarrrr')
            self.arrancado = True
        else:
            # Dale al encendido estando el coche arrancado y escucha...
            print('Kriiiiiiiiiiicccc')
  
    def parar(self):
        self.arrancado = False
  
    def acelerar(self):
        if self.arrancado:
            self.velocidad = self.velocidad + 1

    def frenar(self):
        if self.velocidad > 0:
            self.velocidad = self.velocidad - 1

    def pitar(self):
        print('Bip Bip Bip')

    def consultar_velocimetro(self):
        return self.velocidad

    def consultar_cuenta_kilometros(self):
        return self.cuenta_kilometros

Para fabricar coches a partir de este esquema, invocamos al constructor:

>>> coche1 = Coche()

Como puedes comprobar, el valor de los atributos de datos del objeto recién creado son los que aparecen en la definición de la clase. Por ejemplo:

>>> coche1.marca
''

De modo que, para poder montarte en el coche, lo primero que habría que hacer es darle algo de forma y color:

>>> coche1.marca = "Seat"
>>> coche1.modelo = "Ibiza"
>>> coche1.color = "Blanco"
>>> coche1.numero_de_puertas = 3

Los demás campos podemos dejarlos con sus valores iniciales, no parece muy apropiado que nada más fabricar el coche se nos estampe a toda pastilla contra una pared.

Esta manera de proceder, que habría que repetir con cada coche que fabricáramos, no es, obviamente, ni práctica, ni elegante ni inteligente.

¿Por qué no facilitamos estos valores iniciales en la propia invocación al constructor?

Es decir, algo así como:

coche1 = Coche("Seat", "Ibiza", "Blanco", 3)

Mucho más elegante, menos propenso a errores y, como veremos, permitiéndonos además un mayor control sobre los valores que pueden tomar los atributos.

Para poder comunicarnos de este modo con la clase Coche necesitamos ampliar su definición incorporando un nuevo método: el inicializador.

El inicializador es un método particular cuya función es obvia: ocuparse de los rudimentos iniciales necesarios a la hora de construir el objeto. Toma la forma siguiente, obsérvala con atención:

def __init__(self, argumento1, argumento2, ..., argumentoN):
    código

La palabra init va precedida y seguida por dos símbolos de subrayado (dos delante y dos detrás). Esto, que puede parecer algo esotérico, es una buena convención práctica de Python para evitar posibles conflictos de nombres.

Observa que el primer argumento es self, como en cualquier otro método, que ya sabes que referencia al objeto actual (enseguida lo verás en acción). Los demás argumentos deberán cuadrar con la invocación en el constructor:

coche1 = coche(argumento1, argumento2, ..., argumentoN)

Recuerda que self no se indica dentro del constructor.

Estamos en condiciones de introducir código en el inicializador que, en este caso, es bien simple:

    def __init__(self, marca, modelo, color, puertas):
        self.marca = marca
        self.modelo = modelo
        self.color = color
        self.numero_de_puertas = puertas

En la declaración de parámetros he empleado puertas en lugar de numero_de_puertas simplemente para ilustrar la flexibilidad que tenemos a la hora de elegir nombres de parámetros en las funciones. Naturalmente, en la última línea del código hay que utilizar el nombre real del atributo para que éste se nutra del valor facilitado.

La clase Coche quedaría entonces así:

class Coche:
    
    marca = ''
    modelo = ''
    color = ''
    numero_de_puertas = 0
    cuenta_kilometros = 0
    velocidad = 0
    arrancado = False

    def __init__(self, marca, modelo, color, puertas):
        self.marca = marca
        self.modelo = modelo
        self.color = color
        self.numero_de_puertas = puertas

    def arrancar(self):
        if not self.arrancado:
            print('Roarrrr')
            self.arrancado = True
        else:
            # Dale al encendido estando el coche arrancado y escucha...
            print('Kriiiiiiiiiiicccc')

etcétera, ...

En breve la mejoraremos aún más, pero vayamos asegurando los conceptos paso a paso.

Con esta definición, la siguiente llamada al constructor ya es lícita:

>>> coche1 = Coche("Seat", "Ibiza", "Blanco", 3)

Los IDEs (aquí IDLE) nos facilitan la vida
Los IDEs (en la imagen, IDLE) nos facilitan la vida

Lo que sucede a continuación no debe sorprenderte ahora: los argumentos «Seat», «Ibiza», «Blanco» y 3 son facilitados, respectivamente, a marca, modelo, color y puertas de __init__.

Una vez dentro de __init__, self referencia al objeto actual que se crea al invocar al constructor, es decir, coche1. De modo que interiormente __init__ realiza esto:

coche1.marca = "Seat"
coche1.modelo = "Ibiza"
coche1.color = "Blanco"
coche1.numero_de_puertas = 3

Puedes comprobar que coche1 ha sido inicializado correctamente. Por ejemplo:

>>> coche1.color
'Blanco'
>>> coche1.numero_de_puertas
3

Otro ejemplo de la inteligencia de IDLE
Otro ejemplo de la inteligencia de IDLE

Ahora sí, nuestra clase Coche empieza a tener clase de verdad. Pero aún podemos hacerla más fina…

Sabemos que las variables en Python no necesitan declararse y que se construyen en el momento en que reciben un valor. Este hecho nos permite simplificar nuestra clase. Si los atributos de datos van a ser inicializados dentro de __init__, ¿por qué molestarnos siquiera en dar valores iniciales al definir la clase? Simplifiquemos, entonces:

class Coche:
    
    cuenta_kilometros = 0
    velocidad = 0
    arrancado = False

    def __init__(self, marca, modelo, color, puertas):
        self.marca = marca
        self.modelo = modelo
        self.color = color
        self.numero_de_puertas = puertas

    def arrancar(self):
        if not self.arrancado:
            print('Roarrrr')
            self.arrancado = True
        else:
            # Dale al encendido estando el coche arrancado y escucha...
            print('Kriiiiiiiiiiicccc')

etcétera, ...

Hemos dejado sólo los atributos que expresamente no establecemos en __init__.

Aún podemos ir más allá. ¿Por qué no incluir toda la inicialización en __init__, incluso la de aquellos miembros que no se faciliten explícitamente en la invocación al constructor?

class Coche:
    
    def __init__(self, marca, modelo, color, puertas):
        self.marca = marca
        self.modelo = modelo
        self.color = color
        self.numero_de_puertas = puertas
        self.cuenta_kilometros = 0
        self.velocidad = 0
        self.arrancado = False

    def arrancar(self):
        if not self.arrancado:
            print('Roarrrr')
            self.arrancado = True
        else:
            # Dale al encendido estando el coche arrancado y escucha...
            print('Kriiiiiiiiiiicccc')

etcétera, etcétera...

Los atributos de datos, objetos también en sí (en Python, todo lo es), serán construidos en el momento de la inicialización.

Por lo general, bien sea a la hora de inicializar un objeto, o bien llegado el momento de modificar cualquiera de sus atributos de datos, es buena práctica dejar que sean los métodos quienes se encarguen de ello, en lugar de atacar a las variables miembro directamente. Dentro de un método podemos analizar el valor facilitado, comprobar que sea legítimo, corregirlo o incluso rechazarlo si no procede.

No sería muy sensato escribir código como el siguiente:

coche1.velocidad = 1000

No hay coche que pueda alcanzar 1000 Km/h. Tampoco podría estar parado y, en el instante siguiente, circular a 100 Km/h. Usando los métodos acelerar y frenar que hemos definido en la clase se garantiza un incremento o decremento de la velocidad gradual. Estos métodos, además, podrían asegurarse de que no se excediera en ningún caso un valor máximo por mucho que pisáramos el acelerador (ni mínimo cuando frenásemos, algo que ya hace el método frenar).

Tienes ya las bases suficientes para empezar a utilizar la Programación Orientada a Objetos en tus programas. Te advierto, es una vía casi sin retorno: una vez escribes tu primer programa POO, apetece poco volver a la programación estructurada tradicional.

Pero esto apenas ha empezado, aún queda mucho camino por descubrir…

Javier Montero Gabarró


Python – El poderoso inicializador


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Python – Fabricando objetos


Objetivo: presentar el concepto de Objeto o Instancia de una Clase en Python.

En el anterior artículo introdujimos las clases, auténticos pilares de la Programación Orientada a Objetos. Podemos pensar en ellas como diseños de moldes o plantillas para representar en Python (abstraer) una determinada realidad.

Aprendimos, por ejemplo, a modelar un coche:

class Coche:
    
    marca = ''
    modelo = ''
    color = ''
    numero_de_puertas = 0
    cuenta_kilometros = 0
    velocidad = 0
    arrancado = False

    def arrancar(self):
        if not self.arrancado:
            print('Roarrrr')
            self.arrancado = True
        else:
            # Dale al encendido estando el coche arrancado y escucha...
            print('Kriiiiiiiiiiicccc')
  
    def parar(self):
        self.arrancado = False
  
    def acelerar(self):
        if self.arrancado:
            self.velocidad = self.velocidad + 1

    def frenar(self):
        if self.velocidad > 0:
            self.velocidad = self.velocidad - 1

    def pitar(self):
        print('Bip Bip Bip')

    def consultar_velocimetro(self):
        return self.velocidad

    def consultar_cuenta_kilometros(self):
        return self.cuenta_kilometros

En la definición de clase nos encontramos sus características o atributos, que toman forma de variables (marca, modelo, color, etc…) y la funcionalidad propia de la clase o métodos, que nos indican qué cosas podemos hacer con ella, representados mediante funciones (arrancar(), acelerar(), frenar(), etc.).

Pero esto no es más que un esquema, un molde. Lo que queremos ahora es fabricar coches concretos, como un Seat León de cinco puertas o un Ford Fiesta de tres.

Al hecho de «fabricar» objetos a partir de un molde de clase se le denomina instanciar, y al producto obtenido se le conoce como instancias o, simplemente, objetos de esa clase.

De modo que vamos a asegurarnos que tenemos los conceptos claros: las clases son los moldes a los que recurrimos después para crear objetos concretos.

Para poner en marcha nuestra fábrica de coches virtual no necesitaremos una gran inversión en infraestructura y personal; nos bastará con ejecutar el intérprete de Python.

Para mayor comodidad, supongamos que hemos preparado ya todo el código con la definición de la clase Coche y lo hemos incluido en un fichero tal como coche.py.

La primera labor consistirá, por lo tanto, en ejecutar dicho código. En mi caso, lo abro en Idle y lo ejecuto con Run, pero tú puedes elegir cualquier otra forma de hacerlo. La cuestión es tener el intéprete interactivo con toda la definición de la clase cargada ya en memoria.

Comprobemos su existencia:

>>> Coche
<class '__main__.Coche'>

Para fabricar coches concretos necesitamos invocar a lo que se conoce como el constructor de clase, que se ocupará de crear el objeto en memoria. El rito para que haga acto de presencia es muy simple: basta con escribir el nombre de la clase seguido de un par de paréntesis.

Construyamos un primer coche:

>>> coche1 = Coche()

Típicamente los paréntesis los utilizaremos para definir valores iniciales del objeto. Más adelante, cuando presentemos el inicializador, aprenderás a usar esa característica.

Parémonos un instante a contemplar nuestra primera fabricación:

>>> coche1
<__main__.Coche object at 0x0234B7D0>

Lo que nos está diciendo el intérprete es que coche1 es un objeto de tipo Coche y está ubicado en la dirección de memoria 0x0234B7D0.

Cada objeto posee en Python un identicador único que podemos consultar con la función id():

>>> id(coche1)
37009360

En CPython, la implementación más común de Python, este identificador coincide con la dirección de memoria anterior, como podrás comprobar si te tomas la molestia de pasarlo a hexadecimal.

Ya que nos ha ido tan bien, probemos a fabricar un segundo automóvil:

>>> coche2 = Coche()

Aunque de la misma clase, se trata en efecto de un objeto diferente:

>>> id(coche2)
37634800

Para acceder a los atributos y métodos del objeto recurrimos a la notación punto, separando el nombre del objeto del atributo o método mediante un punto.

Establezcamos algunos atributos de coche1:

<code>>>> coche1.marca = "Seat"
>>> coche1.modelo = "León"
>>> coche1.color = "negro"

Y otros tantos de coche2:

coche2.marca = "Ford"
coche2.modelo = "Fiesta"
coche2.numero_de_puertas = 3

Esta forma de proceder, que puede parecer natural, no es la común. Tal como ya hemos apuntado, para dar valores iniciales a los objetos suele emplezarse la figura del inicializador, como veremos más adelante. Lo importante ahora es comprender bien el concepto.

Comprobemos los atributos de coche1:

>>> coche1.marca
'Seat'
>>> coche1.modelo
'León'
>>> coche1.color
'negro'
>>> coche1.numero_de_puertas
0
>>> coche1.cuenta_kilometros
0
>>> coche1.velocidad
0
>>> coche1.arrancado
False

Aprecia como los atributos que no hemos inicializado expresamente toman los valores que tenían en la definición de la clase.

Juguemos ahora un poco con los métodos, funciones que definen lo que podemos hacer con los objetos.

Traigamos a la palestra el primero de ellos. Lo reescribo aquí por comodidad:

    def arrancar(self):
        if not self.arrancado:
            print('Roarrrr')
            self.arrancado = True
        else:
            # Dale al encendido estando el coche arrancado y escucha...
            print('Kriiiiiiiiiiicccc')

El parámetro self, tal como explicamos en el artículo anterior, es obligatorio y debe figurar el primero en la declaración del método. Hace referencia al objeto en cuestión sobre el que se aplicará el método.

Para invocar el método, recurrirmos nuevamente a la notación punto, pasando entre paréntesis los argumentos que requiere el método en particular sin contar a self. Como en arrancar sólo existe self, a efectos prácticos es como si no tuviera ninguno:

>>> coche1.arrancar()
Roarrrr

Aunque no esté presente en la invocación, se está pasando implícitamente como self el nombre del método que llama a arrancar(), en este caso, coche1. Observa el código del método. Lo primero que hace es comprobar que el coche no está arrancado, en cuyo caso imprime «Roarrr», cambiando a continuación el atributo arrancado a True. Verifiquemos que en efecto se ha realizado esto último:

>>> coche1.arrancado
True

Naturalmente, coche2 permanece completamente ajeno a estas operaciones, pues arrancar no ha actuado sobre él, sino sobre coche1. Comprobemos que sigue detenido:

>>> coche2.arrancado
False

Estando coche1 arrancado, si intentamos arrancarlo de nuevo obtendremos un chirriante y desagradable ruido propio de forzar el motor de arranque sin necesidad:

>>> coche1.arrancar()
Kriiiiiiiiiiicccc

Revisemos algunos métodos más de la definición de la clase Coche:

    def parar(self):
        self.arrancado = False
  
    def acelerar(self):
        if self.arrancado:
            self.velocidad = self.velocidad + 1

    def frenar(self):
        if self.velocidad > 0:
            self.velocidad = self.velocidad - 1

Experimentemos con ellos. Cada vez que aceleramos, la velocidad del vehículo se incrementa en una unidad:

>>> coche1.acelerar()
>>> coche1.velocidad
1
>>> coche1.acelerar()
>>> coche1.velocidad
2

Para detener el vehículo, primero lo frenamos completamente:

>>> coche1.frenar()
>>> coche1.frenar() # Al tener velocidad 2 aplicamos dos veces el método
>>> coche1.velocidad
0

Y, a continuación, sacamos la llave de contacto:

>>> coche1.parar()
>>> coche1.arrancado
False

Es muy importante que estudies con cuidado en estos ejemplos cómo hemos accedido desde los métodos a los atributos del objeto a través de self. En la siguiente lección, cuando presentemos el inicializador, le daremos a esta operatividad un carácter esencial.

Javier Montero Gabarró


Python – Fabricando objetos


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Python – El concepto de Clase

Objetivo: presentar en Python el concepto de clase, pilar sobre el que está construido todo el paradigma de la programación orientada a objetos.

Ha llegado el momento de que introduzcamos la Programación Orientada a Objetos en Python. Supondremos que esta es la primera vez que te acercas a esta metodología y que desconoces completamente o tienes nociones imprecisas de lo que son las clases y los objetos, de modo que comenzaremos por lo esencialmente básico para que no arrastres lagunas conforme el tema vaya adquiriendo más complejidad.

Por cuestiones de economía me he permitido autoplagiarme empleando los ejemplos, e incluso las mismas palabras, que utilicé en la introducción a la POO que escribí en la serie dedicada a Java. Naturalmente, nuestros caminos divergirán tan pronto como comencemos a picar código concreto en Python.

El tratamiento empleado por Python para la POO difiere de manera importante en algunos aspectos al utilizado en otros lenguajes como Java, C++ o C#. Aunque podríamos habernos acercado a este tema directamente de un modo más pythonista, he preferido que nuestros primeros pasos sean sólidos, apoyados en un marco conceptualmente universal, como el compartido por los lenguajes de programación anteriormente citados.

¿Preparado para una nueva manera de resolver problemas?

Voy a proponerte un sencillo juego. Sal a la calle, mira a tu alrededor y contempla el mundo de un modo particular.

Veo veo… ¿Qué ves?
Una cosita… ¿Y qué cosita es?

Para empezar, quiero que identifiques qué tipos de objetos ves…

Puedes, por ejemplo, encontrarte coches, bares, semáforos o personas, por citar sólo unos cuantos tipos. Date cuenta de que, cuando digo tipos de objetos, hablo de un modo conceptual y general. Si te resulta chocante referirte a una persona como un objeto, prueba a sustituir la expresión por tipos de entidades, en su lugar.

Tipo y clase son dos términos sinónimos, de modo que vamos a cambiar «tipos de objetos» por «clases de objetos», aproximándonos un poco más al concepto que da pie al artículo de hoy.

¿Qué clases de objetos vemos? Coches, bares, semáforos, personas…

La siguiente transformación lingüística consiste en simplificar clases de objetos por simplemente clases:

¿Qué clases vemos? Coches, bares, semáforos, personas…

Y ahora sí, nos sumergimos plenamente en la terminología de la programación orientada a objetos:

Tenemos la clase Coche, la clase Bar, la clase Semáforo, la clase Persona

Poco interesan ahora los detalles concretos. No importa si esa persona es Marta, está en el bar «Casa Manolo», si tiene un Seat Panda o si el semáforo está en rojo. Estamos pensando en términos clasificatorios.

Vamos a adentrarnos ahora en el interior de cada una de estas clases.

¿Cómo son los coches? ¿Qué características pueden diferenciar uno de otro?

Por ejemplo, podríamos indicar la marca, el modelo, el color, la cilindrada, etc.

A las características las vamos a denominar atributos, término muy utilizado en la programación orientada a objetos.

¿Qué atributos tiene la clase Bar?

Todo bar tiene un nombre, una ubicación, un estado (si está abierto o cerrado), una lista de precios,…

Como atributos de la clase Semáforo podríamos indicar su ubicación y estado (rojo, verde o ámbar).

La clase Persona podría definir como atributos el nombre, sexo, edad, estado civil, altura, etc.

A continuación pensemos en el comportamiento de estas clases. Preguntémonos qué cosas hacen, qué tipo de acciones pueden realizar.

Un coche puede arrancar, detenerse, girar a la izquierda, acelerar, frenar, encender sus luces. Un semáforo puede cambiar de estado. Un bar puede abrir, cerrar, servirte una cerveza, cobrarla, modificar la lista de precios. Una persona puede hablar, dormir, conducir un coche, tomarse una cerveza en un bar.

En terminología de la programación orientada a objetos, a estas funciones que determinan el comportamiento de una clase se las conoce como métodos.

Empleando un lenguaje puramente pythonista, tanto a los atributos como a los métodos aquí descritos se les conoce como simplemente atributos. Para diferenciar unos de otros, Python emplea el término atributos de datos para referirse a los datos característicos de la clase. No obstante, al menos mientras continuemos presentando los conceptos básicos, seguiremos distinguiendo aquí entre atributos y métodos.

Sabemos qué es una clase y que está compuesta de atributos y métodos. Nuestra labor ahora consistirá en modelar en Python estas clases.

Modelar no es otra cosa sino crear una abstracción que represente de algún modo una determinada realidad.

Para crear en Python la clase Coche comenzamos usando la palabra reservada class, seguida del nombre de la clase y del símbolo de dos puntos:

class Coche:
    instrucción 1
    instrucción 2
    ...
    instrucción n

La clase más simple de todas no haría nada:

class Coche:
    pass

La sentencia pass pasa completamente, no hace absolutamente nada. Es útil en contextos en los que se requiere la presencia de al menos una instrucción, hasta que la inspiración acuda y coloquemos código sustituto.

Típicamente, indentados en la definición, introduciremos los atributos y métodos de que consta la clase Coche.

El nombre Coche lo escribimos con la primera letra en mayúsculas, pues es práctica común entre la POO que los nombres de clases empiecen así.

Introduzcamos algunos atributos:

class Coche:

    marca = ''
    modelo = ''
    color = ''
    numero_de_puertas = 0
    cuenta_kilometros = 0
    velocidad = 0
    arrancado = False

Hemos utilizado, provisionalmente, valores iniciales para los atributos: la cadena vacía para los tipo string, cero para los numéricos y False para el booleano, pero podrían haber sido otros cualesquiera. En el momento en que comencemos a fabricar objetos de esta clase todos estos atributos podrán recibir su valor concreto y particular.

Definamos ahora los métodos de la clase Coche:

class Coche:
    
    marca = ''
    modelo = ''
    color = ''
    numero_de_puertas = 0
    cuenta_kilometros = 0
    velocidad = 0
    arrancado = False

    def arrancar(self):
        pass
  
    def parar(self):
        pass
  
    def acelerar(self):
        pass

    def frenar(self):
        pass

    def pitar(self):
        pass

    def consultar_velocimetro(self):
        pass

    def consultar_cuenta_kilometros(self):
        pass

Observa que los métodos se implementan exactamente igual que las funciones. Como tales, junto a la palabra clave def aparece el nombre del método seguido de unos paréntesis con la declaración de parámetros formales, finalizando con dos puntos e indentando el cuerpo del método a continuación. Al igual que las funciones ordinarias, los métodos podrán o no devolver un valor.

Fíjate en self, un parámetro que debe figurar siempre al comienzo de todo método. Su significado quedará completamente claro en el siguiente artículo, cuando expliquemos el concepto de Objeto. Por el momento, como primera toma de contacto, que te vaya sonando que self hace referencia al objeto concreto sobre el que se está invocando el método.

La elección de self como nombre del parámetro es arbitraria, podría servir cualquier otro término. Aquí nos sumaremos a la denominación favorita entre la comunidad pythonista.

Hemos rellenado el cuerpo de los métodos con la instrucción nula pass, pero seamos algo atrevidos y escribamos algo de código:

class Coche:
    
    marca = ''
    modelo = ''
    color = ''
    numero_de_puertas = 0
    cuenta_kilometros = 0
    velocidad = 0
    arrancado = False

    def arrancar(self):
        if not self.arrancado:
            print('Roarrrr')
            self.arrancado = True
        else:
            # Dale al encendido estando el coche arrancado y escucha...
            print('Kriiiiiiiiiiicccc')
  
    def parar(self):
        self.arrancado = False
  
    def acelerar(self):
        if self.arrancado:
            self.velocidad = self.velocidad + 1

    def frenar(self):
        if self.velocidad > 0:
            self.velocidad = self.velocidad - 1

    def pitar(self):
        print('Bip Bip Bip')

    def consultar_velocimetro(self):
        return self.velocidad

    def consultar_cuenta_kilometros(self):
        return self.cuenta_kilometros

Intenta comprender el código aunque aún no hayamos explicado la notación punto ni el uso de self. No te preocupes, en absoluto, si te surgen dudas, pues todo quedará claro en el siguiente artículo. Lo importante ahora es que te quedes con los conceptos de clase, atributo y método.

La siguiente podría ser una primera aproximación simple al modelado de una persona:

class Persona:
    
    sexo = ''
    nombre = ''
    edad = 0
    coche = Coche()   # El coche que conduce esa persona
  
    def saludar(self):
        print('Hola, me llamo', self.nombre)

    def dormir(self):
        print('Zzzzzzzzzzz')

    def obtener_edad(self):
        return self.edad

Fíjate en que uno de los atributos es precisamente de la clase Coche que acabamos de definir:

coche = Coche() # El coche que conduce esa persona

No confundas el nombre del atributo (coche, en minúsculas), con el nombre de la clase (Coche, con la primera en mayúsculas). Recuerda que para Python son identificadores distintos. Nuevamente, no te preocupes si no comprendes la sentencia, que hace mención a la construcción explícita de un objeto de tipo Coche. El propósito es simplemente ilustrar que entre los atributos de una clase, además de tipos comunes (números, strings, listas, etc…), pueden figurar objetos de cualquier clase, incluso de las creadas por nosotros mismos.

Te propongo como ejercicio que te entretengas modelando las otras clases indicadas al comienzo, u otras de tu elección, para asentar estos conceptos primarios.

Las entidades no tienen por qué ser tangibles, necesariamente. El siguiente código modela nuestras emociones:

class Emocion:
    emocion = ''
  
    def obtener_emocion(self):
        return self.emocion
    
    def cambiar_emocion(self, nueva_emocion):
        self.emocion = nueva_emocion

Observa el método cambiar_emocion(), que además de self define un nuevo parámetro.

En el próximo artículo explicaremos el concepto de Objeto, gracias al cual materializaremos nuestras clases en entidades concretas, coches rugientes, personas de carne y hueso o sentidas emociones. Hasta entonces, bienvenido al apasionante mundo de la programación orientada a objetos.

Javier Montero Gabarró


Python – El concepto de Clase


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Python – Acceso a las variables de entorno

Objetivo: mostrar cómo acceder con Python a las variables de entorno del sistema.

En ocasiones puede ser conveniente que nuestros programas o scripts conozcan cierta información sobre el entorno en el que se están ejecutando. Las variables de entorno constituyen un medio ampliamente utilizado para facilitar este tipo de comunicación.

Comencemos agregando algunas variables nuevas a nuestro entorno antes de ejecutar Python:

javier@menta ~ $ export BANDA="Jethro Tull" COMIDA="gazpacho"

Vamos a utilizar el intérprete interactivo de Python para ejecutar código un tanto manipulador y «pelota», como si se tratase de un falso amigo que finge tus aficiones sólo para obtener algo de ti.

javier@menta ~ $ python3
Python 3.4.0 (default, Apr 11 2014, 13:05:11) 
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

Python dispone de un módulo que facilita el acceso a funciones del sistema operativo construyendo una abstracción independiente de la plataforma, favoreciendo así la portabilidad del código. Aunque no puede decirse que esto se consiga en el 100% de los casos, debemos admitir que los resultados son impresionantes. El módulo en cuestión es os, de modo que nuestra primera tarea consiste en traerlo a la palestra:

>>> import os

El hecho simple de importar os hace que tengamos inmediatamente a nuestra disposición un diccionario, os.environ, con una copia de todas las variables de entorno en forma de parejas clave:valor.

Por ejemplo, supongamos que queremos consultar cuál es nuestro directorio de usuario (variable HOME).:

>>> os.environ["HOME"]
'/home/javier'

Naturalmente, también tenemos a nuestra disposición las dos variables que creamos al comienzo:

>>> print("No hay nada mejor en la vida que escuchar un disco
 de {} mientras se saborea un buen {}, 
¿no crees?".format(os.environ["BANDA"], os.environ["COMIDA"]))

No hay nada mejor en la vida que escuchar un disco de Jethro Tull
 mientras se saborea un buen gazpacho, ¿no crees?
>>>

¿Cómo es posible, si son mi banda y comida favoritas!

En las raras ocasiones en las que tu aplicación quiera modificar el entorno, puede ser tentador (y recomendable) atacar directamente al diccionario. Por ejemplo:

>>>os.environ["BANDA"] = "Deep Purple"

Técnicamente hablando, este tipo de actuación sólo estará disponible en aquellas plataformas que permitan llamadas a la función putenv() (que es ejecutada automáticamente cuando se modifica os.environ). Naturalmente, estas alteraciones sólo estarán disponibles para el proceso actual y subprocesos derivados. No esperes que sobrevivan una vez haya finalizado tu aplicación Python.

Javier Montero Gabarró


Python – Acceso a las variables de entorno


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Python – Un poco de orden, por favor

Objetivo: mostrar cómo ordenar los elementos de una lista.

Benditas sean las listas. ¡Qué sería de nosotros, pythonistas, sin su admirable flexibilidad! He perdido la cuenta de los problemas que he podido resolver con elegancia apoyándome simplemente en estas sólidas estructuras.

Vamos a ampliar el repertorio de las posibilidades que nos ofrecen las listas. Nos ocuparemos hoy de ordenarlas.

Tomemos, por ejemplo, la siguiente lista numérica:

>>> a = [7, -1, 5, 3]

Ordenarla resulta tan sencillo como aplicar el método sort(), disponible en los objetos de tipo lista:

>>> a.sort()
>>> a
[-1, 3, 5, 7]

Bien simple. No obstante hay algo que debes tener siempre presente: esta modificación es «in situ», atacando directamente al objeto original, sin crear una copia.

Si lo que quieres es generar una nueva lista ordenada, pero sin afectar la original, puedes hacer uso de la función sorted(), que toma como argumento una secuencia y devuelve otra diferente ordenada.

>>> a = [7, -1, 5, 3]
>>> b = sorted(a)
>>> a
[7, -1, 5, 3]
>>> b
[-1, 3, 5, 7]

Mucho cuidado con hacer esto:

>>> a = [7, -1, 5, 3]
>>> b = a.sort()

Naturalmente, a se modificaría, ordenándose. ¿Pero qué crees que valdría b?

>>> print(b)
None

Nada. b no vale nada.

Si no entiendes por qué sucede esto, no te pierdas la lectura del artículo Python – Hace falta valor, en el que se desentraña el misterio.

Lo bueno de la función sorted() es que el argumento puede ser una secuencia en general, no solamente una lista. Así, objetos no mutables, como las tuplas, que no disponen del método sort(), podrían beneficiarse de su utilización.

La única limitación de sort() y sorted() es que los elementos han de ser comparables. Si no, Python difícilmente podrá deducir su orden.

La siguiente lista, con tipos diferentes, no podrá ser ordenada por sort():

>>> listamixta = [1, 'a', 5, 'casa']
>>> listamixta.sort()
Traceback (most recent call last):
  File "<pyshell#83>", line 1, in <module>
    listamixta.sort()
TypeError: unorderable types: str() < int()

Ordenar una lista en sentido inverso, de mayor a menor, es igualmente fácil haciendo uso de un argumento opcional, reverse:

>>> a = [7, -1, 5, 3]
>>> a.sort(reverse = True)
>>> a
[7, 5, 3, -1]

La función sorted() dispone también de la misma posibilidad:

>>> a = [7, -1, 5, 3]
>>> sorted(a, reverse = True)
[7, 5, 3, -1]

Observa que, en este último ejemplo, a no ha modificado su valor, puesto que sorted() ha creado un objeto diferente:

>>> a
[7, -1, 5, 3]

Voy a plantearte ahora un problema interesante que servirá de preludio al argumento que presentaremos a continuación y que aumentará sobremanera la potencia de nuestras ordenaciones.

Imagina que queremos ordenar alfabéticamente la siguiente lista:

>>> frutas = ['pera', 'Manzana', 'fresa']

Observa que he comenzado en mayúsculas la palabra Manzana.

El método sort(), estrictamente, cumple su función:

>>> frutas.sort()
>>> frutas
['Manzana', 'fresa', 'pera']

Las letras mayúsculas se almacenan internamente con un código más bajo que las correspondientes minúsculas, de modo que Manzana aparece antes que fresa, pese a que tal vez no fuera eso lo que nos gustaría.

¿Cómo hacer para que la ordenación no tenga en cuenta que la M está en mayúsculas y la trate como si fuera minúscula, pero dejando que aparezca en el resultado tal como fue escrita?

Imagina que podemos crear un tratamiento temporal previo que procese cada término conviertiéndolo completamente en minúsculas para que luego sort trabaje sobre ese resultado, pero sin olvidar cuáles eran los términos originales.

Esto se logra con un nuevo argumento, key:

>>> frutas = ['pera', 'Manzana', 'fresa']
>>> frutas.sort(key = str.lower)
>>> frutas
['fresa', 'Manzana', 'pera']

Y ahora sí, fresa aparece antes que Manzana.

Presta atención: key recibe como valor el nombre de una función que requiera un único argumento. El valor devuelto por la función será utilizado después como base de trabajo para la ordenación.

El método lower(), que se aplica a los objetos de tipo str, strings, convierte una cadena de caracteres toda en minúsculas. Observa que he dicho el nombre de una función, por eso lower se muestra sin paréntesis en el argumento key.

Otro ejemplo. Reorganicemos nuestra macedonia, pero esta vez de modo que las frutas aparezcan ordenadas de acuerdo a su longitud:

>>> frutas = ['pera', 'Manzana', 'fresa']
>>> frutas.sort(key = len)
>>> frutas
['pera', 'fresa', 'Manzana']

Como sabes, la función len devuelve el número de elementos de una secuencia, esto es, el número de letras de que se compone un string, en nuestro caso. Ese total será tomado como criterio para la ordenación.

Podemos utilizar key para saltarnos la limitación que nos impedía ordenar listas mixtas.

>>> listamixta = [1, 'a', 5, 'casa']
>>> listamixta.sort(key = str)
>>> listamixta
[1, 5, 'a', 'casa']

La función str convierte un objeto en string, de modo que los números ya serán comparables con el resto de valores y sort() podrá realizar su trabajo. Observa que la lista resultado sigue siendo mixta; la conversión a string sólo se ha realizado a nivel interno.

Podemos ir más allá y crear incluso nuestras propias funciones para utilizarlas en sort().

Por ejemplo, esta sencilla función invierte un string:

>>> def invertir(cadena):
  return cadena[::-1]

>>> invertir('pimiento')
'otneimip'

Si no entiendes cómo trabaja esta función, echa un vistazo al artículo El mundo al revés.

Vamos a aprovecharla entonces como criterio de ordenación para que sort() ordene la lista atendiendo a la última letra de cada palabra, en lugar de la primera, tal como haría por omisión.

>>> planetas = ['mercurio', 'venus', 'tierra', 'marte']
>>> planetas.sort(key = invertir)
>>> planetas
['tierra', 'marte', 'mercurio', 'venus']

Fíjate por dónde, nuestro planeta el primero…

La función sorted(), como cabría esperar, dispone también del argumento key.

Interesante. Las posibilidades creativas son inmensas. Prácticamente significa que puedes hacer que Python ordene una lista o, por lo general, cualquier secuencia, por cualquier criterio que puedas imaginar. Es lo que me encanta de Python: este lenguaje rezuma creatividad lo mires por donde lo mires.

Javier Montero Gabarró


Python – Un poco de orden, por favor


El texto de este artículo se encuentra sometido a una licencia Creative Commons del tipo CC-BY-NC-ND (reconocimiento, no comercial, sin obra derivada, 3.0 unported)


El Club del Autodidacta


Consulta el índice completo de artículos relacionados con Python.

Uso de cookies

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.plugin cookies

ACEPTAR
Aviso de cookies