El mecanismo de clases de Python añade clases al lenguaje con un mínimo de sintaxis y semántica nueva. Es una mezcla de los mecanismos de clase de C++ y Modula-3. Como en los módulos, las clases de Python no ponen una barrera absoluta entre la definición y el usuario, sino que más bien se fían de la buena educación del usuario para no ``invadir la definición''. Se mantienen las características más importantes con plena potencia. El mecanismo de herencia de clases permite múltiples clases base, una clase derivada puede redefinir cualquier método de sus clases base y un método puede llamar a un método de una clase base con el mismo nombre. Los objetos pueden contener una cantidad arbitraria de datos privados.
En terminología C++, todos los miembros de la clase (datos incluidos) son públicos y todas las funciones miembro son virtuales. No hay constructores ni destructores especiales. Como en Modula-3, no existen abreviaturas para hacer referencia a los miembros del objeto desde sus propios métodos. La función método se declara con un primer argumento explícito que representa al objeto y que se proporciona implícitamente al llamar a la función. Como en Smalltalk, las clases son ellas mismas objetos, aunque en un sentido más amplio de la palabra: en Python, todos los tipos de datos son objetos. Esto proporciona la semántica para importar y renombrar. A diferencia de C++ o en Modula3, los tipos internos pueden ser la clase base para extensiones del usuario. Además, como en C++ y al contrario de Modula-3, la mayoría de operadores internos con sintaxis especial (operadores aritméticos, índices, etc.) se pueden redefinir en las clases.
A falta de una terminología universalmente aceptada para hablar de clases, haré uso ocasional de términos de Smalltalk y C++ (haría uso de términos de Modula-3, ya que la semántica orientada al objeto es más cercana a Python que la de C++, pero no espero que muchos lectores la dominen).
Los objetos tienen individualidad. Se pueden asociar múltiples nombres (y en diferentes ámbitos) al mismo objeto, lo que se conoce como ``generar alias'' en otros lenguajes. Esto no se aprecia a primera vista en Python y no hace falta tenerlo en cuenta cuando se trata con tipos inmutables (números, cadenas, tuplas...). Sin embargo los alias tienen un efecto (¡buscado!) en la semántica del código Python que involucra los objetos mutables, como listas, diccionarios y la mayoría de los tipos que representan entidades externas al programa (ficheros, ventanas...). Se suele usar en beneficio del programa, ya que los alias se comportan como punteros en algunos aspectos. Por ejemplo, pasar un objeto es poco costoso, ya que la implementación sólo pasa un puntero. Si la función modifica el objeto que pasa como argumento, el que llama a la función verá los cambios. De este modo se elimina la necesidad de tener los dos mecanismos de traspaso de argumentos de Pascal.
Antes de presentar las clases, debo contar algo sobre las reglas de alcance de Python. Las definiciones de clases realizan truquitos con los espacios nominales y se necesita saber cómo funcionan los alcances y espacios nominales para comprender plenamente lo que ocurre. Incidentalmente, el conocimiento de este tema es útil para cualquier programador en Python avanzado.
Empecemos con unas definiciones.
Un espacio nominal es una correspondencia entre nombres y objetos. La mayoría de los espacios de nombres se implementan en la actualidad como diccionarios, pero eso no se nota en modo alguno (salvo por el rendimiento) y puede cambiar en el futuro. Ejemplos de espacios nominales son:
Por cierto, utilizo la palabra atributo para referirme a cualquier
nombre que venga detrás de un punto; por ejemplo, en la expresión
z.real
, real
es un atributo del objeto z
.
Hablando estrictamente, las referencias a nombres en un módulo son referencias
a atributos. En la expresión nombremod.nombrefunc
, nombremod
es un objeto módulo y nombrefunc
es atributo suyo.
En este caso, resulta que existe una correspondencia directa entre los
atributos del módulo y los nombres globales definidos en el módulo: ¡comparten
el mismo espacio nominal9.1!
Los atributos pueden ser de sólo lectura o de lectura/escritura. En este
caso, es posible asignar valores a los atributos. Los atributos de los módulos
son de lectura/escritura: Se puede escribir "nombremod.respuesta = 42".
Los atributos de lectura/escritura se pueden borrar con la sentencia
del, por ejemplo: "del nombremod.respuesta" eliminará el
atributo respuesta del objeto denotado por nombremod
.
Los espacios nominales se crean en diferentes momentos y tienen tiempos de vida diferentes. El espacio nominal que contiene los nombres internos se crea al arrancar el intérprete de Python y nunca se borra. El espacio nominal global de un módulo se crea al leer la definición del módulo. Normalmente, los espacios nominales de los módulos también duran hasta que se sale del intérprete. Las sentencias ejecutadas por el nivel superior de llamadas, leídas desde un guion o interactivamente, se consideran parte de un módulo denominado __main__, así que tienen su propio espacio nominal (los nombres internos también residen en un módulo, llamado __builtin__).
El espacio nominal local de una función se crea cuando se llama a la función y se borra al salir de la función, por una sentencia "return"o si salta una excepción que la función no captura (en realidad, lo más parecido a lo que ocurre es el olvido). Por supuesto, las llamadas recursivas generan cada una su propio espacio nominal.
Un ámbito es una región textual de un programa Python en que el espacio nominal es directamente accesible. ``Directamente accesible'' significa en este contexto una referencia sin calificar (sin puntos) que intenta encontrar un nombre dentro de un espacio nominal.
A pesar de que los ámbitos se determinan estáticamente, se utilizan dinámicamente. En cualquier momento de la ejecución, hay al menos tres ámbitos anidados cuyos espacios nominales están directamente disponibles: El ámbito interno, el primero en el que se busca, contiene los nombres locales; los espacios nominales de las posibles funciones llamadas hasta llegar al ámbito actual, en los que se busca empezando por el ámbito llamante inmediato hasta los más externos; el ámbito intermedio, el siguiente en la búsqueda, contiene los nombres globales del módulo actual; y el ámbito externo (el último para la búsqueda) es el espacio nominal que contiene los nombres internos (__builtin__).
Si se declara un nombre como global, todas las referencias y asignaciones afectan directamente al ámbito de enmedio, que contiene los nombres globales del módulo. De otro modo, todas las variables que se encuentren fuera del ámbito interno son de sólo lectura.
Normalmente, el ámbito local hace referencia a los nombres locales de la función en curso (textualmente). Fuera de las funciones, el ámbito local hace referencia al mismo espacio nominal que el ámbito global: el espacio nominal del módulo. Las definiciones de clases colocan otro espacio nominal en el ámbito local.
Es importante darse cuenta de que los ámbitos se determinan textualmente: El ámbito global de una función definida en un módulo es el espacio nominal de este módulo, sin importar desde dónde o con qué alias se haya llamado a la función. Por otra parte, la búsqueda real de nombres se lleva a cabo dinámicamente, en tiempo de ejecución. Sin embargo, la definición del lenguaje tiende a la resolución estática de los nombres, así que ¡no te fíes de la resolución dinámica de los nombres! De hecho ya se determinan estáticamente las variables locales.
Un asunto especial de Python es que las asignaciones siempre van
al ámbito más interno. Las asignaciones no copian datos, simplemente
enlazan nombres a objetos. Lo mismo vale para los borrados: la sentencia
"del x" elimina el enlace de x
del espacio nominal al que
hace referencia el ámbito local. De hecho, todas las operaciones que
introducen nombres nuevos utilizan el ámbito local. Particularmente,
las sentencias import y las definiciones de funciones asocian el nombre del
módulo o función al ámbito local. Se puede utilizar la sentencia
global para indicar que ciertas variables residen en el ámbito
global.
Las clases introducen una pizca de sintaxis nueva, tres tipos de objeto nuevos y algo de semántica nueva.
La forma más simple de definición de clase tiene este aspecto:
class nombreClase: <sentencia-1> . . . <sentencia-N>
Las definiciones de clases, como las definiciones de funciones (sentencias def) deben ejecutarse para tener efecto (es perfectamente correcto colocar una definición de clase en una rama de una sentencia if o dentro de una función).
En la práctica, las sentencias de dentro de una definición de clase serán definiciones de funciones, pero se permite otro tipo de sentencias, lo que resulta útil en algunos casos, ya veremos esto. Las definiciones de funciones interiores a la clase suelen tener una lista de argumentos un poco especial, dictada por las convenciones de llamada a método. Esto también se explica más adelante.
Cuando se entra en una definición de clase, se genera un nuevo espacio nominal, que se utiliza como ámbito local; así que todas las asignaciones a variables locales caen dentro de este nuevo espacio nominal. En particular, las definiciones de funciones enlazan aquí el nombre de la nueva función.
Cuando se abandona una definición de clase de manera normal (se ejecuta la última línea de su código), se crea un objeto de clase. Es, sencillamente, un envoltorio del contenido del espacio nominal creado por la definición de la clase. Se verá con más detalle en la siguiente sección. El ámbito local original (el que estaba activo cuando se entró en la definición de clase) se reinstancia y el objeto clase se enlaza con el nombre de clase dado en la cabecera de la función (en el ejemplo nombreClase).
Los objetos de clase soportan dos tipos de operaciones: referencia a atributos e instanciación.
Las referencias a atributos utilizan la sintaxis estándar
que se utiliza para todas las referencias a atributos en
Python: obj.nombre
. Los nombres de atributos válidos son todos
los nombres que estaban en el espacio nominal de la clase cuando fue creada
la clase. Por lo tanto, si la definición de la clase tiene este aspecto:
class MiClase: "Simple clase de ejemplo" i = 12345 def f(self): return 'hola, mundo'
MiClase.i
y MiClase.f
son referencias a atributos válidas, que
devuelven un entero y un objeto método, respectivamente. También se puede
asignar valores a los atributos de una clase; puedes cambiar el valor de
MiClase.i
con una asignación. __doc__ es también un atributo
válido, que devuelve la cadena de documentación que
corresponde a la clase: "Simple clase de ejemplo"
.
La instanciación de clases utiliza notación de función. Basta con imaginar que el objeto clase es una función sin parámetros que devuelve una instancia nueva de la clase. Por ejemplo, siguiendo con el ejemplo anterior:
x = MiClase()
crea una nueva instancia de la clase y la asigna a la variable
local x
.
La operación de instanciación (``la llamada'' a un objeto clase) genera
un objeto vacío. Muchas clases prefieren generar los objetos en un estado
inicial conocido. Por ello, una clase puede definir un método especial
denominado __init__(), así:
def __init__(self): self.vaciar()
Cuando una clase define un método __init__(), la instanciación de clases llama automáticamente a __init__() para la instancia de clase recién creada. Así que, en el ejemplo de la Bolsa, se puede obtener una instancia de clase inicializada nueva mediante:
x = Bolsa()
Por supuesto, el método __init__() podría tener argumentos que le añadirían flexibilidad. En tal caso, los argumentos proporcionados al operador de instanciación de clase se pasan a __init__(). Por ejemplo,
>>> class Complejo: ... def __init__(self, parteReal, parteImaginaria): ... self.r = parteReal ... self.i = parteImaginaria ... >>> x = Complejo(3.0, -4.5) >>> x.r, x.i (3.0, -4.5)
¿Qué se puede hacer con los objetos instancia? Las únicas operaciones que entienden son las referencias a atributos. Hay dos tipos de nombres de atributo válidos, los atributos de datos y los métodos.
Los atributos de datos corresponden
a las ``variables de instancia'' de Smalltalk o a los ``miembros dato''
de C++. No es necesario declarar los atributos de datos. Como las variables
locales, aparecen por arte de magia la primera vez que se les asigna un valor.
Por ejemplo, si x
es una instancia de la clase MiClase
creada
anteriormente, el código siguiente mostrará el valor 16
sin
dejar rastro:
x.contador = 1 while x.contador < 10: x.contador = x.contador * 2 print x.contador del x.contador
El otro tipo de referencia a atributo de los objetos instancia son los métodos. Un método es una función que ``pertenece a'' un objeto. En Python, el término método no se limita a las instancias de una clase, ya que otros tipos de objeto pueden tener métodos también. Por ejemplo, los objetos de tipo lista tienen métodos llamados append, insert, remove, sort, etc. Sin embargo, vamos a utilizar ahora el término exclusivamente para referirnos a los métodos de objetos instancia de una clase, salvo que se indique lo contrario.
Los nombres válidos de métodos de un objeto instancia dependen de su
clase. Por definición, todos los atributos de una clase que son
objetos función definen los métodos correspondientes
de sus instancias. Así que, en nuestro ejemplo, x.f
es una referencia
a método correcta, ya que MiClase.f
es una función, pero x.i
no lo es, ya que MiClase.i
no es una función. Pero x.f
no es lo mismo que MiClase.f
- es un
objeto método, no un objeto función.
Normalmente, se llama a un método de manera inmediata, por ejemplo:
x.f()
En nuestro ejemplo, esto devuelve la cadena 'hola, mundo'
.
Sin embargo, no es necesario llamar a un método inmediatamente:
x.f
es un objeto método y se puede almacenar y recuperar
más tarde, por ejemplo:
xf = x.f while True: print xf()
mostrará "hola, mundo" hasta que las ranas críen pelo.
¿Qué ocurre exactamente cuando se llama a un método? Habrás observado
que x.f()
fue invocado sin argumento, aunque la definición
del método f especificaba un argumento. ¿Qué le ha pasado
al argumento? Desde luego, Python hace saltar una excepción cuando
se llama a una función que necesita un argumento sin especificar ninguno
(aunque no se utilice)...
En realidad, te puedes imaginar la respuesta: Lo que tienen de especial los
métodos es que el objeto que los llama se pasa como primer argumento
de la función. En nuestro ejemplo, la llamada x.f()
es totalmente
equivalente a MiClase.f(x)
. En general, llamar a un método con una lista
de argumentos es equivalente a llamar a la función correspondiente
con la lista de argumentos resultante de insertar el objeto del método
al principio de la lista de argumentos original.
Si todavía no entiendes cómo funcionan los métodos, igual te aclara las cosas un vistazo a la implementación. Cuando se hace referencia a un atributo de una instancia que no es un atributo de datos, se busca en su clase. Si el nombre denota un atributo de clase válido que resulta ser un objeto función, se crea un objeto método empaquetando juntos (punteros hacia) el objeto instancia y el objeto función recién encontrado en un objeto abstracto: el objeto método. Cuando se llama al objeto método con una lista de argumentos, se desempaqueta de nuevo, se construye una nueva lista de argumentos a partir del objeto instancia y la lista de argumentos original y se llama al objeto función con la nueva lista de argumentos.
Los atributos de datos se tienen en cuenta en lugar de los atributos método con el mismo nombre. Para evitar conflictos nominales accidentales, que podrían causar errores difíciles de rastrear en programas grandes, conviene utilizar algún tipo de convención que minimice la probabilidad de conflictos. Las convenciones incluyen poner en mayúsculas los nombres de métodos, preceder los nombre de atributos de datos con una pequeña cadena única (o sólo un guion bajo) o usar verbos para los métodos y nombres para los atributos de datos.
Los métodos pueden hacer referencia a atributos de datos tanto como los usuarios normales (los clientes) de un objeto. En otras palabras, no es posible usar las clases para implementar tipos de datos abstractos puros. De hecho, no hay nada en Python para posibilitar la ocultación de datos, todo se basa en convenciones (por otra parte, la implementación de Python escrita en C puede ocultar completamente los detalles de implementación y el control de acceso a un objeto si es necesario, lo que pueden hacer también las extensiones a Python escritas en C).
Los clientes deben utilizar los atributos de datos con cuidado. Los clientes pueden embrollar invariantes mantenidas por los métodos, chafándolas con sus atributos de datos. Observa que los clientes pueden añadir atributos de datos propios a una instancia de objeto sin afectar a la validez de los métodos, siempre que se eviten los conflictos de nombres. Nuevamente, ser coherente en los nombres puede ahorrarnos un montón de dolores de cabeza.
No hay un atajo para hacer referencia a los atributos dato (¡ni a otros métodos!) desde los métodos. Encuentro que esto, en realidad, favorece la legibilidad de los métodos, porque así no hay manera de confundir las variables locales y las variables de la instancia cuando se repasa un método.
Por convención, el primer argumento de un método se suele llamar
self
(yo mismo). No es más que una convención, el nombre self
no le dice nada a Python (sin embargo, no seguir esta convención
hace que tu código sea menos legible por otros programadores y no
sería extraño que haya navegadores de la jerarquía de clases que
suponen que la sigues).
Cualquier objeto función que es atributo de una clase define un método para las instancias de esa clase. No es necesario que la definición de la función esté textualmente encerrada en la definición de la clase. Asignar un objeto función a una variable local de la clase también vale. Por ejemplo:
# Función definida fuera de la clase def f1(self, x, y): return min(x, x+y) class C: f = f1 def g(self): return 'hola, mundo' h = g
Ahora f
, g
y h
son atributos de la clase C
que hacen referencia a objetos función, por lo que los tres son métodos
de las instancias de la clase C, siendo h
exactamente equivalente
a g
. Observa que esta práctica suele valer sólo para confundir
al lector del programa.
Los métodos pueden llamar a otros métodos utilizando los atributos método
del argumento self
:
class Bolsa: def __init__(self): self.datos = [] def agregar(self, x): self.datos.append(x) def agregarDosVeces(self, x): self.agregar(x) self.agregar(x)
Los métodos puede hacer referencia a los nombres globales del mismo modo que las funciones normales. El ámbito global asociado a un método en un método es el módulo que contiene la definición de la clase (la clase en sí nunca se utiliza como ámbito global). Aunque es raro encontrar un buen motivo para usar un dato global en un método, hay bastantes usos legítimos del ámbito global: de momento, los métodos pueden usar las funciones y los módulos importados al ámbito global, al igual que las funciones y las clases definidas en él. Normalmente, la clase que contiene el método está definida en este ámbito global. En la siguiente sección encontraremos algunas buenas razones para que un método haga referencia a su propia clase.
Por supuesto, una característica de un lenguaje no sería digna del nombre ``clase'' si no aportara herencia. La sintaxis de una definición de clase derivada tiene este aspecto:
class nombreClaseDerivada(nombreClaseBase): <sentencia-1> . . . <sentencia-N>
El nombre nombreClaseBase debe estar definido en un ámbito que contenga la definición de la clase derivada. En lugar de un nombre de clase base, se permite poner una expresión. Esto es útil cuando la clase base está definida en otro módulo,
class nombreClaseDerivada(nombreMod.nombreClaseBase):
La ejecución de la definición de una clase derivada se lleva a cabo del mismo modo que la clase base. Cuando se construye el objeto de la clase, se recuerda la clase base. Esto se utiliza para resolver referencias a atributos: si no se encuentra un atributo solicitado en la clase, se busca en la clase base. Esta regla se aplica recursivamente si la clase base es a su vez derivada.
No hay nada especial sobre la instanciación de las clases derivadas:
nombreClaseDerivada()
crea una nueva instancia de la clase. Las
referencias a métodos se resuelven de la siguiente manera: Se busca
el atributo de la clase correspondiente, descendiendo por la cadena de
clases base, si es necesario, y la referencia a método es correcta si
de este modo se obtiene un objeto función.
Las clases derivadas pueden redefinir métodos de sus clases base. Como los métodos no tienen privilegios especiales al llamar a otros métodos del mismo objeto, un método de una clase base que llama a otro método definido en la misma clase base puede acabar llamando a un método de una clase derivada que lo redefina (para los programadores de C++: todos los métodos en Python son virtuales).
Puede que una redefinición de método en una clase derivada quiera ampliar, en lugar de reemplazar, el método de la clase base del mismo nombre. Existe un modo sencillo de llamar al método de la clase base directamente: simplemente, utilizar "nombreClaseBase.nombreMétodo(self, argumentos)". Esto también les vale a los clientes, en ciertas ocasiones (observa que esto sólo funciona si la clase base está definida o se ha importado directamente en el ámbito global).
Python también aporta una forma limitada de herencia múltiple. Una definición de clase con múltiples clases base tiene este aspecto:
class nombreClaseDerivada(Base1, Base2, Base3): <sentencia-1> . . . <sentencia-N>
La única regla necesaria para explicar la semántica es la regla de resolución utilizada para las referencias a atributos de la clase. Se busca primero por profundidad y luego de izquierda a derecha. De este modo, si no se encuentra un atributo en nombreClaseDerivada, se busca en Base1, en las clases base de Base1 y, si no se encuentra, en Base2, en las clases base de ésta y así sucesivamente.
Para algunos parece más natural buscar en Base2 y en Base3 antes que en las clases base de Base1. Sin embargo, esto exigiría conocer si un atributo particular de Base1 está definido realmente en Base1 en una de sus clases base, antes de imaginarse las consecuencias de un conflicto de nombres con un atributo de la Base2. La regla de buscar primero por profundidad evita la diferencia entre atributos de la Base1 directos y heredados.
Queda claro que el uso indiscriminado de la herencia múltiple hace del mantenimiento una pesadilla, dada la confianza de Python en las convenciones para evitar los conflictos de nombre accidentales. Un problema bien conocido de la herencia múltiple es el de una clase derivada de dos clases que resulta que tienen una clase base común. A pesar de que resulta sencillo, en este caso, figurarse qué ocurre (la instancia tendrá un sola copia de las ``variables de la instancia'' o atributos de datos utilizada por la base común), no queda clara la utilidad de este tipo de prácticas.
Se pueden utilizar, de manera limitada, identificadores privados de la
clase. Cualquier identificador de la forma __fiambre
(al menos
dos guiones bajos iniciales, no más de un guion bajo final) se
reemplaza textualmente con _nombreClase__fiambre
,
donde nombreClase
es la clase en curso, eliminando los guiones
bajos iniciales. Esta reescritura se realiza sin tener en cuenta
la posición sintáctica del identificador, por lo que se puede
utilizar para definir, de manera privada,
variables de clase e instancia, métodos y variables globales.
También sirve para almacenar variables de instancia privadas
de esta clase en instancias de otras clases. Puede que se
recorten los nombres cuando el nombre reescrito tendría más de 255
caracteres. Fuera de las clases o cuando el nombre de la clase consta
sólo de guiones bajos, no se reescriben los nombres.
La reescritura de nombres pretende dar a las clases un modo sencillo de definir métodos y variables de instancia ``privados'', sin tener que preocuparse por las variables de instancia definidas por las clases derivadas ni guarrear con las variables de instancia por el código externo a la clase. Observa que las reglas de reescritura se han diseñado sobre todo para evitar accidentes; aún es posible, con el suficiente empeño, leer o modificar una variable considerada privada. Esto puede ser útil en casos especiales, como el depurador, por lo que no se ha cerrado esta puerta falsa. Hay un pequeño fallo: la derivación de una clase con el mismo nombre que su clase base hace posible el uso de las variables privadas de la clase base.
Observa que el código pasado a exec
, eval()
o
evalfile()
no considera el nombre de la clase
llamante la clase actual. Es similar al efecto de la sentencia global
,
cuyo efecto está, de igual manera, restringido al código de un fichero.
Se aplica la misma restricción a getattr()
,
setattr()
, delattr()
y también cuando se hace
referencia a __dict__
directamente.
A veces es útil tener un tipo de dato similar al ``record'' de Pascal o a la ``struct'' de C, que reúnan unos cuantos datos con nombre. Para realizar esto, se puede usar una definición de clase vacía:
class Empleado: pass juan = Empleado() # Creación de una ficha de empleado vacía # Rellenamos los campos de la ficha juan.nombre = 'Juan Pérez' juan.departamento = 'Centro de cálculo' juan.sueldo = 1000
Si hay código en Python que espere recibir un tipo abstracto de datos concreto, es posible pasarle una clase que emule los métodos de este tipo de dato, en lugar de la clase genuina. Por ejemplo, si disponemos de una función que da formato a unos datos de un objeto fichero, podemos definir una clase con los métodos read() y readline() que tome los datos de una cadena de almacenamiento temporal y pasar dicha clase como argumento.
Los objetos de métodos de instancia también tienen atributos:
m.im_self
es el objeto que tiene el método m y
m.im_func
es el objeto función correspondiente al método.
Las excepciones definidas por usuario se identifican mediante clases. Utilizando este mecanismo, es posible crear jerarquías ampliables de excepciones.
Hay dos formas válidas nuevas de sentencia raise
:
raise Clase, instancia raise instancia
En la primera forma, instancia
debe ser una instancia de
Clase o de una clase derivada de ella. La segunda forma es un
atajo de:
raise instancia.__class__, instancia
Una clase de una cláusula except captura una excepción si es de la misma clase que la excepción que ha saltado o es de una clase base de ella (al revés no; una clase derivada no captura ninguna de sus clases base). Por ejemplo, el código a continuación mostrará B, C, D, en ese orden:
class B: pass class C(B): pass class D(C): pass for c in [B, C, D]: try: raise c() except D: print "D" except C: print "C" except B: print "B"
Observa que si invertimos las cláusulas ("except B" primero), se mostraría B, B, B, ya que la primera cláusula captura todas las excepciones, por ser clase base de todas ellas.
Cuando se presenta un mensaje de error para una excepción sin capturar, se muestra el nombre de la clase, dos puntos, un espacio y, por último, la instancia convertida a cadena mediante la función interna str().
Ya debes de haberte dado cuenta de que la mayoría de los objetos contenedores se pueden recorrer con una sentencia for:
for elemento in [1, 2, 3]: print elemento for element in (1, 2, 3): print elemento for clave in {'uno':1, 'dos':2}: print clave for car in "123": print car for linea in open("myfich.txt"): print linea
Este estilo de acceso es claro, conciso y práctico. El uso de iteradores impregna y unifica Python. En la trastienda, la sentencia for llama a iter() sobre el objeto contenido. La función devuelve un objeto iterador que define el método next() que accede a los elementos del contenedor de uno a uno. Cuando no quedan más elementos, next() lanza una excepción StopIteration que indica al bucle for que ha de terminar. Este ejemplo muestra cómo funciona todo esto:
>>> s = 'abc' >>> it = iter(s) >>> it <iterator object at 0x00A1DB50> >>> it.next() 'a' >>> it.next() 'b' >>> it.next() 'c' >>> it.next() Traceback (most recent call last): File "<pyshell#6>", line 1, in -toplevel- it.next() StopIteration
Después de ver los mecanismos del protocolo iterador, es fácil añadir comportamiento
de iterador a tus propias clases. Define un método __iter__() que
devuelva un objeto con un método next(). Si la clase define
next(), __iter__() puede simplemente devolver self
:
class Delreves: "Iterador para recorrer una secuencia en orden inverso" def __init__(self, data): self.datos = datos self.indice = len(datos) def __iter__(self): return self def next(self): if self.index == 0: raise StopIteration self.indice = self.indice - 1 return self.data[self.indice] >>> for chr in Delreves('magro'): ... print car ... o r g a m
Los generadores son una herramienta simple y potente para crear iteradores. Se escriben como funciones normales, pero usan la sentencia yield siempre que quieren devolver datos. Cada vez que se llama a next(), el generador reanuda la ejecución donde se quedó (recordando todos los valores de los datos y la última sentencia en ejecutarse). Este ejemplo muestra que es inmediato crear generadores:
def delreves(data): for indice in range(len(datos)-1, -1, -1): yield datos[indice] >>> for car in delreves('Wanda'): ... print car ... a d n a W
Cualquier cosa que se pueda hacer con generadores se puede hacer también con iteradores basados en clases, como se describió en la anterior sección. Lo que hace que los generadores sean tan compactos es que su métodos __iter__() y next() se crean automáticamente.
Otra característica clave es que las variables locales y el estado de ejecución
se guardan automáticamente entre llamadas. Esto hizo que la función fuese más
fácil de escribir y mucho más clara que la solución utilizando variables de instancia
como self.indice
y self.datos
.
Además de la creación de métodos y gestión del estado automáticos, cuando terminan los generadores, hacen saltar la excepción StopIteration automáticamente. La combinación de estas características hace que la creación de iteradores no lleve más esfuerzo que el de crear una función normal.
Hay generadores simples que se pueden codificar brevemente como expresiones utilizando una sintaxis similar a las listas autodefinidas, con paréntesis en lugar de corchetes. Estas expresiones se utilizan cuando el generador lo utiliza de inmediato la función que lo contiene. Las expresiones generadoras son más compactas pero menos versátiles que las funciones generadoras completas y tienden a ser menos agresivas con la memoria que las listas autodefinidas.
Ejemplos:
>>> sum(i*i for i in range(10)) # suma de los cuadrados 285 >>> xvec = [10, 20, 30] >>> yvec = [7, 5, 3] >>> sum(x*y for x,y in zip(xvec, yvec)) # producto escalar 260 >>> from math import pi, sin >>> tabla_senos = dict((x, sin(x*pi/180)) for x in range(0, 91)) >>> palabras_sin_repes = set(palabra for linea in pagina for palabra in linea.split()) >>> mejorOpositor = max((opositor.nota, opositor.nombre) for opositor in opositores) >>> datos = 'golf' >>> list(datos[i] for i in range(len(datos)-1,-1,-1)) ['f', 'l', 'o', 'g']