Kitabı oku: «El gran libro de Python», sayfa 3
Las cadenas de texto
Las cadenas de texto en Python están representadas por una secuencia de caracteres Unicode de longitud arbitraria, encerrados entre comillas simples, comillas, tres comillas simples o tres comillas.
Una cadena de texto es un objeto de tipo str:
y sus elementos están ordenados, lo que significa que a cada uno de ellos se le asocia un número entero llamado índice, que vale 0 para el elemento más a la izquierda y aumenta en una unidad progresivamente para el resto, yendo ordenadamente de izquierda a derecha.
Por este motivo, las cadenas de caracteres pertenecen a la categoría de las secuencias, la cual comprende todos los tipos integrados que representan contenedores ordenados de longitud arbitraria (cadenas, listas y tuplas).
El método str.index() devuelve el índice de la primera aparición del elemento pasado como argumento:
También es posible completar la operación inversa, es decir, obtener un elemento de la cadena de caracteres utilizando como palabra clave el índice.
Esta operación se denomina indexación (indexing) y se lleva a cabo mediante la siguiente sintaxis:
Otra operación que podemos realizar con los índices es la fragmentación (slicing), que permite obtener los elementos de una cadena comprendidos entre dos índices arbitrarios:
Las operaciones de indexación y de fragmentación son habituales en todos los objetos que pertenecen a la categoría de las secuencias. Esta categoría se encuentra dentro de otra más genérica, la de los objetos iterables, de los cuales hablaremos en la sección Objetos iterables, iteradores y contexto de iteración al final de este capítulo. Los objetos iterables soportan una operación denominada de desempaquetado (unpacking), que consiste en asignar los elementos del objeto a etiquetas, del modo siguiente:
Veamos otras operaciones que podemos realizar con las cadenas de caracteres:
Los objetos de tipo str no pueden ser modificados, y por este motivo se dice que son objetos inmutables:
Las listas
Las listas también pertenecen a la categoría de las secuencias, pero, a diferencia de las cadenas, pueden contener objetos de algún tipo. Se representan insertando los elementos entre corchetes, separados por una coma:
Como ya sabemos, las listas pueden ser modificadas. Por este motivo se dice que son objetos mutables.
Las tuplas
Otros tipos de objetos parecidos a las listas son las tuplas. Estas también pertenecen a la categoría de las secuencias y pueden contener objetos de cualquier tipo, pero, a diferencia de las listas, son inmutables, y se representan insertando los elementos entre paréntesis en lugar de entre corchetes:
Los diccionarios
Los diccionarios son contenedores que tienen como elementos parejas clave:valor. Se representan insertando los elementos entre llaves, separados por una coma:
A diferencia de los objetos que pertenecen a la categoría de las secuencias, los diccionarios no son contenedores ordenados, por lo cual su búsqueda se efectúa por clave y no por índice:
Los conjuntos
Los conjuntos son contenedores no ordenados de objetos únicos e inmutables, y tienen las mismas propiedades que los conjuntos matemáticos. Los conjuntos se representan insertando los elementos entre llaves y separándolos con una coma:
Los conjuntos son objetos mutables:
Sin embargo, los elementos de un conjunto deben ser objetos inmutables:
Funciones y clases integradas
Ya hemos visto alguna clase y función integrada, como type y id(). Veamos ahora otros ejemplos en acción para poder ir apreciando la potencia de los objetos integrados de Python:
Un primer vistazo a la librería estándar y al concepto de módulo
Probablemente hemos oído decir que Python tiene las pilas incluídas (batteries included). Esta frase la utilizan los pythonistas para decir que, una vez instalado Python, ya disponemos de todo cuanto necesitamos para ser productivos, puesto que, además de contar con objetos integrados de altísimo nivel, disponemos también de una librería estándar (standard library) que lo abarca prácticamente todo.
La librería estándar de Python está estructurada en módulos, cada uno de los cuales tiene un entorno de uso concreto. Por ejemplo, el módulo math nos permite trabajar con las matemáticas, el módulo datetime, con la fecha y la hora, entre otros. Para importar un módulo se utiliza la palabra clave import, y después, como para todos los otros objetos, podemos acceder a sus atributos mediante el delimitador punto:
Podemos utilizar import en combinación con la palabra clave from para importar desde el módulo solo los atributos que nos interesan, evitando así tener que escribir el nombre del módulo cada vez.
Otros módulos de la librería estándar, que utilizaremos en múltiples ocasiones en este libro, son datetime, sys y os. El módulo datetime, como ya hemos dicho, nos permite trabajar con la fecha y la hora:
El módulo sys se ocupa de los objetos vinculados con el intérprete y la arquitectura de nuestro ordenador:
El módulo os, del cual hablaremos en la sección Ejercicio final de este capítulo, proporciona el soporte para interactuar con el sistema operativo.:
Los entornos de uso de la librería estándar no se detienen aquí. Existen módulos que proporcionan el soporte para acceder a internet y controlar el navegador web:
Otros módulos proporcionan una interfaz para los archivos comodines (wildcard):
entre muchos otros.
Esta ha sido una muy breve introducción a la librería estándar, de la cual hablaremos de nuevo en este libro. Si no podéis esperar, podéis echar un vistazo a la documentación online, que encontraréis en la página web http://docs.python.org/3/library/index.html.
Definir las funciones
Las funciones se definen mediante la instrucción def:
Si no se encuentra la instrucción return, la función devuelve implícitamente el objeto None:
Hablaremos de las funciones de forma detallada en el Capítulo 3.
Definir las clases
Los tipos de datos básicos son objetos increíblemente útiles que nos permiten ser inmediatamente productivos y escribir gran parte del código sin sentir la necesidad de disponer de objetos de otro tipo. Pero cada uno de nosotros tiene sus propias exigencias, y quizás desearía utilizar objetos que le faciliten al máximo el trabajo.
Supongamos, por ejemplo, que queremos escribir una aplicación que solicite al usuario completar una acción de registro insertando sus datos, como nombre y apellido. En este caso, estaría bien tener los objetos con las carácterísticas de una persona, que tengan, por ejemplo, como atributos precisamente un nombre, un apellido y un método que restituyera el tiempo pasado desde el momento del registro. Para hacerlo, el lenguaje dispone de la instrucción class. Esta permite crear un objeto llamado clase o tipo, mediante el cual podemos crear otros objetos denominados instancias de la clase. Si hacemos memoria, recordaremos que ya hemos presentado estos conceptos en la sección Tipos de datos básicos, dedicada precisamente a las instancias de los tipos de datos básicos.
Todas las instancias de una clase tienen los mismos atributos, por lo que, por ejemplo, podemos crear una clase que defina a una persona genérica y utilizar sus instancias para representar a los usuarios que se registran en nuestra aplicación:
Las instancias de una clase MyClass se denominan también objetos de tipo MyClass, por lo que diremos que las instancias p1 y p2 del tipo Person son objetos de tipo Person.
El método _ _init_ _() inicia y termina con dos guiones bajos, por lo que, como ya hemos dicho en la sección Tipos de datos básicos, es un método especial; de hecho, es llamado automáticamente por Python justo después de la creación de la instancia con el objetivo de inicializarla. El mecanismo es el siguiente: cuando Python encuentra la instrucción p1 = Person('Tim', 'Peters'), primero crea la instancia, después llama al método _ _init_ _() y le pasa automáticamente la instancia que acaba de crear como primer argumento y, por último, devuelve la instancia asignándola a p1. Por tanto, en realidad Python nos evita hacer manualmente la llamada Person._ _init_ _(p1, 'Tim', 'Peters'). El primer parámetro de _ _init_ _(), normalmente denominado self, está presente también en la definición del método Person.since(). De hecho, la llamada a este método mediante una instancia, como en el caso de p2.since(), equivale a la llamada Person.since(p2), por lo que también en este ejemplo la instancia se pasa automáticamente al método como primer argumento y se asigna a self.
La función integrada isinstance() nos dice si un objeto es una instancia de una clase:
Si no tenéis una base en programación orientada a objetos y no os queda claro todo cuanto hemos dicho acerca del concepto de clase y de su entorno de uso, no os preocupéis: el Capítulo 5 está dedicado por completo a estos argumentos.
Un primer vistazo a los archivos
Los archivos en Python se gestionan mayormente con la función integrada open(). Dicha función permite crear un objeto archivo (file object), es decir, un objeto mediante el cual podemos interactuar con el archivo que reside en el sistema de archivos, que llamaremos archivo subyacente. Los atributos del objeto archivo nos proporcionan información como el nombre del archivo y la codificación que se utiliza para leerlo:
Dichos atributos también nos permiten interactuar con el archivo subyacente, por ejemplo, para leer su contenido:
Podemos obtener el contenido del archivo también como una lista de líneas, mediante el método readline():
Como podemos observar, las líneas son cadenas que terminan con \n. Esta representación no corresponde a la secuencia de los dos caracteres \ y n, sino a un carácter único:
Esto forma parte de un conjunto de caracteres, denominados caracteres de control, los cuales no tienen asociado ningún símbolo gráfico. Hablaremos de ello en profundidad en el próximo capítulo. De momento solo nos interesa saber que \n se utiliza para representar un salto de línea, denominado en inglés newline. Por tanto, al leer el contenido de un archivo, cada salto de línea está representado por \n, y cada \n dentro de una cadena se ve como un salto de línea cuando esta se escribe en un archivo o se muestra en pantalla con print():
Podemos ejecutar operaciones en las líneas de un archivo de manera muy sencilla. De hecho, cuando en un bucle for se repite un archivo, se accede automáticamente a sus líneas, una a una:
Los archivos tienen un modo de apertura, como el de lectura o escritura. Para especificar dicho modo, se pasa a open() como segundo argumento una cadena. La cadena "w" indica escritura (writing mode), mientras que la cadena "r" indica lectura (reading mode). Si no se especifica ningún modo, por defecto el archivo se abre en modo lectura:
Damos por terminada nuestra introducción a los archivos. Hablaremos de nuevo de ellos con mayor profundidad en el Capítulo 3.
La función integrada print()
La forma general de la función integrada print() es la siguiente:
Si no se le pasa ningún objeto, imprime una línea en blanco:
En cambio, si se le pasa uno o más objetos obj_i, los imprime separándolos con la cadena sep, que por defecto es un espacio, y terminando la impresión con la cadena end, que por defecto es un salto de línea:
Básicamente, la función print() solo crea la cadena:
para después pasarla al método write() del objeto asignado al parámetro file. Por tanto, y puesto que por defecto tenemos file=sys.stdout, la siguiente print():
equivale a esta llamada al método sys.stdout.write():
Así, si como archivo no utilizamos la salida estándar sino un archivo físico:
el código equivalente a la print() es el siguiente:
El parámetro flush sirve para forzar el vaciado del buffer inmediatamente después de la impresión. Por defecto, tenemos flush=False, lo que significa que el contenido del buffer no se escribe inmediatamente en el archivo:
En cambio, si escribimos flush=True, el buffer se vacía en cada impresión:
Hablaremos del buffering en la sección Los archivos del Capítulo 3 y en el Apéndice C. Si quisiéramos efectuar un elevado número de impresiones en un archivo en lugar de en pantalla, sería genial poder evitar pasar cada vez el archivo a la print() como argumento. Podemos hacerlo cambiando temporalmente sys.stdout, dado que este se asigna por defecto al parámetro file:
Obtener información sobre los objetos
El término introspección se utiliza para referirse a la capacidad de obtener información sobre los objetos. Python tiene muchísimas herramientas para hacer introspección, muchas de las cuales ya hemos visto, como la clase integrada type y la función integrada id():
las funciones integradas hasattr() y dir():
las funciones integradas isinstance() y callable():
y la palabra clave in:
Más adelante veremos muchos otros, como la palabra clave is, que podemos utilizar para saber si dos objetos son idénticos:
Profundizaremos en el concepto de identidad dentro de poco. Por el momento concentrémonos en dos importantes herramientas para la introspección que se utilizan principalmente en el modo interactivo: las funciones integradas dir() y help().
La función integrada dir()
La función integrada dir(), como ya hemos dicho, devuelve una lista de los nombres de los atributos más significativos de un objeto:
La finalidad de la función integrada dir() no es la de comprobar si un objeto tiene un atributo concreto, pues no todos los atributos del objeto aparecen en la lista devuelta por la función:
Para comprobar si un objeto tiene un atributo indicado debemos utilizar hasattr():
El ámbito principal de uso de dir() es la ayuda al programador durante la fase de escritura del código. Cuando programan, a menudo no recuerdan de memoria todos los atributos de un objeto, y normalmente consultan la documentación para intentar encontrar una lista de los atributos, esperando que a partir del nombre se pueda deducir el significado.
Por ejemplo, estamos escribiendo el código y necesitamos obtener la versión en mayúsculas de una cadena de texto. Sabemos que las cadenas tienen un método que devuelve una copia de la misma en mayúsculas, pero no recordamos el nombre. Es en este momento cuando dir() se convierte en nuestra mejor amiga. Iniciamos el intérprete en modo interactivo y controlamos la lista de los atributos de las cadenas de texto:
¡Premio! Es el atributo str.upper():
Por tanto, debemos utilizar la función integrada dir() como si fuera el índice de nuestra documentación. De forma parecida a un índice, dir() nos proporciona una lista de los nombres, pero no nos da información acerca de su significado.
La función integrada help() y las cadenas de documentación
Para obtener información sobre un atributo, podemos utilizar la función integrada help(). Para entender cuándo y cómo utilizarla, consideremos un ejemplo práctico. Supongamos que debemos escribir el código para poder leer las líneas del archivo siguiente:
y obtener para cada línea la suma de sus elementos (los números separados por un punto y coma). En la sección Un primer vistazo a los archivos, ya hemos podido ver que podemos leer las líneas de un archivo de una en una, como cadenas de texto. Ahora queremos saber si las cadenas tienen un método que permita separarlas según un carácter en concreto (en nuestro caso, el punto y coma). Así, abrimos la shell interactiva y escribimos dir(str). Vemos que las cadenas de texto tienen el método str.split() que parece ser el que buscábamos, por lo que intentaremos saber si se comporta tal y como queremos:
Ya sabemos que str.split() divide la cadena según los espacios. Pero nosotros queremos dividirla según el punto y coma, por lo que nos interesa saber si podemos pasar a str.split() un argumento que indique el carácter de separación. Y es en este punto cuando entra en juego la función help(). Si llamamos a help(str.split) en la shell interactiva, aparece en la pantalla la documentación del método str.split():
La documentación nos dice que str.split() acepta un primer argumento opcional que indica el elemento según el cual debe dividirse la cadena:
Ahora debemos ejecutar la suma de los elementos. En la sección Funciones y clases integradas hemos podido comprobar que la función integrada sum() realiza la suma de los elementos de una secuencia. Para poderlos sumar antes debemos convertirlos en números enteros:
Esta es una sintaxis denominada lista por comprensión (list comprehension), que nos permite hacerlo de un modo más conciso:
Podemos usar esta sintaxis para generar en línea el argumento que se debe pasar a sum():
En realidad, en este caso podemos utilizar una sintaxis todavía más sencilla, denominada expresión generadora (en inglés, generator expression), que veremos en el ejercicio final de este capítulo y de manera más detallada en el Capítulo 3:
Ahora tenemos toda la información que sirve para escribir nuestro programa:
La función integrada help() recaba la mayor parte de la información accediendo a cada una de las cadenas de texto de la documentación, denominadas cadenas de documentación (en inglés, documentation string o, de manera más sencilla, docstring). Las cadenas de documentación se utilizan para documentar los módulos, las clases, los métodos y las funciones, y se ubican en la parte superior del cuerpo de estos elementos. La PEP-0257 sugiere utilizar las triples comillas para delimitar el texto de las cadenas de documentación, incluso si ocupan una sola línea. Consideremos la siguiente función:
La primera línea del cuerpo de doubling() es una cadena de documentación y se asigna al atributo _ _doc_ _:
La función integrada help() obtiene la información del objeto accediendo a este atributo, como muestra el resultado de help(doubling):
NOTA
Observad que hemos llamado a help(doubling) y no a help(doubling()). Cuando pasamos a help() un objeto llamable no debemos utilizar el paréntesis, puesto que si no, llamamos al objeto y, en lugar de pasar a help() la función, le pasamos el objeto que ha devuelto.
Podemos obtener la documentación de los métodos de los tipos de datos básicos calificándolos tanto con la clase como con la instancia: