Capítulo 4. Introducción a los tipos de objetos de Python

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

Este capítulo inicia nuestro recorrido por el lenguaje Python. En un sentido informal, en Python hacemos cosas con cosas.1 Las "cosas" adoptan la forma de operaciones como la suma y la concatenación, y las "cosas" se refieren a los objetos sobre los que realizamos esas operaciones. En esta parte del libro, nos centraremos en esas cosas y en lo que nuestros programas pueden hacer con ellas.

De manera algo más formal, en Python, los datos adoptan la forma de objetos, ya seanobjetos incorporados que proporciona Python u objetos que creamos utilizando clases Python o herramientas externas del lenguaje, como las bibliotecas de extensión C. Aunque concretaremos esta definición más adelante, los objetos son esencialmente trozos de memoria, con valores y conjuntos de operaciones asociadas. Como veremos, todo es un objeto en un script de Python. Incluso los números sencillos son objetos, con valores (por ejemplo, 99) y operaciones admitidas (suma, resta, etc.).

Como los objetos son también la noción más fundamental de la programación en Python, empezaremos este capítulo con un repaso de los tipos de objetos incorporados en Python. En capítulos posteriores daremos un segundo paso para completar detalles que pasaremos por alto en este repaso. Aquí, nuestro objetivo es hacer un breve recorrido para introducir los conceptos básicos.

La jerarquía conceptual de Python

Antes de pasar al código, establezcamos primero una imagen clara de cómo encaja este capítulo en el panorama general de Python. Desde una perspectiva más concreta, los programas Python pueden descomponerse en módulos, sentencias, expresiones y objetos, como se indica a continuación:

  1. Los programas se componen de módulos.

  2. Los módulos contienen declaraciones .

  3. Las declaraciones contienen expresiones .

  4. Las expresiones crean y procesan objetos.

El debate sobre los módulos del Capítulo 3introdujo el nivel más alto de esta jerarquía. Los capítulos de esta parte comienzan en la parte inferior, explorando tanto los objetos incorporados como las expresiones que puedes codificar para utilizarlos.

Pasaremos a estudiar las sentencias en la siguiente parte del libro, aunque descubriremos que existen en gran medida para gestionar los objetos que conoceremos aquí. Además, cuando lleguemos a las clases en la parte de POO de este libro, descubriremos que nos permiten definir nuevos tipos de objetos propios, tanto utilizando como emulando los tipos de objetos que exploraremos aquí. Por todo esto, los objetos incorporados son un punto de embarque obligatorio para todos los viajes a Python .

Nota

Las introducciones tradicionales a la programación suelen hacer hincapié en sus tres pilares de secuencia ("Haz esto, luego aquello"), selección ("Haz esto si aquello es cierto") y repetición ("Haz esto muchas veces"). Python tiene herramientas en las tres categorías, junto con algunas para la definición defunciones y clases. Estos temas pueden ayudarte a organizar tu pensamiento desde el principio, pero son un poco artificiales y simplistas. Expresiones como comprensión, por ejemplo, son tanto repetición como selección; algunos de estos términos tienen otros significados en Python; y muchos conceptos posteriores no parecerán encajar en absoluto en este molde. En Python, el principio unificador más fuerte son los objetos, y lo que podemos hacer con ellos. Para ver por qué, sigue leyendo.

¿Por qué utilizar tipos incorporados?

Si has utilizado lenguajes de bajo nivel como C o C++, en sabes que gran parte de tu trabajo se centra en implementar objetos -también conocidos como estructuras de datos - pararepresentar los componentes del dominio de tu aplicación. Tienes que diseñar estructuras de memoria, gestionar la asignación de memoria, implementar rutinas de búsqueda y acceso, etc. Estas tareas son tan tediosas (y propensas a errores) como parecen, y suelen distraer de los verdaderos objetivos de tu programa.

En los programas típicos de Python, la mayor parte de este trabajo sucio desaparece. Como Python proporciona potentes tipos de objeto como parte intrínseca del lenguaje, normalmente no hay necesidad de codificar implementaciones de objetos antes de empezar a resolver problemas. De hecho, a menos que necesites un procesamiento especial que los tipos incorporados no proporcionan, casi siempre es mejor que utilices un objeto incorporado en lugar de implementar uno propio. He aquí algunas razones:

  • Los objetos incorporados facilitan la escritura de programas. Para tareas sencillas, los tipos incorporados suelen ser todo lo que necesitas para representar la estructura de los dominios problemáticos. Como obtienes herramientas potentes como las colecciones (listas) y las tablas de búsqueda (diccionarios) de forma gratuita, puedes utilizarlas inmediatamente. Puedes hacer mucho trabajo sólo con los tipos de objetos incorporados de Python.

  • Los objetos incorporados son componentes de las extensiones. Para tareas más complejas, puede que necesites proporcionar tus propios objetos utilizando clases de Python o interfaces del lenguaje C. Pero como verás en partes posteriores de este libro, los objetos implementados manualmente suelen construirse sobre tipos incorporados, como listas y diccionarios. Por ejemplo, una estructura de datos de pila puede implementarse como una clase que gestiona o personaliza una lista incorporada.

  • Los objetos incorporados suelen ser más eficientes que las estructuras de datos personalizadas. Los tipos incorporados de Python emplean algoritmos de estructura de datos ya optimizados que se implementan en C para aumentar la velocidad. Aunque puedes escribir tipos de objetos similares por tu cuenta, normalmente te costará conseguir el nivel de rendimiento que ofrecen los tipos de objetos incorporados.

  • Los objetos incorporados son una parte estándar del lenguaje. En cierto modo, Python toma prestado tanto de lenguajes que dependen de herramientas incorporadas (por ejemplo, LISP) como de lenguajes que dependen de que el programador proporcione implementaciones de herramientas o marcos de trabajo propios (por ejemplo, C++). Aunque puedes implementar tipos de objetos únicos en Python, no es necesario que lo hagas para empezar. Además, como las herramientas incorporadas de Python son estándar, siempre son las mismas; los marcos de trabajo propietarios, en cambio, tienden a diferir de un sitio a otro.

En otras palabras, los tipos de objetos incorporados no sólo facilitan la programación, sino que también son más potentes y eficientes que la mayoría de los que se pueden crear desde cero. Independientemente de si implementas nuevos tipos de objetos, los objetos incorporados forman el núcleo de todo programa Python.

Tipos de datos básicos de Python

La Tabla 4-1 muestra los tipos de objetos incorporados en Python y parte de la sintaxis utilizada para codificar sus literales, es decir, las expresiones que generan estos objetos.2 Algunos de estos tipos probablemente te resultarán familiares si has utilizado otros lenguajes; por ejemplo, los números y las cadenas representan valores numéricos y textuales, respectivamente, y los objetos archivo proporcionan una interfaz para procesar archivos reales almacenados en tu ordenador.

Sin embargo, para algunos lectores, los tipos de objetos de la Tabla 4-1 pueden ser más generales y potentes de lo que están acostumbrados. Por ejemplo, verás que las listas y los diccionarios por sí solos son potentes herramientas de representación de datos que obvian la mayor parte del trabajo que realizas para soportar colecciones y búsquedas en lenguajes de nivel inferior. En resumen, las listas proporcionan colecciones ordenadas de otros objetos, mientras que los diccionarios almacenan objetos por clave; tanto las listas como los diccionarios pueden anidarse, pueden crecer y decrecer a petición, y pueden contener objetos de cualquier tipo.

Tabla 4-1. Vista previa de los objetos incorporados
Tipo de objetoEjemplo literales/creación

Númerobers

1234, 3.1415, 3+4j, 0b111, Decimal(), Fraction()

Strings

'spam', "Bob's", b'a\x01c', u'sp\xc4m'

Lists

[1, [2, 'three'], 4.5], list(range(10))

Dictionarios

{'food': 'spam', 'taste': 'yum'}, dict(hours=10)

Tuples

(1, 'spam', 4, 'U'), tuple('spam'), namedtuple

Files

open('eggs.txt'), open(r'C:\ham.bin', 'wb')

Configuras

set('abc'), {'a', 'b', 'c'}

Otros tipos de núcleo

Booleanos, tipos, None

Unidad de programa tipos

Funciones, módulos, clases (Parte IV, Parte V, Parte VI)

Tipos relacionados con la aplicación

Código compilado, seguimiento de pila(Parte IV, Parte VII)

También mostradas en la Tabla 4-1, las unidades de programa como funciones, módulos y clases -que conoceremos en partes posteriores de este libro- también son objetos en Python; se crean con sentencias y expresiones como def, class, import y lambda y pueden pasarse libremente por los scripts, almacenarse dentro de otros objetos, etc. Python también proporciona un conjunto de tipos relacionados con la implementación, como los objetos de código compilado, que suelen interesar más a los creadores de herramientas que a los desarrolladores de aplicaciones; también los exploraremos en partes posteriores, aunque con menos profundidad debido a sus funciones especializadas.

A pesar de su título, la Tabla 4-1 no está realmente completa, porque todo lo que procesamos en los programas Python es un tipo de objeto. Por ejemplo, cuando realizamos una comparación de patrones de texto en Python, creamos objetos patrón, y cuando realizamos scripts de red, utilizamos objetos socket. Estos otros tipos de objetos se crean generalmente importando y utilizando funciones de módulos de bibliotecas -por ejemplo, en , en los módulos re y socket para patrones y sockets- y tienen un comportamiento propio.

Sin embargo, solemos llamar tipos de datos principales a los demás tipos de objetos de la Tabla 4-1, porque están integrados en el lenguaje Python, es decir, existe una sintaxis de expresión específica para generar la mayoría de ellos. Por ejemplo, cuando ejecutas el siguiente código con caracteres entre comillas:

>>> 'spam'

estás, técnicamente hablando, ejecutando una expresión literal que genera y devuelve un nuevo objeto cadena . Existe una sintaxis específica del lenguaje Python para crear este objeto. Del mismo modo, una expresión envuelta en corchetes hace una lista, una entre llaves hace un diccionario, y así sucesivamente. Aunque, como veremos, en Python no hay declaraciones de tipos, la sintaxis de las expresiones que ejecutas determina los tipos de objetos que creas y utilizas. De hecho, las expresiones de generación de objetos como las de la Tabla 4-1 son generalmente donde se originan los tipos en el lenguaje Python.

Y lo que es igual de importante, una vez que creas un objeto, vinculas su conjunto de operaciones para siempre: sólo puedes realizar operaciones de cadena con una cadena y operaciones de lista con una lista. En términos formales, esto significa que Python está tipado dinámicamente, un modelo que realiza un seguimiento de los tipos por ti automáticamente en lugar de requerir código de declaración, pero también está fuertemente tipado, una restricción que significa que sólo puedes realizar en un objeto operaciones que sean válidas para su tipo.

Estudiaremos en detalle cada uno de los tipos de objetos de la Tabla 4-1 en próximos capítulos. Pero antes de entrar en detalles, empecemos echando un vistazo rápido a los objetos principales de Python en acción. El resto de este capítulo proporciona una vista previa de las operaciones que exploraremos en mayor profundidad en los capítulos siguientes. No esperes encontrar aquí la historia completa: el objetivo de este capítulo es sólo abrirte el apetito e introducir algunas ideas clave. Aun así, la mejor forma de empezar es empezar, así que vamos a meternos de lleno en algo de código real.

Números

Si en el pasado has programado o escrito , algunos de los tipos de objetos de la Tabla 4-1 te resultarán familiares. El conjunto de objetos principales de Python incluye los sospechosos habituales: números enteros que no tienen parte fraccionaria, números de coma flotante que sí la tienen, y tiposmás exóticos: números complejos con partes imaginarias, decimalescon precisión fija, racionales con numerador y denominador, y conjuntos completos. Los números incorporados son suficientes para representar la mayoría de las cantidades numéricas -desde tu edad hasta tu saldo bancario-, pero hay más tipos disponibles como complementos de terceros.

Aunque ofrece algunas opciones más sofisticadas, los tipos numéricos básicos de Python son, bueno, básicos. Los números en Python admiten las operaciones matemáticas normales . Por ejemplo, el signo más (+) realiza la suma , una estrella (*) se utiliza para la multiplicación, y dos estrellas (**) se utilizan para la exponenciación :

>>> 123 + 222                    # Integer addition
345
>>> 1.5 * 4                      # Floating-point multiplication
6.0
>>> 2 ** 100                     # 2 to the power 100, again
1267650600228229401496703205376

Fíjate en el último resultado: El tipo entero de Python 3.X proporciona automáticamente precisión extra para números grandes como éste cuando es necesario (en 2.X, un tipo entero largo separado maneja números demasiado grandes para el tipo entero normal de forma similar). Por ejemplo, puedes calcular 2 a la potencia 1.000.000 como un entero en Python, pero probablemente no deberías intentar imprimir el resultado: con más de 300.000 dígitos, ¡puedes esperar un rato!

>>> len(str(2 ** 1000000))       # How many digits in a really BIG number?
301030

Esta forma de llamada anidada funciona desde dentro hacia fuera: primero convierte el número del resultado ** en una cadena de dígitos con la función integrada str , y luego obtiene la longitud de la cadena resultante con len. El resultado final es el número de dígitos. str y len funcionan con muchos tipos de objetos; veremos más sobre ambos a medida que avancemos.

En Python anteriores a las versiones 2.7 y 3.1, cuando empieces a experimentar con números en coma flotante, es probable que te encuentres con algo que puede parecer un poco raro a primera vista:

>>> 3.1415 * 2                   # repr: as code (Pythons < 2.7 and 3.1)
6.2830000000000004
>>> print(3.1415 * 2)            # str: user-friendly
6.283

El primer resultado no es un error; es un problema de visualización. Resulta que hay dos formas de imprimir cada objeto en Python: con total precisión (como en el primer resultado mostrado aquí), y de forma fácil de usar (como en el segundo). Formalmente, la primera forma se conoce como as-code de un objeto repr, y la segunda es su forma amigable str. En los Python más antiguos, el punto flotante repr muestra a veces más precisión de la que cabría esperar. La diferencia también puede importar cuando pasemos a utilizar clases. Por ahora, si algo te parece raro, prueba a mostrarlo con una declaración de llamada a una función incorporada print.

Mejor aún, actualízate a Python 2.7 y a la última versión 3.X, en las que los números en coma flotante se muestran de forma más inteligente, normalmente con menos dígitos extraños; como este libro se basa en Python 2.7 y 3.3, ésta es la forma de presentación que mostraré a lo largo del libro para los números en coma flotante:

>>> 3.1415 * 2                   # repr: as code (Pythons >= 2.7 and 3.1)
6.283

Además de las expresiones, hay un puñado de módulos numéricos útiles que vienen con Python: los módulos no son más que paquetes de herramientas adicionales que importamos para utilizarlas:

>>> import math
>>> math.pi
3.141592653589793
>>> math.sqrt(85)
9.219544457292887

El módulo math contiene herramientas numéricas más avanzadas como funciones, mientras que el módulo random realiza la generación de números aleatorios y selecciones aleatorias (aquí, a partir de una lista de Python codificada entre corchetes: una colección ordenada de otros objetos que se presentarán más adelante en este capítulo):

>>> import random
>>> random.random()
0.7082048489415967
>>> random.choice([1, 2, 3, 4])
1

Python también incluye objetos numéricos más exóticos -como números complejos, de precisión fija y racionales, así como conjuntos y booleanos- y el dominio de extensión de código abierto de terceros tiene aún más (por ejemplo, matrices y vectores, y números de precisión extendida). Aplazaremos la discusión de estos tipos hasta más adelante en este capítulo y en el libro.

Hasta ahora, hemos estado utilizando Python como una simple calculadora; para hacer mejor justicia a sus tipos incorporados, pasemos a explorar cadenas.

Cuerdas

Las cadenas son utilizadas para registrar tanto información textual (tu nombre, por ejemplo) como colecciones arbitrarias de bytes (como el contenido de un archivo de imagen). Son nuestro primer ejemplo de lo que en Python llamamos una secuencia: unacolección de otros objetos ordenada posicionalmente. Las secuencias mantienen un orden de izquierda a derecha entre los elementos que contienen: sus elementos se almacenan y obtienen por sus posiciones relativas. En sentido estricto, las cadenas son secuencias de cadenas de un carácter; otros tipos de secuencias más generales son las listas y las tuplas, que veremos más adelante.

Operaciones de secuencia

Como secuencias, las cadenas admiten operaciones que asumen un orden posicional entre los elementos. Por ejemplo, si tenemos una cadena de cuatro caracteres codificada entre comillas (normalmente de la variedad simple), podemos verificar su longitud con la función incorporada len y obtener sus componentes con expresiones de indexación:

>>> S = 'Spam'           # Make a 4-character string, and assign it to a name
>>> len(S)               # Length
4
>>> S[0]                 # The first item in S, indexing by zero-based position
'S'
>>> S[1]                 # The second item from the left
'p'

En Python, los índices se codifican como desplazamientos desde el principio, por lo que empiezan por 0: el primer elemento está en el índice 0, el segundo en el índice 1, etc.

Observa cómo asignamos aquí la cadena a una variable llamada S. Entraremos en detalle en cómo funciona esto más adelante (especialmente en el Capítulo 6), pero las variables de Python nunca necesitan ser declaradas de antemano. Una variable se crea cuando le asigna un valor, se le puede asignar cualquier tipo de objeto, y se sustituye por su valor cuando aparece en una expresión. También debe haber sido asignada previamente en el momento en que utilices su valor. A efectos de este capítulo, basta con saber que necesitamos asignar un objeto a una variable para guardarlo para su uso posterior.

En Python, también podemos indexar hacia atrás, desde el final: los índices positivos cuentan desde la izquierda, y los índices negativos cuentan hacia atrás desde la derecha:

>>> S[-1]                # The last item from the end in S
'm'
>>> S[-2]                # The second-to-last item from the end
'a'

Formalmente, un índice negativo simplemente se suma a la longitud de la cadena, por lo que las dos operaciones siguientes son equivalentes (aunque la primera es más fácil de codificar y menos fácil de equivocarse):

>>> S[-1]                # The last item in S
'm'
>>> S[len(S)-1]          # Negative indexing, the hard way
'm'

Fíjate en que podemos utilizar una expresión arbitraria entre corchetes, no sólo un número literal codificado; en cualquier lugar en el que Python espere un valor, podemos utilizar un literal, una variable o cualquier expresión que deseemos. De este modo, la sintaxis de Python es completamente general.

Además de la indexación posicional simple, las secuencias también admiten una forma más general de indexación conocida como "rebanada", que es una forma de extraer una sección entera (rebanada) en un solo paso. Por ejemplo:

>>> S                     # A 4-character string
'Spam'
>>> S[1:3]                # Slice of S from offsets 1 through 2 (not 3)
'pa'

Probablemente, la forma más fácil de pensar en los trozos es que son una forma de extraer una columna entera de una cadena en un solo paso. Su forma general, X[I:J], significa "dame todo lo que hay en X desde el desplazamiento I hasta el desplazamiento J, pero sin incluirlo". El resultado se devuelve en un nuevo objeto. La segunda de las operaciones anteriores, por ejemplo, nos da todos los caracteres de la cadena S desde los desplazamientos 1 a 2 (es decir, de 1 a 3 - 1) como una nueva cadena. El efecto es cortar o "analizar" los dos caracteres del medio.

En un corte, el límite izquierdo es por defecto cero, y el derecho es por defecto la longitud de la secuencia que se está cortando. Esto da lugar a algunas variaciones de uso habituales:

>>> S[1:]                 # Everything past the first (1:len(S))
'pam'
>>> S                     # S itself hasn't changed
'Spam'
>>> S[0:3]                # Everything but the last
'Spa'
>>> S[:3]                 # Same as S[0:3]
'Spa'
>>> S[:-1]                # Everything but the last again, but simpler (0:-1)
'Spa'
>>> S[:]                  # All of S as a top-level copy (0:len(S))
'Spam'

Observa en el penúltimo comando cómo se pueden utilizar también desplazamientos negativos para dar límites a los trozos, y cómo la última operación copia efectivamente toda la cadena. Como aprenderás más adelante, no hay ninguna razón para copiar una cadena, pero esta forma puede ser útil para secuencias como las listas.

Por último, como secuencias, las cadenas también admiten la concatenación con el signo más (unir dos cadenas en una nueva cadena) y la repetición (hacer una nueva cadena repitiendo otra):

>>> S
'Spam'
>>> S + 'xyz'             # Concatenation
'Spamxyz'
>>> S                     # S is unchanged
'Spam'
>>> S * 8                 # Repetition
'SpamSpamSpamSpamSpamSpamSpamSpam'

Observa que el signo más (+) significa cosas distintas para objetos distintos: suma para los números y concatenación para las cadenas. Ésta es una propiedad general de Python que llamaremos polimorfismo más adelante en el libro: en suma, el significado de una operación depende de los objetos sobre los que se opera. Como verás cuando estudiemos la tipificación dinámica, esta propiedad de polimorfismo explica gran parte de la concisión y flexibilidad del código Python. Como los tipos no están limitados, una operación codificada en Python puede funcionar automáticamente con muchos tipos de objetos distintos, siempre que tengan una interfaz compatible (como la operación +). Esta resulta ser una gran idea en Python; aprenderás más sobre ella más adelante en nuestro recorrido por .

Inmutabilidad

Observa también en los ejemplos anteriores que no cambiábamos la cadena original con ninguna de las operaciones que ejecutábamos sobre ella. Cada operación con cadenas está definida para producir una nueva cadena como resultado, porque las cadenas son inmutables en Python: no se pueden modificar en su lugar después de crearlas. En otras palabras, nunca puedes sobrescribir los valores de los objetos inmutables. Por ejemplo, no puedes cambiar una cadena asignando a una de sus posiciones, pero siempre puedes construir una nueva y asignarle el mismo nombre. Como Python limpia los objetos antiguos a medida que avanzas (como verás más adelante), esto no es tan ineficiente como puede parecer:

>>> S
'Spam'

>>> S[0] = 'z'             # Immutable objects cannot be changed
...error text omitted...
TypeError: 'str' object does not support item assignment

>>> S = 'z' + S[1:]        # But we can run expressions to make new objects
>>> S
'zpam'

Todos los objetos de Python se clasifican como inmutables (inalterables) o no inmutables. En cuanto a los tipos principales, los números, las cadenas y las tuplas son inmutables; las listas, los diccionarios y los conjuntos no lo son: pueden cambiarse libremente de lugar, al igual que la mayoría de los objetos nuevos que codificarás con clases. Esta distinción resulta ser crucial en el trabajo con Python, de formas que aún no podemos explorar a fondo. Entre otras cosas, la inmutabilidad puede utilizarse para garantizar que un objeto permanece constante a lo largo de tu programa; los valores de los objetos mutables pueden cambiar en cualquier momento y lugar (y lo esperes o no).

Estrictamente hablando, puedes modificar datos basados en texto in situ si los expandes en una lista de caracteres individuales y los vuelves a unir sin nada entre ellos, o utilizas el tipo más nuevo bytearray disponible en Python 2.6, 3.0 y posteriores:

>>> S = 'shrubbery'
>>> L = list(S)                                     # Expand to a list: [...]
>>> L
['s', 'h', 'r', 'u', 'b', 'b', 'e', 'r', 'y']
>>> L[1] = 'c'                                      # Change it in place
>>> ''.join(L)                                      # Join with empty delimiter
'scrubbery'

>>> B = bytearray(b'spam')                          # A bytes/list hybrid (ahead)
>>> B.extend(b'eggs')                               # 'b' needed in 3.X, not 2.X
>>> B                                               # B[i] = ord(x) works here too
bytearray(b'spameggs')
>>> B.decode()                                      # Translate to normal string
'spameggs'

bytearray admite cambios in situ para texto, pero sólo para texto cuyos caracteres tengan todos como máximo 8 bits de ancho (por ejemplo, ASCII). Todas las demás cadenas siguen siendo inmutables-bytearray es un híbrido distinto de cadenas de bytesinmutables (cuya sintaxis b'...' es obligatoria en 3.X y opcional en 2.X) y listas mutables (codificadas y mostradas en []), y tenemos que aprender más sobre ambas y sobre el texto Unicode para comprender plenamente este código.

Métodos específicos para cada tipo

Todas las operaciones con cadenas que hemos estudiado hasta ahora son en realidad operaciones con secuencias, es decir, que también funcionan con otras secuencias de Python, como listas y tuplas. Sin embargo, además de las operaciones de secuencia genéricas, las cadenas también tienen operaciones propias, disponibles como métodos: funcionesque se adjuntan a un objeto concreto y actúan sobre él, y que se activan con una expresión de llamada.

Por ejemplo, el método de cadena find es la operación básica de búsqueda de subcadenas (devuelve el desplazamiento de la subcadena pasada, o −1 si no está presente), y el método de cadena replacerealiza búsquedas y sustituciones globales; ambos actúan sobre el sujeto al que se adjuntan y desde el que se llaman:

>>> S = 'Spam'
>>> S.find('pa')                 # Find the offset of a substring in S
1
>>> S
'Spam'
>>> S.replace('pa', 'XYZ')       # Replace occurrences of a string in S with another
'SXYZm'
>>> S
'Spam'

De nuevo, a pesar de los nombres de estos métodos de cadena, aquí no estamos cambiando las cadenas originales, sino creando nuevas cadenas como resultado: como las cadenas son inmutables, es la única forma de que esto funcione. Los métodos de cadena son la primera línea de herramientas de procesamiento de texto en Python. Otros métodos dividen una cadena en subcadenas con un delimitador (útil como forma sencilla de análisis sintáctico), realizan conversiones entre mayúsculas y minúsculas, comprueban el contenido de la cadena (dígitos, letras, etc.) y eliminan los espacios en blanco de los extremos de la cadena:

>>> line = 'aaa,bbb,ccccc,dd'
>>> line.split(',')              # Split on a delimiter into a list of substrings
['aaa', 'bbb', 'ccccc', 'dd']

>>> S = 'spam'
>>> S.upper()                    # Upper- and lowercase conversions
'SPAM'
>>> S.isalpha()                  # Content tests: isalpha, isdigit, etc.
True

>>> line = 'aaa,bbb,ccccc,dd\n'
>>> line.rstrip()                # Remove whitespace characters on the right side
'aaa,bbb,ccccc,dd'
>>> line.rstrip().split(',')     # Combine two operations
['aaa', 'bbb', 'ccccc', 'dd']

Fíjate en el último comando: elimina antes de dividir porque Python ejecuta de izquierda a derecha, obteniendo un resultado temporal por el camino. Las cadenas también admiten una operación de sustitución avanzada conocida como formateo, disponible como expresión (la original) y como llamada a un método de cadena (nueva a partir de 2.6 y 3.0); la segunda te permite omitir los números relativos de los valores de los argumentos a partir de 2.7 y 3.1:

>>> '%s, eggs, and %s' % ('spam', 'SPAM!')          # Formatting expression (all)
'spam, eggs, and SPAM!'

>>> '{0}, eggs, and {1}'.format('spam', 'SPAM!')    # Formatting method (2.6+, 3.0+)
'spam, eggs, and SPAM!'

>>> '{}, eggs, and {}'.format('spam', 'SPAM!')      # Numbers optional (2.7+, 3.1+)
'spam, eggs, and SPAM!'

El formato es rico en funciones, de las que hablaremos más adelante en este libro, y que suelen ser más importantes cuando debes generar informes numéricos:

>>> '{:,.2f}'.format(296999.2567)                   # Separators, decimal digits
'296,999.26'
>>> '%.2f | %+05d' % (3.14159, −42)                 # Digits, padding, signs
'3.14 | −0042'

Una nota: aunque las operaciones de secuencia son genéricas, los métodos no lo son; aunque algunos tipos comparten algunos nombres de métodos, las operaciones de método de cadena generalmente sólo funcionan con cadenas, y nada más. Como regla general, el conjunto de herramientas de Python es estratificado: las operaciones genéricas que abarcan varios tipos aparecen como funciones o expresiones incorporadas (por ejemplo, len(X), X[0]), pero las operaciones específicas de un tipo son llamadas a métodos (por ejemplo, aString.upper()). Encontrar las herramientas que necesitas entre todas estas categorías te resultará más natural a medida que utilices más Python, pero la siguiente sección te da algunos consejos que puedes utilizar ahora mismo.

Conseguir ayuda

Los métodos presentados en la sección anterior son una muestra representativa, aunque pequeña, de lo que hay disponible para los objetos cadena. En general, este libro no es exhaustivo en su visión de los métodos de los objetos. Para más detalles, siempre puedes llamar a la función dir incorporada en . Esta función enumera las variables asignadas en el ámbito de quien la llama cuando se llama sin argumento; más útil aún, devuelve una lista de todos los atributos disponibles para cualquier objeto que se le pase. Como los métodos son atributos de función, aparecerán en esta lista. Suponiendo que S siga siendo la cadena, aquí tienes sus atributos en Python 3.3 (Python 2.X varía ligeramente):

>>> dir(S)
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__',
'__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__',
'__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__',
'__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count',
'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index',
'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower',
'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust',
'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex',
'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith',
'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

Probablemente no te interesen los nombres con doble guión bajo de esta lista hasta más adelante en el libro, cuando estudiemos la sobrecarga de operadores en las clases: representan la implementación del objeto cadena y están disponibles para facilitar su personalización. El método __add__ de cadenas, por ejemplo, es el que realmente realiza la concatenación ; Python mapea internamente el primero de los siguientes a la segunda forma, aunque normalmente no deberías utilizar la segunda (es menos intuitiva, e incluso podría ejecutarse más lentamente):

>>> S + 'NI!'
'spamNI!'
>>> S.__add__('NI!')
'spamNI!'

En general, el guión bajo doble inicial y final es el patrón de nomenclatura que utiliza Python para los detalles de implementación. Los nombres sin guión bajo de esta lista son los métodos invocables de los objetos cadena.

La función dir simplemente da los nombres de los métodos. Para preguntar qué hacen, puedes pasarlos a la función help:

>>> help(S.replace)
Help on built-in function replace:

replace(...)
    S.replace(old, new[, count]) -> str

    Return a copy of S with all occurrences of substring
    old replaced by new.  If the optional argument count is
    given, only the first count occurrences are replaced.

help es una de las muchas interfaces de un sistema de código que viene con Python conocido como PyDoc, unaherramienta para extraer documentación de los objetos. Más adelante en el libro, verás que PyDoc también puede renderizar sus informes en formato HTML para mostrarlos en un navegador web.

También puedes pedir ayuda sobre una cadena entera(por ejemplo, help(S)), pero es posible que obtengas más o menos ayuda de la que deseas -información sobre cada método de cadena en Python antiguos, y probablemente ninguna ayuda en las versiones más recientes, porque las cadenas se tratan de forma especial. Generalmente es mejor preguntar sobre un método concreto.

Tanto dir como help también aceptan como argumentos un objeto real (como nuestra cadena S), o el nombre de un tipo de datos (como str, list y dict). Esta última forma devuelve la misma lista que dir pero muestra todos los detalles del tipo help, y te permite preguntar sobre un método concreto a través del nombre del tipo (por ejemplo, ayuda en str.replace).

Para más detalles, también puedes consultar el manual de referencia de la biblioteca estándar de Python o libros de referencia publicados comercialmente, pero dir y help son el primer nivel de documentación en Python.

Otras formas de codificar cadenas

Hasta ahora, hemos visto en las operaciones de secuencia y los métodos específicos del tipo del objeto cadena. Python también nos proporciona diversas formas de codificar cadenas, que exploraremos en mayor profundidad más adelante. Por ejemplo, los caracteres especiales pueden representarse como secuencias de escape de barra invertida , que Python muestra en notación de escape hexadecimal \xNN , a menos que representen caracteres imprimibles:

>>> S = 'A\nB\tC'            # \n is end-of-line, \t is tab
>>> len(S)                   # Each stands for just one character
5

>>> ord('\n')                # \n is one character coded as decimal value 10
10

>>> S = 'A\0B\0C'            # \0, a binary zero byte, does not terminate string
>>> len(S)
5
>>> S                        # Non-printables are displayed as \xNN hex escapes
'A\x00B\x00C'

Python permite encerrar cadenas entre comillas simples o dobles: significan lo mismo, pero permiten encerrar el otro tipo de comillas sin escape (la mayoría de los programadores prefieren las comillas simples). También permite literales de cadena multilínea entre comillas triples (simples o dobles): cuando se utiliza esta forma, todas las líneas se concatenan y se añaden caracteres de fin de línea donde aparecen saltos de línea. Se trata de una comodidad sintáctica menor, pero es útil para incrustar cosas como código HTML, XML o JSON de varias líneas en un script de Python, y para interrumpir líneas de código temporalmente: basta con añadir tres comillas por encima y por debajo:

>>> msg = """
aaaaaaaaaaaaa
bbb'''bbbbbbbbbb""bbbbbbb'bbbb
cccccccccccccc
"""
>>> msg
'\naaaaaaaaaaaaa\nbbb\'\'\'bbbbbbbbbb""bbbbbbb\'bbbb\ncccccccccccccc\n'

Python también admite un literal de cadena sin formato que desactiva el mecanismo de escape de la barra invertida. Estos literales empiezan por la letra r y son útiles para cadenas como las rutas de directorios en Windows (por ejemplo, r'C:\text\new').

Cadenas Unicode

Las cadenas de Python también vienen con soporte Unicode completo, necesario para procesar texto en conjuntos de caracteres internacionalizados. Los caracteres de los alfabetos japonés y ruso, por ejemplo, están fuera del conjunto ASCII. Este texto no ASCII puede aparecer en páginas web, correos electrónicos, interfaces gráficas de usuario, JSON, XML o en cualquier otro lugar. Cuando aparece, para manejarlo bien se necesita soporte Unicode. Python tiene dicho soporte incorporado, pero la forma de su soporte Unicode varía según la línea de Python, y es una de sus diferencias más destacadas.

En Python 3.X, la cadena normal str maneja texto Unicode (incluido ASCII, que no es más que un tipo simple de Unicode); un tipo de cadena distinto bytes representa valores de bytes sin procesar (incluidos los medios y el texto codificado); y los literales 2.X Unicode se admiten en 3.3 y posteriores por compatibilidad con 2.X (se tratan igual que las cadenas normales 3.X str):

>>> 'sp\xc4m'                     # 3.X: normal str strings are Unicode text
'spÄm'
>>> b'a\x01c'                     # bytes strings are byte-based data
b'a\x01c'
>>> u'sp\u00c4m'                  # The 2.X Unicode literal works in 3.3+: just str
'spÄm'

En Python 2.X, la cadena normal str maneja tanto cadenas de caracteres de 8 bits (incluyendo texto ASCII) como valores de bytes sin procesar; un tipo de cadena distinto unicode representa texto Unicode; y los literales de bytes 3.X se admiten en 2.6 y posteriores para compatibilidad con 3.X (se tratan igual que las cadenas normales 2.X str ):

>>> print u'sp\xc4m'              # 2.X: Unicode strings are a distinct type
spÄm
>>> 'a\x01c'                      # Normal str strings contain byte-based text/data
'a\x01c'
>>> b'a\x01c'                     # The 3.X bytes literal works in 2.6+: just str
'a\x01c'

Formalmente, tanto en 2.X como en 3.X, las cadenas no Unicode son secuencias de bytes de 8 bits que se imprimen con caracteres ASCII cuando es posible, y las cadenas Unicode son secuencias de puntos de código Unicode -números identificativosde los caracteres-, que no se asignan necesariamente a bytes individuales cuando se codifican en archivos o se almacenan en memoria. De hecho, la noción de bytes no se aplica a Unicode: algunas codificaciones incluyen puntos de código de caracteres demasiado grandes para un byte, e incluso el simple texto ASCII de 7 bits no se almacena un byte por carácter en algunas codificaciones y esquemas de almacenamiento en memoria:

>>> 'spam'                        # Characters may be 1, 2, or 4 bytes in memory
'spam'
>>> 'spam'.encode('utf8')         # Encoded to 4 bytes in UTF-8 in files
b'spam'
>>> 'spam'.encode('utf16')        # But encoded to 10 bytes in UTF-16
b'\xff\xfes\x00p\x00a\x00m\x00'

Tanto 3.X como 2.X también admiten el tipo de cadena bytearray que conocimos antes, que es esencialmente una cadena bytes (una str en 2.X) que admite la mayoría de las operaciones de cambio mutable in situ del objeto lista.

Tanto 3.X como 2.X también admiten la codificación de caracteres no ASCII con escapes hexadecimales \x y escapes Unicode cortos \u y largos \U, así como codificaciones a nivel de archivo declaradas en los archivos fuente del programa. Aquí tienes nuestro carácter no ASCII codificado de tres formas en 3.X (añade una "u" inicial y di "print" para ver lo mismo en 2.X):

>>> 'sp\xc4\u00c4\U000000c4m'
'spÄÄÄm'

Lo que significan estos valores y cómo se utilizan difiere entre las cadenas de texto, que son la cadena normal en 3.X y Unicode en 2.X, y las cadenas de bytes, que son bytes en 3.X y la cadena normal en 2.X. Todos estos escapes pueden utilizarse para incrustar enteros de valor ordinal de punto de código Unicode reales en cadenas de texto. En cambio, las cadenas de bytes sólo utilizan los escapes hexadecimales de \x para incrustar la forma codificada del texto, no sus valores de punto de código descodificados: los bytes codificados son iguales que los puntos de código, sólo que para algunas codificaciones y caracteres:

>>> '\u00A3', '\u00A3'.encode('latin1'), b'\xA3'.decode('latin1')
('£', b'\xa3', '£')

Como diferencia notable, Python 2.X permite que sus cadenas normales y Unicode se mezclen en expresiones siempre que la cadena normal sea toda ASCII; en cambio, Python 3.X tiene un modelo más estricto que nunca permite que sus cadenas normales y de bytes se mezclen sin una conversión explícita:

u'x' + b'y'            # Works in 2.X (where b is optional and ignored)
u'x' + 'y'             # Works in 2.X: u'xy'

u'x' + b'y'            # Fails in 3.3 (where u is optional and ignored)
u'x' + 'y'             # Works in 3.3: 'xy'

'x' + b'y'.decode()    # Works in 3.X if decode bytes to str: 'xy'
'x'.encode() + b'y'    # Works in 3.X if encode str to bytes: b'xy'

Aparte de estos tipos de cadenas, el procesamiento Unicode se reduce principalmente a la transferencia de datos de texto hacia y desde archivos:el textose codifica en bytes cuando se almacena en un archivo, y se descodifica en caracteres (también conocidos como puntos de código) cuando se vuelve a leer en la memoria. Una vez cargado, solemos procesar el texto sólo como cadenas descodificadas.

Sin embargo, debido a este modelo, los archivos también tienen un contenido específico en 3.X: los archivos de texto implementan codificaciones con nombre y aceptan y devuelven cadenas str, pero los archivos binarios tratan en cambio con cadenas bytespara datos binarios sin procesar. En Python 2.X, el contenido de los archivos normales son str bytes, y un módulo especial codecs maneja Unicode y representa el contenido con el tipo unicode.

Volveremos a encontrarnos con Unicode en los archivos que se tratan más adelante en este capítulo, pero el resto de la historia de Unicode la dejamos para más adelante en este libro. Aparece brevemente en un ejemplo del Capítulo 25en relación con los símbolos monetarios, pero en su mayor parte se pospone hasta la parte de temas avanzados de este libro. Unicode es crucial en algunos ámbitos, pero muchos programadores pueden arreglárselas con un conocimiento pasajero. Si tus datos son todo texto ASCII, las historias de cadenas y archivos son en gran medida las mismas en 2.X y 3.X. Y si eres nuevo en programación, puedes aplazar con seguridad la mayoría de los detalles de Unicode hasta que hayas dominado los fundamentos de las cadenas.

Coincidencia de patrones

Un punto que vale la pena señalar en antes de continuar es que ninguno de los métodos propios del objeto cadena admite el procesamiento de texto basado en patrones. La concordancia de patrones de texto es una herramienta avanzada que queda fuera del alcance de este libro, pero a los lectores con experiencia en otros lenguajes de programación les interesará saber que para hacer concordancia de patrones en Python, importamos un módulo de llamado re. Este módulo tiene llamadas análogas para buscar, dividir y reemplazar, pero como podemos utilizar patrones para especificar subcadenas, podemos ser mucho más generales:

>>> import re
>>> match = re.match('Hello[ \t]*(.*)world', 'Hello    Python world')
>>> match.group(1)
'Python '

Este ejemplo busca una subcadena que empiece por la palabra "Hola", seguida de cero o más tabuladores o espacios, seguida de caracteres arbitrarios que se guardarán como grupo coincidente, y terminada por la palabra "mundo". Si se encuentra una subcadena de este tipo, las partes de la subcadena coincidentes con partes del patrón encerradas entre paréntesis estarán disponibles como grupos. El siguiente patrón, por ejemplo, selecciona tres grupos separados por barras o dos puntos, y es similar a la división por un patrón de alternativas:

>>> match = re.match('[/:](.*)[/:](.*)[/:](.*)', '/usr/home:lumberjack')
>>> match.groups()
('usr', 'home', 'lumberjack')

>>> re.split('[/:]', '/usr/home:lumberjack')
['', 'usr', 'home', 'lumberjack']

La concordancia de patrones es una herramienta avanzada de procesamiento de texto en sí misma, pero Python también admite un procesamiento de texto y lenguaje aún más avanzado, como el análisis sintáctico de XML y HTML y el análisis del lenguaje natural. Veremos más ejemplos breves de patrones y análisis sintáctico de XML al final del Capítulo 37, pero ya he hablado lo suficiente de las cadenas para este tutorial, así que pasemos al siguiente tipo.

Listas

El objeto lista de Python es la secuencia general más proporcionada por el lenguaje. Las listas son colecciones ordenadas posicionalmente de objetos de tipado arbitrario, y no tienen tamaño fijo. También son mutables: a diferencia delas cadenas , las listas pueden modificarse in situ mediante la asignación a desplazamientos, así como mediante diversas llamadas a métodos de listas. Por tanto, son una herramienta muy flexible para representar colecciones arbitrarias: listas de archivos en una carpeta, empleados en una empresa, correos electrónicos en tu bandeja de entrada, etc.

Operaciones de secuencia

Dado que son secuencias, las listas admiten todas las operaciones de secuencia que hemos comentado para las cadenas; la única diferencia es que los resultados suelen ser listas en lugar de cadenas. Por ejemplo, dada una lista de tres elementos:

>>> L = [123, 'spam', 1.23]            # A list of three different-type objects
>>> len(L)                             # Number of items in the list
3

podemos indexar, trocear, etc., igual que con las cadenas:

>>> L[0]                               # Indexing by position
123
>>> L[:-1]                             # Slicing a list returns a new list
[123, 'spam']

>>> L + [4, 5, 6]                      # Concat/repeat make new lists too
[123, 'spam', 1.23, 4, 5, 6]
>>> L * 2
[123, 'spam', 1.23, 123, 'spam', 1.23]

>>> L                                  # We're not changing the original list
[123, 'spam', 1.23]

Operaciones específicas de tipo

Las listas de Python pueden recordar a las matrices de otros lenguajes, pero suelen ser más potentes. Por un lado, no tienen restricción de tipo fijo: la lista que acabamos de ver, por ejemplo, contiene tres objetos de tipos completamente distintos (un entero, una cadena y un número de coma flotante). Además, las listas no tienen un tamaño fijo. Es decir, pueden crecer y decrecer a demanda, en respuesta a operaciones específicas de la lista:

>>> L.append('NI')                     # Growing: add object at end of list
>>> L
[123, 'spam', 1.23, 'NI']

>>> L.pop(2)                           # Shrinking: delete an item in the middle
1.23
>>> L                                  # "del L[2]" deletes from a list too
[123, 'spam', 'NI']

Aquí, el método lista append amplía el tamaño de la lista e inserta un elemento al final ; a continuación, el método pop (o una sentencia equivalente del ) elimina un elemento en un desplazamiento determinado, haciendo que la lista se reduzca. Otros métodos de lista insertan un elemento en una posición arbitraria (insert), eliminan un elemento dado por su valor (remove), añaden varios elementos al final (extend), etc. Como las listas son mutables, la mayoría de los métodos de lista también cambian el objeto lista en su lugar, en lugar de crear uno nuevo:

>>> M = ['bb', 'aa', 'cc']
>>> M.sort()
>>> M
['aa', 'bb', 'cc']
>>> M.reverse()
>>> M
['cc', 'bb', 'aa']

Por ejemplo, el método de la lista sort ordena la lista de forma ascendente por defecto, y reverse lo invierte; en ambos casos, los métodos modifican la lista directamente.

Comprobación de límites

Aunque las listas no tienen un tamaño fijo , Python sigue sin permitirnos hacer referencia a elementos que no están presentes. Indexar fuera del final de una lista siempre es un error, pero también lo es asignar fuera del final:

>>> L
[123, 'spam', 'NI']

>>> L[99]
...error text omitted...
IndexError: list index out of range

>>> L[99] = 1
...error text omitted...
IndexError: list assignment index out of range

Esto es intencionado, ya que suele ser un error intentar asignar fuera del final de una lista (y uno especialmente desagradable en el lenguaje C, que no hace tanta comprobación de errores como Python). En lugar de hacer crecer la lista silenciosamente, Python informa de un error. Para hacer crecer una lista, llamamos a métodos de lista como append.

Nido

Una buena característica de Los tipos de datos principales de Python admiten anidamientoarbitrario :podemos anidarlos en cualquier combinación y tan profundamente como queramos. Por ejemplo, podemos tener una lista que contenga un diccionario, que a su vez contenga otra lista, y así sucesivamente. Una aplicación inmediata de esta característica es representar matrices, o "matrices multidimensionales" en Python. Una lista con listas anidadas hará el trabajo para las aplicaciones básicas (obtendrás indicaciones de "..." en las líneas 2 y 3 de lo que sigue en algunas interfaces, pero no en IDLE):

>>> M = [[1, 2, 3],               # A 3 × 3 matrix, as nested lists
         [4, 5, 6],               # Code can span lines if bracketed
         [7, 8, 9]]
>>> M
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Aquí hemos codificado una lista que contiene otras tres listas. El efecto es representar una matriz de números de 3 × 3. Se puede acceder a una estructura de este tipo de varias formas:

>>> M[1]                          # Get row 2
[4, 5, 6]

>>> M[1][2]                       # Get row 2, then get item 3 within the row
6

En este caso, la primera operación recupera toda la segunda fila, y la segunda, el tercer elemento de esa fila (se ejecuta de izquierda a derecha, como las operaciones anteriores de extracción y división de cadenas). Encadenar operaciones de índice nos adentra cada vez más en nuestra estructura de objetos anidados .3

Comprensiones

Además de las operaciones de secuencia y los métodos de lista, Python incluye una operación más avanzada conocida como expresión de comprensión de lista, que resulta ser una forma potente de procesar estructuras como nuestra matriz. Supongamos, por ejemplo, que necesitamos extraer la segunda columna de nuestra matriz de muestra. Es fácil coger filas mediante una simple indexación, porque la matriz se almacena por filas, pero es casi igual de fácil obtener una columna con una comprensión de lista:

>>> col2 = [row[1] for row in M]             # Collect the items in column 2
>>> col2
[2, 5, 8]

>>> M                                        # The matrix is unchanged
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Las comprensiones de lista derivan de la notación de conjuntos ; son una forma de construir una nueva lista ejecutando una expresión en cada elemento de una secuencia, de uno en uno, de izquierda a derecha. Las comprensiones de lista se codifican entre corchetes (para indicarte que forman una lista) y se componen de una expresión y una construcción de bucle que comparten un nombre de variable (row, aquí). La comprensión de lista anterior significa básicamente lo que dice: "Dame row[1] para cada fila de la matriz M, en una nueva lista". El resultado es una nueva lista que contiene la columna 2 de la matriz.

Las comprensiones de listas pueden ser más complejas en la práctica:

>>> [row[1] + 1 for row in M]                 # Add 1 to each item in column 2
[3, 6, 9]

>>> [row[1] for row in M if row[1] % 2 == 0]  # Filter out odd items
[2, 8]

La primera operación aquí, por ejemplo, añade 1 a cada elemento a medida que se recoge, y la segunda utiliza una cláusula if para filtrar los números impares del resultado utilizando la expresión % módulo (resto de la división). Las comprensiones de lista crean nuevas listas de resultados, pero pueden utilizarse para iterar sobre cualquier objeto iterable, término que desarrollaremos más adelante en este avance. Aquí, por ejemplo, utilizamos las comprensiones de lista para recorrer una lista de coordenadas y una cadena codificadas:

>>> diag = [M[i][i] for i in [0, 1, 2]]      # Collect a diagonal from matrix
>>> diag
[1, 5, 9]

>>> doubles = [c * 2 for c in 'spam']        # Repeat characters in a string
>>> doubles
['ss', 'pp', 'aa', 'mm']

Estas expresiones también se pueden utilizar para recoger varios valores, siempre que los envolvamos en una colección anidada. A continuación se ilustra el uso de range-una función incorporada en que genera números enteros sucesivos, y requiere una list circundante para mostrar todos sus valores sólo en 3.X (2.X hace una lista física de una vez):

>>> list(range(4))                           # 0..3 (list() required in 3.X)
[0, 1, 2, 3]
>>> list(range(−6, 7, 2))                    # −6 to +6 by 2 (need list() in 3.X)
[−6, −4, −2, 0, 2, 4, 6]

>>> [[x ** 2, x ** 3] for x in range(4)]     # Multiple values, "if" filters
[[0, 0], [1, 1], [4, 8], [9, 27]]
>>> [[x, x / 2, x * 2] for x in range(−6, 7, 2) if x > 0]
[[2, 1, 4], [4, 2, 8], [6, 3, 12]]

Como probablemente te habrás dado cuenta, las comprensiones de listas y sus parientes, como las funciones incorporadas map y filter , son demasiado complicadas para tratarlas más formalmente en este capítulo preliminar. El objetivo principal de esta breve introducción es ilustrar que Python incluye en su arsenal herramientas tanto sencillas como avanzadas. Las comprensiones de listas son una característica opcional, pero suelen ser muy útiles en la práctica y a menudo proporcionan una ventaja sustancial en la velocidad de procesamiento. También funcionan con cualquier tipo que sea una secuencia en Python, así como con algunos tipos que no lo son. Sabrás mucho más sobre ellas más adelante en este libro.

Sin embargo, como adelanto, verás que en los últimos Python, la sintaxis de la comprensión se ha generalizado para otras funciones: hoy en día no sólo sirve para hacer listas. Por ejemplo, encerrar una comprensión entre paréntesis puede utilizarse también para crear generadores que produzcan resultados bajo demanda. A modo de ejemplo, la función sum suma elementos en una secuencia; en este ejemplo, suma todos los elementos de las filas de nuestra matriz a petición:

>>> G = (sum(row) for row in M)              # Create a generator of row sums
>>> next(G)                                  # iter(G) not required here
6
>>> next(G)                                  # Run the iteration protocol next()
15
>>> next(G)
24

El built-in map puede hacer un trabajo similar, generando los resultados de ejecutar elementos a través de una función, de uno en uno y a petición. Al igual que range, envolverlo en list le obliga a devolver todos sus valores en Python 3.X; esto no es necesario en 2.X, donde map hace una lista de resultados todos a la vez en su lugar, y no es necesario en otros contextos que iteren automáticamente, a menos que también se requieran múltiples escaneos o un comportamiento similar al de una lista:

>>> list(map(sum, M))                        # Map sum over items in M
[6, 15, 24]

En Python 2.7 y 3.X, la sintaxis de comprensión también se puede utilizar para crear conjuntos y diccionarios:

>>> {sum(row) for row in M}                  # Create a set of row sums
{24, 6, 15}

>>> {i : sum(M[i]) for i in range(3)}        # Creates key/value table of row sums
{0: 6, 1: 15, 2: 24}

De hecho, las listas, los conjuntos, los diccionarios y los generadores se pueden construir con comprensiones en 3.X y 2.7:

>>> [ord(x) for x in 'spaam']                # List of character ordinals
[115, 112, 97, 97, 109]
>>> {ord(x) for x in 'spaam'}                # Sets remove duplicates
{112, 97, 115, 109}
>>> {x: ord(x) for x in 'spaam'}             # Dictionary keys are unique
{'p': 112, 'a': 97, 's': 115, 'm': 109}
>>> (ord(x) for x in 'spaam')                # Generator of values
<generator object <genexpr> at 0x000000000254DAB0>

Sin embargo, para comprender objetos como generadores, conjuntos y diccionarios, debemos avanzar .

Diccionarios

Los diccionarios de Python son algo completamente diferente (referencia a los Monty Python): no son secuencias en absoluto, sino que se conocen como mapeos. Los mapeos también son colecciones de otros objetos, pero almacenan los objetos por clave en lugar de por posición relativa. De hecho, los mapeos no mantienen ningún orden fiable de izquierda a derecha; simplemente asignan claves a valores asociados. Los diccionarios, el único tipo de mapeo en el conjunto de objetos principales de Python, son también mutables: como las listas, pueden modificarse en su lugar y pueden crecer y decrecer a demanda. También como las listas, son una herramienta flexible para representar colecciones, pero sus claves más mnemotécnicas son más adecuadas cuando los elementos de una colección tienen nombre o etiqueta: los campos de un registro de base de datos, por ejemplo.

Operaciones cartográficas

Cuando se escriben como literales, los diccionarios se codifican entre llaves y constan de una serie de pares "clave: valor". Los diccionarios son útiles siempre que necesitemos asociar un conjunto de valores a claves, para describir las propiedades de algo, por ejemplo. Como ejemplo, considera el siguiente diccionario de tres elementos (con las claves "comida", "cantidad" y "color", ¿quizá los detalles de un hipotético elemento de menú?

>>> D = {'food': 'Spam', 'quantity': 4, 'color': 'pink'}

Podemos indexar este diccionario por claves para obtener y cambiar los valores asociados a las claves. La operación de indexación del diccionario utiliza la misma sintaxis que la utilizada para las secuencias, pero el elemento entre corchetes es una clave, no una posición relativa:

>>> D['food']              # Fetch value of key 'food'
'Spam'

>>> D['quantity'] += 1     # Add 1 to 'quantity' value
>>> D
{'color': 'pink', 'food': 'Spam', 'quantity': 5}

Aunque la forma literal con llaves se utiliza, quizá sea más habitual ver diccionarios construidos de distintas maneras (es raro conocer todos los datos de tu programa antes de que se ejecute). El siguiente código, por ejemplo, empieza con un diccionario vacío y lo va rellenando tecla a tecla. A diferencia de las asignaciones fuera de los límites de las listas, que están prohibidas, las asignaciones a nuevas claves del diccionario crean esas claves:

>>> D = {}
>>> D['name'] = 'Bob'      # Create keys by assignment
>>> D['job']  = 'dev'
>>> D['age']  = 40

>>> D
{'age': 40, 'job': 'dev', 'name': 'Bob'}

>>> print(D['name'])
Bob

En este caso, estamos utilizando las claves del diccionario como nombres de campo en un registro que describe a alguien. En otras aplicaciones, los diccionarios también pueden utilizarse para sustituir a las operaciones de búsqueda: indexar un diccionario por clave suele ser la forma más rápida de codificar una búsqueda en Python.

Como aprenderemos más adelante, también podemos hacer diccionarios pasando al tipo dict el nombre o bien argumentos de palabra clave (un especial name=value en las llamadas a funciones de ), o el resultado de comprimir secuencias de claves y valores obtenidos en tiempo de ejecución (por ejemplo, de archivos). Los dos siguientes hacen el mismo diccionario que el ejemplo anterior y su equivalente {} forma literal, aunque el primero tiende a hacer menos mecanografía:

>>> bob1 = dict(name='Bob', job='dev', age=40)                      # Keywords
>>> bob1
{'age': 40, 'name': 'Bob', 'job': 'dev'}

>>> bob2 = dict(zip(['name', 'job', 'age'], ['Bob', 'dev', 40]))    # Zipping
>>> bob2
{'job': 'dev', 'name': 'Bob', 'age': 40}

Observa cómo se desordena el orden de izquierda a derecha de las claves del diccionario. Los mapeos no están ordenados posicionalmente, así que, a menos que tengas suerte, volverán en un orden distinto al que los escribiste. El orden exacto puede variar según Python, pero no deberías depender de ello, ni esperar que el tuyo coincida con el de este libro.

Anidación revisitada

En el ejemplo anterior, utilizamos un diccionario para describir a una persona hipotética, con tres claves. Supongamos, sin embargo, que la información es más compleja. Tal vez necesitemos registrar un nombre y un apellido, junto con varios cargos. Esto nos lleva a otra aplicación del anidamiento de objetos de Python en acción. El siguiente diccionario, codificado todo a la vez como un literal, captura información más estructurada:

>>> rec = {'name': {'first': 'Bob', 'last': 'Smith'},
           'jobs': ['dev', 'mgr'],
           'age':  40.5}

Aquí volvemos a tener un diccionario de tres claves en la parte superior (claves "nombre", "empleos" y "edad"), pero los valores se han vuelto más complejos: un diccionario anidado para el nombre, a fin de admitir múltiples partes, y una lista anidada para los empleos, a fin de admitir múltiples funciones y futuras ampliaciones. Podemos acceder a los componentes de esta estructura del mismo modo que antes lo hicimos para nuestra matriz basada en listas, pero esta vez la mayoría de los índices son claves de diccionario, no desplazamientos de lista:

>>> rec['name']                         # 'name' is a nested dictionary
{'last': 'Smith', 'first': 'Bob'}

>>> rec['name']['last']                 # Index the nested dictionary
'Smith'

>>> rec['jobs']                         # 'jobs' is a nested list
['dev', 'mgr']
>>> rec['jobs'][-1]                     # Index the nested list
'mgr'

>>> rec['jobs'].append('janitor')       # Expand Bob's job description in place
>>> rec
{'age': 40.5, 'jobs': ['dev', 'mgr', 'janitor'], 'name': {'last': 'Smith',
'first': 'Bob'}}

Observa cómo la última operación expande la lista de tareas anidada: como la lista de tareas es una parte de memoria independiente del diccionario que la contiene, puede crecer y decrecer libremente (la disposición de la memoria de objetos se tratará más adelante en este libro).

La verdadera razón para mostrarte este ejemplo es demostrar la flexibilidad de los tipos de datos básicos de Python. Como puedes ver, el anidamiento nos permite construir estructuras de información complejas de forma directa y sencilla. Construir una estructura similar en un lenguaje de bajo nivel como C sería tedioso y requeriría mucho más código: tendríamos que disponer y declarar estructuras y matrices, rellenar valores, enlazarlo todo, etc. En Python, todo esto es automático: la ejecución de la expresión crea toda la estructura de objetos anidados por nosotros. De hecho, ésta es una de las principales ventajas de los lenguajes de programación como Python.

Y lo que es igual de importante, en un lenguaje de bajo nivel tendríamos que tener cuidado de limpiar todo el espacio del objeto cuando ya no lo necesitáramos. En Python, cuando perdemos la última referencia al objeto -asignando su variable a otra cosa, por ejemplo-, todo el espacio de memoria ocupado por la estructura de ese objeto se limpia automáticamente por nosotros:

>>> rec = 0                             # Now the object's space is reclaimed

Técnicamente hablando, Python tiene una función conocida como recolección de basura, que limpia la memoria no utilizada a medida que se ejecuta tu programa y te libera de tener que gestionar esos detalles en tu código. En Python estándar (también conocido como CPython), el espacio se recupera inmediatamente, en cuanto se elimina la última referencia a un objeto. Estudiaremos cómo funciona esto más adelante, en el Capítulo 6; por ahora, basta con saber que puedes utilizar objetos libremente, sin preocuparte de crear su espacio ni de limpiarlo sobre la marcha.

Busca también una estructura de registros similar a la que acabamos de codificar en el Capítulo 8, el Capítulo 9 y el Capítulo 27, donde la utilizaremos para comparar y contrastar listas, diccionarios, tuplas, tuplas con nombre y clases: una serie de opciones de estructuras de datos con ventajas y desventajas que veremos en más adelante.4

Claves que faltan: si Pruebas

Como mapeos, los diccionarios admiten el acceso a elementos sólo por clave, con el tipo de operaciones que acabamos de ver. Pero, además, también admiten operaciones de tipo específico con llamadas a métodos que son útiles en diversos casos de uso común. Por ejemplo, aunque podemos asignar una nueva clave para ampliar un diccionario, obtener una clave inexistente sigue siendo un error:

>>> D = {'a': 1, 'b': 2, 'c': 3}
>>> D
{'a': 1, 'c': 3, 'b': 2}

>>> D['e'] = 99                      # Assigning new keys grows dictionaries
>>> D
{'a': 1, 'c': 3, 'b': 2, 'e': 99}

>>> D['f']                           # Referencing a nonexistent key is an error
...error text omitted...
KeyError: 'f'

Esto es lo que queremos: suele ser un error de programación buscar algo que realmente no está ahí. Pero en algunos programas genéricos, no siempre podemos saber qué claves estarán presentes cuando escribimos nuestro código. ¿Cómo tratamos estos casos y evitamos errores? Una solución es hacer pruebas por adelantado. La expresión de pertenencia al diccionario in nos permite consultar la existencia de una clave y bifurcarnos en el resultado con una sentencia Python if. A continuación, asegúrate de pulsar Intro dos veces para ejecutar el if interactivamente después de escribir su código (como se explica en el Capítulo 3, una línea vacía significa "ir" en el prompt interactivo), y al igual que para los diccionarios y listas multilínea anteriores, el prompt cambia a "..." en algunas interfaces para las líneas dos y siguientes:

>>> 'f' in D
False

>>> if not 'f' in D:                           # Python's sole selection statement
       print('missing')

missing

Este libro tiene más que decir sobre la sentencia if en capítulos posteriores, pero la forma que utilizamos aquí es sencilla: consiste en la palabra if, seguida de una expresión que se interpreta como un resultado verdadero o falso, seguida de un bloque de código que se ejecuta si la prueba es verdadera. En su forma completa, la sentencia if también puede tener una cláusula else para un caso por defecto, y una o más cláusulas elif ("else if") para otras pruebas. Es la principal herramienta de selección dePython; junto con su expresión ternaria if/else(que conoceremos dentro de un momento) y el filtro de comprensión if que vimos antes, es la forma en que codificamos la lógica de las elecciones y decisiones en nuestros guiones.

Si has utilizado otros lenguajes de programación en el pasado, puede que te preguntes cómo sabe Python cuándo termina la sentencia if. Explicaré en profundidad las reglas sintácticas de Python en capítulos posteriores, pero en resumen, si tienes más de una acción que ejecutar en un bloque de sentencias, simplemente aplica la misma indentación a todas sus sentencias, lo que favorece la legibilidad del código y reduce el número de caracteres que tienes que escribir:

>>> if not 'f' in D:
        print('missing')
        print('no, really...')                 # Statement blocks are indented

missing
no, really...

Además de la prueba in, hay varias formas de evitar el acceso a claves inexistentes en los diccionarios que creamos: el método get, un índice condicional con un valor predeterminado; el método Python 2.X has_key, un trabajo similar a in que ya no está disponible en 3.X; la sentencia , una herramienta que conoceremos por primera vez en el Capítulo 10 y que atrapa y recupera las excepciones por completo; y la expresión ternaria (de tres partes) / .X; la sentencia try, una herramienta que conoceremos por primera vez en el Capítulo 10 y que atrapa y se recupera totalmente de las excepciones; y la expresión ternaria (de tres partes) if/else, que es esencialmente una sentencia if comprimida en una sola línea. He aquí algunos ejemplos:

>>> value = D.get('x', 0)                      # Index but with a default
>>> value
0
>>> value = D['x'] if 'x' in D else 0          # if/else expression form
>>> value
0

Dejaremos los detalles sobre estas alternativas para un capítulo posterior. Por ahora, veamos el papel de otro método de diccionario en un caso de uso común .

Claves de clasificación: bucles for

Como se ha mencionado antes en , como los diccionarios no son secuencias, no mantienen ningún orden fiable de izquierda a derecha. Si creamos un diccionario y lo imprimimos, sus claves pueden volver en un orden distinto al que las escribimos, y pueden variar según la versión de Python y otras variables:

>>> D = {'a': 1, 'b': 2, 'c': 3}
>>> D
{'a': 1, 'c': 3, 'b': 2}

Pero, ¿qué hacemos si necesitamos imponer un orden a los elementos de un diccionario? Una solución habitual es obtener una lista de claves con el método diccionario keys, ordenarla con el método lista sort, y luego recorrer el resultado con un bucle Python for (como en if, asegúrate de pulsar la tecla Intro dos veces después de codificar el siguiente bucle for, y omite el paréntesis exterior en el print en Python 2.X):

>>> Ks = list(D.keys())                # Unordered keys list
>>> Ks                                 # A list in 2.X, "view" in 3.X: use list()
['a', 'c', 'b']

>>> Ks.sort()                          # Sorted keys list
>>> Ks
['a', 'b', 'c']

>>> for key in Ks:                     # Iterate though sorted keys
        print(key, '=>', D[key])       # <== press Enter twice here (3.X print)

a => 1
b => 2
c => 3

Se trata de un proceso de tres pasos, aunque, como veremos en capítulos posteriores, en versiones recientes de Python puede hacerse en un solo paso con la función incorporada más reciente sorted. La llamada a sorted devuelve el resultado y ordena diversos tipos de objetos, en este caso ordenando automáticamente las claves del diccionario:

>>> D
{'a': 1, 'c': 3, 'b': 2}

>>> for key in sorted(D):
        print(key, '=>', D[key])

a => 1
b => 2
c => 3

Además de mostrar los diccionarios, este caso de uso sirve para introducir el bucle for de Python. El bucle for es una forma sencilla y eficaz de recorrer todos los elementos de una secuencia y ejecutar un bloque de código para cada elemento sucesivamente. Se utiliza una variable de bucle definida por el usuario (key, aquí) para hacer referencia al elemento actual en cada paso. El efecto neto en nuestro ejemplo es imprimir las claves y valores del diccionario desordenado, en orden de clave.

El bucle for, y su colega más general el bucle while, son las principales formas en que codificamos tareas repetitivas como sentencias en nuestros guiones. Pero, en realidad, el bucle for, como su pariente la comprensión de lista introducida antes, es una operación de secuencia. Funciona con cualquier objeto que sea una secuencia y, como la comprensión de lista, incluso con algunas cosas que no lo son. Aquí, por ejemplo, está recorriendo los caracteres de una cadena, imprimiendo la versión en mayúsculas de cada uno de ellos a medida que avanza:

>>> for c in 'spam':
        print(c.upper())

S
P
A
M

El bucle while de Python es una herramienta de bucle más general; no se limita a recorrer secuencias, pero generalmente requiere más código para hacerlo:

>>> x = 4
>>> while x > 0:
        print('spam!' * x)
        x -= 1

spam!spam!spam!spam!
spam!spam!spam!
spam!spam!
spam!

Hablaremos en profundidad de las sentencias de bucle, la sintaxis y las herramientas más adelante en el libro. Antes, sin embargo, debo confesar que esta sección no ha sido todo lo comunicativa que podría haber sido. Realmente, el bucle for, y todos sus cohortes que recorren los objetos de izquierda a derecha, no son sólo operaciones de secuencia, sino operaciones iterables, como describe la siguiente sección .

Iteración y optimización

Si el bucle for de la última sección se parece a la expresión de comprensión de lista introducida antes, debería: ambas son herramientas de iteración realmente generales. De hecho, ambas funcionarán con cualquier objeto iterable que siga el protocolo de iteración: ideas dominantes en Python que subyacen a todas sus herramientas de iteración.

En pocas palabras, un objeto es iterable si es una secuencia almacenada físicamente en memoria o un objeto que genera un elemento cada vez en el contexto de una operación de iteración, una especie de secuencia "virtual". Más formalmente, ambos tipos de objetos se consideran iterables porque admiten el protocolo de iteración: responden a la llamada itercon un objeto que avanza en respuesta a las llamadas next y lanza una excepción cuando termina de producir valores.

La expresión generadora de comprensión que vimos antes es un objeto de este tipo: sus valores no se almacenan en memoria de una vez, sino que se producen a medida que se solicitan, normalmente por herramientas de iteración. Los objetos archivode Python iteran de forma similar línea a línea cuando los utiliza una herramienta de iteración: el contenido del archivo no está en una lista, sino que se obtiene bajo demanda. Ambos son objetos iterables en Python, una categoría que se amplía en 3.X para incluir herramientas básicas como range y map. Al diferir los resultados según sea necesario, estas herramientas pueden ahorrar memoria y minimizar los retrasos.

Tendré más que decir sobre el protocolo de iteración más adelante en este libro. Por ahora, ten en cuenta que todas las herramientas de Python que exploran un objeto de izquierda a derecha utilizan el protocolo de iteración. Por eso la llamada a sorted utilizada en la sección anterior funciona directamente sobre el diccionario: no tenemos que llamar al método keys para obtener una secuencia porque los diccionarios son objetos iterables, con un next que devuelve claves sucesivas.

También puede ayudarte ver que cualquier expresión de comprensión de listas, como ésta, que calcula los cuadrados de una lista de números:

>>> squares = [x ** 2 for x in [1, 2, 3, 4, 5]]
>>> squares
[1, 4, 9, 16, 25]

siempre puede codificarse como un bucle for equivalente que construya la lista de resultados manualmente añadiendo a medida que avanza:

>>> squares = []
>>> for x in [1, 2, 3, 4, 5]:          # This is what a list comprehension does
        squares.append(x ** 2)         # Both run the iteration protocol internally

>>> squares
[1, 4, 9, 16, 25]

Ambas herramientas aprovechan internamente el protocolo de iteración y producen el mismo resultado. Sin embargo, la comprensión de listas y las herramientas de programación funcional relacionadas, como map y filter, a menudo se ejecutan más rápido que un bucle for en la actualidad en algunos tipos de código (quizás incluso el doble de rápido), una propiedad que podría ser importante en tus programas para grandes conjuntos de datos. Dicho esto, debo señalar que las medidas de rendimiento son un asunto delicado en Python porque optimiza mucho y pueden variar de una versión a otra.

Una regla general en Python es codificar primero para simplificar y facilitar la lectura, y preocuparse del rendimiento más tarde, cuando el programa funcione y hayas demostrado que existe un verdadero problema de rendimiento. La mayoría de las veces, tu código será lo suficientemente rápido tal y como es. Sin embargo, si necesitas ajustar el código para mejorar el rendimiento, Python incluye herramientas que te ayudarán: incluye los módulos time y timeit para medir la velocidad de las alternativas, y el módulo profile para aislar los cuellos de botella.

Encontrarás más información sobre ellos más adelante en este libro (consulta especialmente el caso práctico de evaluación comparativa del Capítulo 21) y en los manuales de Python. Por el bien de este avance, pasemos al siguiente tipo de datos básico.

Tuplas

El objeto tupla (pronunciado "toople" o "tuhple", según a quién preguntes) es más o menos como una lista que no puede modificarse: las tuplas son secuencias, como las listas, pero son inmutables, como las cadenas. Funcionalmente, se utilizan para representar colecciones fijas de elementos: los componentes de una fecha concreta del calendario, por ejemplo. Sintácticamente, suelen codificarse entre paréntesis en lugar de corchetes, y admiten tipos arbitrarios, anidamiento arbitrario y las operaciones secuenciales habituales:

>>> T = (1, 2, 3, 4)            # A 4-item tuple
>>> len(T)                      # Length
4

>> T + (5, 6)                   # Concatenation
(1, 2, 3, 4, 5, 6)

>>> T[0]                        # Indexing, slicing, and more
1

A partir de Python 2.6 y 3.0, las tuplas también tienen métodos de tipo específico a los que se puede llamar, pero no tantos como las listas:

>>> T.index(4)                  # Tuple methods: 4 appears at offset 3
3
>>> T.count(4)                  # 4 appears once
1

La principal distinción de las tuplas es que no pueden modificarse una vez creadas. Es decir, son secuencias inmutables (las tuplas de un elemento, como la de aquí, requieren una coma al final):

>>> T[0] = 2                    # Tuples are immutable
...error text omitted...
TypeError: 'tuple' object does not support item assignment

>>> T = (2,) + T[1:]            # Make a new tuple for a new value
>>> T
(2, 2, 3, 4)

Al igual que las listas y los diccionarios, las tuplas admiten tipos mixtos y anidamiento, pero no crecen ni decrecen porque son inmutables (los paréntesis que encierran los elementos de una tupla pueden omitirse a menudo, como se hace aquí; en contextos en los que las comas no importan, son lo que realmente construye una tupla):

>>> T = 'spam', 3.0, [11, 22, 33]
>>> T[1]
3.0
>>> T[2][1]
22
>>> T.append(4)
AttributeError: 'tuple' object has no attribute 'append'

¿Por qué tuplas?

Entonces, ¿por qué tener un tipo que es como una lista, pero admite menos operaciones? Francamente, en la práctica, las tuplas no se suelen utilizar tan a menudo como las listas, pero su inmutabilidad es la razón de ser. Si pasas una colección de objetos por tu programa como una lista, puede modificarse en cualquier lugar; si utilizas una tupla, no. Es decir, las tuplas proporcionan una especie de restricción de integridad que resulta conveniente en programas más grandes que los que escribiremos aquí. Hablaremos más sobre las tuplas más adelante en el libro, incluida una extensión que se basa en ellas llamada tuplas con nombre. Por ahora, sin embargo, pasemos a nuestro último tipo principal: el archivo .

Archivos

Objetos de archivo son la interfaz principal del código Python con los archivos externos de tu ordenador. Se pueden utilizar para leer y escribir notas de texto, clips de audio, documentos de Excel, mensajes de correo electrónico guardados y cualquier otra cosa que tengas almacenada en tu máquina. Los archivos son un tipo básico, pero son una especie de bicho raro: no existe una sintaxis literal específica para crearlos, sino que, para crear un objeto archivo, debes llamar a la función open incorporada en , pasando un nombre de archivo externo y un modo de procesamiento opcional como cadenas.

Por ejemplo, para crear un archivo de salida de texto, pasarías su nombre y la cadena de modo de procesamiento 'w' para escribir los datos:

>>> f = open('data.txt', 'w')      # Make a new file in output mode ('w' is write)
>>> f.write('Hello\n')             # Write strings of characters to it
6
>>> f.write('world\n')             # Return number of items written in Python 3.X
6
>>> f.close()                      # Close to flush output buffers to disk

Esto crea un archivo en el directorio actual y escribe texto en él (el nombre del archivo puede ser una ruta de directorio completa si necesitas acceder a un archivo en otro lugar de tu ordenador). Para volver a leer lo que acabas de escribir, vuelve a abrir el archivo en el modo de procesamiento 'r', para leer la entrada de texto; éste es el modo por defecto si omites el modo en la llamada. A continuación, lee el contenido del archivo en una cadena y muéstrala. El contenido de un archivo siempre es una cadena en tu script, independientemente del tipo de datos que contenga:

>>> f = open('data.txt')           # 'r' (read) is the default processing mode
>>> text = f.read()                # Read entire file into a string
>>> text
'Hello\nworld\n'

>>> print(text)                    # print interprets control characters
Hello
world

>>> text.split()                   # File content is always a string
['Hello', 'world']

Otros métodos de objetos archivo admiten funciones adicionales que no tenemos tiempo de tratar aquí. Por ejemplo, los objetos archivo proporcionan más formas de leer y escribir (read acepta un tamaño máximo opcional de bytes/caracteres, readline lee una línea cada vez, etc.), así como otras herramientas (seek se desplaza a una nueva posición del archivo). Sin embargo, como veremos más adelante, la mejor forma de leer un archivo hoy en día es no leerlo en absoluto: los archivosproporcionan un iterador que lee automáticamente línea por línea en los bucles for y otros contextos:

>>> for line in open('data.txt'): print(line)

Conoceremos el conjunto completo de métodos de archivo más adelante en este libro, pero si quieres una vista previa rápida ahora, ejecuta una llamada a diren cualquier archivo abierto y una help en cualquiera de los nombres de método que aparezcan:

>>> dir(f)
[ ...many names omitted...
'buffer', 'close', 'closed', 'detach', 'encoding', 'errors', 'fileno', 'flush',
'isatty', 'line_buffering', 'mode', 'name', 'newlines', 'read', 'readable',
'readline', 'readlines', 'seek', 'seekable', 'tell', 'truncate', 'writable',
'write', 'writelines']

>>>help(f.seek)
...try it and see...

Archivos Bytes Binarios

Los ejemplos de la sección anterior ilustran conceptos básicos de archivos que bastan para muchas funciones. Técnicamente, sin embargo, se basan en la codificación Unicode por defecto de la plataforma en Python 3.X, o en la naturaleza de bytes de 8 bits de los archivos en Python 2.X. Los archivos de texto siempre codifican cadenas en 3.X, y escriben ciegamente contenido de cadenas en 2.X. Esto es irrelevante para los datos ASCII simples utilizados anteriormente, que se asignan a y desde bytes de archivo sin cambios. Pero para tipos de datos más ricos, las interfaces de los archivos pueden variar en función tanto del contenido como de la línea Python que utilices.

Como ya insinuamos cuando conocimos antes las cadenas, Python 3.X establece una distinción tajante entre datos de texto y binarios en los archivos: los archivos de texto representan el contenido como cadenas normales str y realizan la codificación y descodificación Unicode automáticamente al escribir y leer datos, mientras que los archivos binarios representan el contenido como una cadena especial bytes y te permiten acceder al contenido del archivo sin alterarlo. Python 2.X admite la misma dicotomía, pero no la impone tan rígidamente, y sus herramientas difieren.

Por ejemplo, los archivos binarios son útiles para procesar medios, acceder a datos creados por programas C, etc. Para ilustrarlo, El módulo struct de Python puede crear y desempaquetar datos binariosempaquetados -bytes enterosque registran valores que no son objetos de Python- para escribirlos en un archivo en modo binario. Estudiaremos esta técnica en detalle más adelante en el libro, pero el concepto es sencillo: lo siguiente crea un archivo binario en Python 3.X (los archivos binarios funcionan igual en 2.X, pero el prefijo literal de cadena "b" no es necesario y no se mostrará):

>>> import struct
>>> packed = struct.pack('>i4sh', 7, b'spam', 8)     # Create packed binary data
>>> packed                                           # 10 bytes, not objects or text
b'\x00\x00\x00\x07spam\x00\x08'
>>>
>>> file = open('data.bin', 'wb')                    # Open binary output file
>>> file.write(packed)                               # Write packed binary data
10
>>> file.close()

La lectura de datos binarios de vuelta es esencialmente simétrica; no todos los programas necesitan adentrarse tan profundamente en el reino de bajo nivel de los bytes, pero los archivos binarios facilitan esta tarea en Python:

>>> data = open('data.bin', 'rb').read()              # Open/read binary data file
>>> data                                              # 10 bytes, unaltered
b'\x00\x00\x00\x07spam\x00\x08'
>>> data[4:8]                                         # Slice bytes in the middle
b'spam'
>>> list(data)                                        # A sequence of 8-bit bytes
[0, 0, 0, 7, 115, 112, 97, 109, 0, 8]
>>> struct.unpack('>i4sh', data)                      # Unpack into objects again
(7, b'spam', 8)

Archivos de texto Unicode

Los archivos de texto se utilizan para procesar todo tipo de datos basados en texto, desde notas a contenido de correo electrónico o documentos JSON y XML. Sin embargo, en el amplio mundo interconectado de hoy en día, no podemos hablar de texto sin preguntarnos también "¿de qué tipo?": también debes conocer el tipo de codificación Unicode del texto si difiere del predeterminado de tu plataforma, o si no puedes confiar en ese predeterminado por razones de portabilidad de datos.

Por suerte, esto es más fácil de lo que parece. Para acceder a archivos que contengan texto Unicode no ASCII del tipo introducido anteriormente en este capítulo, simplemente pasamos un nombre de codificación si el texto del archivo no coincide con la codificación por defecto de nuestra plataforma. En este modo, los archivos de texto de Python se codifican automáticamente al escribirlos y se descodifican al leerlos según el nombre del esquema de codificación que proporciones. En Python 3 .X:

>>> S = 'sp\xc4m'                                          # Non-ASCII Unicode text
>>> S
'spÄm'
>>> S[2]                                                   # Sequence of characters
'Ä'

>>> file = open('unidata.txt', 'w', encoding='utf-8')      # Write/encode UTF-8 text
>>> file.write(S)                                          # 4 characters written
4
>>> file.close()

>>> text = open('unidata.txt', encoding='utf-8').read()    # Read/decode UTF-8 text
>>> text
'spÄm'
>>> len(text)                                              # 4 chars (code points)
4

Esta codificación y descodificación automáticas es lo que normalmente quieres. Como los archivos se encargan de esto en las transferencias, puedes procesar el texto en memoria como una simple cadena de caracteres sin preocuparte por sus orígenes codificados en Unicode. Sin embargo, si es necesario, también puedes ver lo que realmente está almacenado en tu archivo pasando al modo binario:

>>> raw = open('unidata.txt', 'rb').read()                 # Read raw encoded bytes
>>> raw
b'sp\xc3\x84m'
>>> len(raw)                                               # Really 5 bytes in UTF-8
5

También puedes codificar y descodificar manualmente si obtienes datos Unicode de una fuente que no sea un archivo, por ejemplo, de un mensaje de correo electrónico o de una conexión de red:

>>> text.encode('utf-8')                                   # Manual encode to bytes
b'sp\xc3\x84m'
>>> raw.decode('utf-8')                                    # Manual decode to str
'spÄm'

Esto también es útil para ver cómo los archivos de texto codificarían automáticamente la misma cadena de forma diferente con distintos nombres de codificación, y proporciona una forma de traducir datos a codificaciones diferentes: son bytes diferentes en los archivos, pero se descodifican a la misma cadena en la memoria si proporcionas el nombre de codificación adecuado:

>>> text.encode('latin-1')                                 # Bytes differ in others
b'sp\xc4m'
>>> text.encode('utf-16')
b'\xff\xfes\x00p\x00\xc4\x00m\x00'

>>> len(text.encode('latin-1')), len(text.encode('utf-16'))
(4, 10)

>>> b'\xff\xfes\x00p\x00\xc4\x00m\x00'.decode('utf-16')    # But same string decoded
'spÄm'

Todo esto funciona más o menos igual en Python 2.X, pero las cadenas Unicode se codifican y muestran con una "u" inicial, las cadenas de bytes no requieren ni muestran una "b" inicial, y los archivos de texto Unicode deben abrirse con codecs.open, que acepta un nombre de codificación igual que el de 3.X, , y utiliza la cadena especial para representar el contenido en memoria.X open, y utiliza la cadena especial unicode para representar el contenido en la memoria. El modo de archivo binario puede parecer opcional en 2.X, ya que los archivos normales son sólo datos basados en bytes, pero es necesario para evitar cambiar los finales de línea si los hay (más sobre esto más adelante en el libro):

>>> import codecs
>>> codecs.open('unidata.txt', encoding='utf8').read()     # 2.X: read/decode text
u'sp\xc4m'
>>> open('unidata.txt', 'rb').read()                       # 2.X: read raw bytes
'sp\xc3\x84m'
>>> open('unidata.txt').read()                             # 2.X: raw/undecoded too
'sp\xc3\x84m'

Aunque generalmente no necesitarás preocuparte por esta distinción si sólo tratas con texto ASCII, las cadenas y archivos de Python son una ventaja si tratas con datos binarios (que incluyen la mayoría de tipos de medios) o texto en conjuntos de caracteres internacionalizados (que incluyen la mayor parte del contenido de la Web e Internet en general hoy en día). Python también admite nombres de archivo no ASCII (no sólo contenido), pero es en gran medida automático; herramientas como los caminantes y los listadores ofrecen más control cuando es necesario, aunque aplazaremos más detalles de hasta el Capítulo 37.

Otras herramientas similares a los archivos

La función open es el caballo de batalla para la mayor parte del procesamiento de archivos que harás en Python. Sin embargo, para tareas más avanzadas, Python incluye herramientas adicionales similares a los archivos: tuberías, FIFOs, sockets, archivos de acceso por clave, estanterías de objetos persistentes, archivos basados en descriptores, interfaces de bases de datos relacionales y orientadas a objetos, y mucho más. Los archivos de descriptores, por ejemplo, admiten el bloqueo de archivos y otras herramientas de bajo nivel, y los sockets proporcionan una interfaz para la comunicación en red y entre procesos. No trataremos muchos de estos temas en este libro, pero te resultarán útiles cuando empieces a programar Python en serio.

Otros tipos de núcleo

Más allá de los tipos básicos que hemos visto hasta ahora, hay otros que pueden o no pertenecer a la categoría, dependiendo de la amplitud con que se defina. Los conjuntos, por ejemplo, son una adición reciente al lenguaje que no son ni mapeos ni secuencias; más bien, son colecciones desordenadas de objetos únicos e inmutables. Puedes crear conjuntos llamando a la función set incorporada en o utilizando los nuevos literales y expresiones de conjuntos de 3.X y 2.7, y admiten las operaciones matemáticas habituales con conjuntos (la elección de la nueva sintaxis {...} para los literales de conjuntos tiene sentido, ya que los conjuntos son muy parecidos a las claves de un diccionario sin valores):

>>> X = set('spam')                 # Make a set out of a sequence in 2.X and 3.X
>>> Y = {'h', 'a', 'm'}             # Make a set with set literals in 3.X and 2.7

>>> X, Y                            # A tuple of two sets without parentheses
({'m', 'a', 'p', 's'}, {'m', 'a', 'h'})

>>> X & Y                           # Intersection
{'m', 'a'}
>>> X | Y                           # Union
{'m', 'h', 'a', 'p', 's'}
>>> X - Y                           # Difference
{'p', 's'}
>>> X > Y                           # Superset
False

>>> {n ** 2 for n in [1, 2, 3, 4]}  # Set comprehensions in 3.X and 2.7
{16, 1, 4, 9}

Incluso los programadores con menos inclinación matemática suelen encontrar útiles los conjuntos para tareas comunes como filtrar duplicados, aislar diferencias y realizar pruebas de igualdad neutras al orden sin ordenar listas, cadenas y todos los demás objetos iterables:

>>> list(set([1, 2, 1, 3, 1]))      # Filtering out duplicates (possibly reordered)
[1, 2, 3]
>>> set('spam') - set('ham')        # Finding differences in collections
{'p', 's'}
>>> set('spam') == set('asmp')      # Order-neutral equality ('spam'=='asmp' False)
True

Los conjuntos también admiten pruebas de pertenencia a in, aunque todos los demás tipos de colecciones de Python también lo hacen:

>>> 'p' in set('spam'), 'p' in 'spam', 'ham' in ['eggs', 'spam', 'ham']
(True, True, True)

Además, Python ha incorporado recientemente algunos tipos numéricos nuevos: números decimales, que son números de coma flotante de precisión fija, y números fraccionarios, que son números racionales con numerador y denominador. Ambos pueden utilizarse para sortear las limitaciones e imprecisiones inherentes a las matemáticas de coma flotante:

>>> 1 / 3                           # Floating-point (add a .0 in Python 2.X)
0.3333333333333333
>>> (2/3) + (1/2)
1.1666666666666665

>>> import decimal                  # Decimals: fixed precision
>>> d = decimal.Decimal('3.141')
>>> d + 1
Decimal('4.141')

>>> decimal.getcontext().prec = 2
>>> decimal.Decimal('1.00') / decimal.Decimal('3.00')
Decimal('0.33')

>>> from fractions import Fraction  # Fractions: numerator+denominator
>>> f = Fraction(2, 3)
>>> f + 1
Fraction(5, 3)
>>> f + Fraction(1, 2)
Fraction(7, 6)

Python también viene con booleanos (con objetos predefinidos True y False que son esencialmente los enteros 1 y 0 con lógica de visualización personalizada), y admite desde hace tiempo un objeto marcador de posición especial llamado Nonecomúnmente utilizado para inicializar nombres y objetos:

>>> 1 > 2, 1 < 2                    # Booleans
(False, True)
>>> bool('spam')                    # Object's Boolean value
True

>>> X = None                        # None placeholder
>>> print(X)
None
>>> L = [None] * 100                # Initialize a list of 100 Nones
>>> L
[None, None, None, None, None, None, None, None, None, None, None, None,
None, None, None, None, None, None, None, None, ...a list of 100 Nones...]

Cómo romper la flexibilidad de tu código

Tendré más que decir sobre todos los tipos de objetos de Python más adelante, pero uno merece un tratamiento especial aquí. El tipo objeto, devuelto por la función incorporada type, es un objeto que da el tipo de otro objeto; su resultado difiere ligeramente en 3.X, porque los tipos se han fusionado completamente con las clases (algo que exploraremos en el contexto de las clases de "nuevo estilo" en la Parte VI). Suponiendo que L siga siendo la lista de la sección anterior:

# In Python 2.X:
>>> type(L)                         # Types: type of L is list type object
<type 'list'>
>>> type(type(L))                   # Even types are objects
<type 'type'>

# In Python 3.X:
>>> type(L)                         # 3.X: types are classes, and vice versa
<class 'list'>
>>> type(type(L))                   # See Chapter 32 for more on class types
<class 'type'>

Además de permitirte explorar tus objetos de forma interactiva, el objeto type, en su aplicación más práctica, permite que el código compruebe los tipos de los objetos que procesa. De hecho, hay al menos tres formas de hacerlo en un script de Python:

>>> if type(L) == type([]):         # Type testing, if you must...
        print('yes')

yes
>>> if type(L) == list:             # Using the type name
        print('yes')

yes
>>> if isinstance(L, list):         # Object-oriented tests
        print('yes')

yes

Ahora que te he mostrado todas estas formas de hacer pruebas de tipo, sin embargo, estoy obligado por ley a decirte que hacer eso es casi siempre lo incorrecto en un programa Python (¡y a menudo un signo de un ex-programador C que empieza a usar Python!). La razón no quedará completamente clara hasta más adelante en el libro, cuando empecemos a escribir unidades de código más grandes, como las funciones, pero es un concepto (quizá el) básico de Python. Al comprobar tipos específicos en tu código, rompes de hecho su flexibilidad: lo limitas a trabajar con un solo tipo. Sin esas comprobaciones, tu código puede funcionar con toda una gama de tipos.

Esto está relacionado con la idea de polimorfismo de mencionada anteriormente, y se deriva de la falta de declaraciones de tipos en Python. Como aprenderás, en Python codificamos para interfaces deobjetos (operaciones admitidas), no para tipos. Es decir, nos importa lo que hace un objeto, no lo que es. No preocuparse por tipos específicos significa que el código es automáticamente aplicable a muchos de ellos: cualquier objeto con una interfaz compatible funcionará, independientemente de su tipo específico. Aunque la comprobación de tipos es compatible -e incluso necesaria en algunos casos excepcionales-, verás que no suele ser la forma "pitónica" de pensar. De hecho, verás que el polimorfismo es probablemente la idea clave para utilizar bien Python.

Clases definidas por el usuario

Estudiaremos a fondo la programación orientada a objetos en Python -una característica opcional pero potente del lenguaje que reduce el tiempo de desarrollo al permitir la programación por personalización- más adelante en este libro. Sin embargo, en términos abstractos, las clases definen nuevos tipos de objetos que amplían el conjunto básico, por lo que merecen un vistazo aquí. Digamos, por ejemplo, que deseas tener un tipo de objeto que modele a los empleados. Aunque en Python no existe un tipo básico específico de este tipo, la siguiente clase definida por el usuario podría ajustarse a tus necesidades:

>>> class Worker:
         def __init__(self, name, pay):          # Initialize when created
             self.name = name                    # self is the new object
             self.pay  = pay
         def lastName(self):
             return self.name.split()[-1]        # Split string on blanks
         def giveRaise(self, percent):
             self.pay *= (1.0 + percent)         # Update pay in place

Esta clase define un nuevo tipo de objeto que tendrá atributos name y pay (a veces llamados información de estado), así como dos bits de comportamiento codificados como funciones (normalmente llamados métodos). Llamar a la clase como a una función genera instancias de nuestro nuevo tipo, y los métodos de la clase reciben automáticamente la instancia que está siendo procesada por una llamada a un método determinado (en el argumento self ):

>>> bob = Worker('Bob Smith', 50000)             # Make two instances
>>> sue = Worker('Sue Jones', 60000)             # Each has name and pay attrs
>>> bob.lastName()                               # Call method: bob is self
'Smith'
>>> sue.lastName()                               # sue is the self subject
'Jones'
>>> sue.giveRaise(.10)                           # Updates sue's pay
>>> sue.pay
66000.0

El objeto "yo" implícito es la razón por la que llamamos a esto un modelo orientado a objetos: siempre hay un sujeto implícito en las funciones dentro de una clase. En cierto sentido, sin embargo, el tipo basado en clases simplemente se basa en tipos básicos y los utiliza: un objeto Worker definido por el usuario aquí, por ejemplo, no es más que una colección de una cadena y un número (name y pay, respectivamente), además de funciones para procesar esos dos objetos incorporados.

La historia más amplia de las clases es que su mecanismo de herencia soporta jerarquías de software que se prestan a la personalización por extensión. Ampliamos el software escribiendo nuevas clases, no cambiando lo que ya funciona. También debes saber que las clases son una característica opcional de Python, y que los tipos incorporados más sencillos, como las listas y los diccionarios, suelen ser mejores herramientas que las clases codificadas por el usuario. Sin embargo, todo esto va mucho más allá de los límites de nuestro tutorial introductorio sobre tipos objeto, así que considéralo sólo un adelanto; para conocer a fondo los tipos definidos por el usuario codificados con clases, tendrás que seguir leyendo. Dado que las clases se basan en otras herramientas de Python, son uno de los principales objetivos del recorrido de este libro.

Y todo lo demás

Como ya hemos dicho, todo lo que puedes procesar en un script de Python es un tipo de objeto, por lo que nuestro recorrido por los tipos de objetos es necesariamente incompleto. Sin embargo, aunque todo en Python es un "objeto", sólo los tipos de objetos que hemos conocido hasta ahora se consideran parte del conjunto de tipos básicos de Python. Otros tipos en Python o bien son objetos relacionados con la ejecución del programa (como funciones, módulos, clases y código compilado), que estudiaremos más adelante, o bien son implementados por funciones de módulos importados, no por la sintaxis del lenguaje. Estos últimos también suelen tener funciones específicas de la aplicación: patrones de texto, interfaces de bases de datos, conexiones de red, etc.

Además, ten en cuenta que los objetos que hemos conocido aquí son objetos, pero no necesariamente orientados a objetos -unconcepto que suele requerir herencia y la sentencia Python class, que volveremos a ver más adelante en este libro. Aún así, los objetos del núcleo de Python son los caballos de batalla de casi todos los scripts de Python que te encuentres, y suelen ser la base de tipos no centrales más grandes.

Resumen del capítulo

Y con esto hemos terminado nuestro recorrido inicial por los tipos de datos. Este capítulo ha ofrecido una breve introducción a los tipos de objetos básicos de Python y a las clases de operaciones que podemos aplicarles. Hemos estudiado operaciones genéricas que funcionan en muchos tipos de objetos (operaciones de secuencia como indexar y cortar, por ejemplo), así como operaciones específicas de tipos disponibles como llamadas a métodos (por ejemplo, dividir cadenas y añadir listas). También hemos definido algunos términos clave, como inmutabilidad, secuencias y polimorfismo.

Por el camino, hemos visto que los tipos de objetos principales de Python son más flexibles y potentes que los disponibles en lenguajes de bajo nivel como C. Por ejemplo, las listas y los diccionarios de Python obvian la mayor parte del trabajo que hay que hacer para soportar colecciones y búsquedas en lenguajes de bajo nivel. Las listas son colecciones ordenadas de otros objetos, y los diccionarios son colecciones de otros objetos que se indexan por clave en lugar de por posición. Tanto los diccionarios como las listas pueden anidarse, crecer y decrecer a voluntad y contener objetos de cualquier tipo. Además, su espacio se limpia automáticamente a medida que avanzas. También hemos visto que las cadenas y los archivos trabajan mano a mano para soportar una rica variedad de datos binarios y de texto.

Me he saltado la mayoría de los detalles aquí para ofrecer un recorrido rápido, así que no esperes que todo este capítulo tenga sentido todavía. En los próximos capítulos empezaremos a profundizar, dando una segunda pasada por los tipos de objetos básicos de Python que completarán los detalles omitidos aquí y te darán una comprensión más profunda. Comenzaremos el próximo capítulo con una mirada en profundidad a los números de Python. Pero antes, aquí tienes otra prueba para repasar.

Pon a prueba tus conocimientos: Cuestionario

Exploraremos los conceptos de introducidos en este capítulo con más detalle en los próximos capítulos, así que aquí nos limitaremos a cubrir las grandes ideas:

  1. Nombra cuatro de los tipos de datos principales de Python.

  2. ¿Por qué se llaman tipos de datos "básicos"?

  3. ¿Qué significa "inmutable" y qué tres tipos del núcleo de Python se consideran inmutables?

  4. ¿Qué significa "secuencia" y qué tres tipos entran en esa categoría?

  5. ¿Qué significa "mapeo" y qué tipo de núcleo es un mapeo?

  6. ¿Qué es el "polimorfismo" y por qué debería importarte?

Pon a prueba tus conocimientos: Respuestas

  1. Los números, las cadenas, las listas, los diccionarios, las tuplas, los archivos y los conjuntos suelen considerarse los tipos de objetos (datos) principales. A veces también se clasifican así los tipos, None, y los booleanos. Hay varios tipos de números (enteros, de coma flotante, complejos, fraccionarios y decimales) y varios tipos de cadenas (cadenas simples y cadenas Unicode en Python 2.X, y cadenas de texto y cadenas de bytes en Python 3.X).

  2. Se conocen como tipos "núcleo" porque forman parte del propio lenguaje Python y siempre están disponibles; para crear otros objetos, generalmente debes llamar a funciones de módulos importados. La mayoría de los tipos principales tienen una sintaxis específica para generar los objetos: 'spam', por ejemplo, es una expresión que crea una cadena y determina el conjunto de operaciones que se le pueden aplicar. Por eso, los tipos principales están integrados en la sintaxis de Python. En cambio, debes llamar a la función incorporada open para crear un objeto archivo (aunque normalmente también se considera un tipo núcleo).

  3. Un objeto "inmutable" es un objeto que no puede modificarse tras su creación. Los números, las cadenas y las tuplas en Python entran en esta categoría. Aunque no puedes cambiar un objeto inmutable in situ, siempre puedes crear uno nuevo ejecutando una expresión. Las bytearrays de los últimos Python ofrecen mutabilidad para el texto, pero no son cadenas normales, y sólo se aplican directamente al texto si es de un tipo simple de 8 bits (por ejemplo, ASCII).

  4. Una "secuencia" es una colección de objetos ordenada posicionalmente. Las cadenas, las listas y las tuplas son secuencias en Python. Comparten operaciones de secuencia comunes, como la indexación, la concatenación y el corte, pero también tienen llamadas a métodos específicos de cada tipo. Un término relacionado, "iterable", significa una secuencia física o virtual que produce sus elementos a petición.

  5. El término "mapeo" denota un objeto que mapea claves a valores asociados. El diccionario de Python es el único tipo de mapeo del conjunto de tipos del núcleo. Los mapeos no mantienen ningún orden posicional de izquierda a derecha; admiten el acceso a los datos almacenados por clave, además de llamadas a métodos específicos del tipo.

  6. "Polimorfismo" significa que el significado de una operación (como +) depende de los objetos sobre los que se opera. Ésta resulta ser una idea clave (quizá la idea clave) para utilizar bien Python: no restringir el código a tipos específicos hace que ese código sea automáticamente aplicable a muchos tipos.

1 Perdona mi formalidad. Soy informático.

2 En este libro, el término literal significa simplemente una expresión cuya sintaxis genera un objeto, a veces también llamado constante. Ten en cuenta que el término "constante" no implica objetos o variables que nunca puedan cambiarse (es decir, este término no está relacionado con const de C++ o "inmutable" de Python, tema que se explora en la sección "Inmutabilidad").

3 Esta estructura matricial funciona para tareas a pequeña escala, pero para cálculos numéricos más serios probablemente querrás utilizar una de las extensiones numéricas de Python, como los sistemas de código abierto NumPy y SciPy. Estas herramientas pueden almacenar y procesar grandes matrices de forma mucho más eficiente que nuestra estructura de lista anidada. Se ha dicho que NumPy convierte a Python en el equivalente de una versión libre y más potente del sistema Matlab, y organizaciones como la NASA, Los Álamos, JPL y muchas otras utilizan esta herramienta para tareas científicas y financieras. Busca en la Web para obtener más detalles.

4 Dos notas de aplicación aquí. En primer lugar, como adelanto, el registro rec que acabamos de crear podría ser realmente un registro de base de datos real, si empleamos el sistema de persistencia de objetos de Python: una forma sencilla de almacenar objetos nativos de Python en archivos sencillos o en bases de datos de acceso por clave, que traduce automáticamente los objetos a y desde flujos de bytes en serie. No entraremos en detalles aquí, pero estate atento a la cobertura de los módulos de persistencia pickle y shelve de Python en el Capítulo 9, Capítulo 28, Capítulo 31 y Capítulo 37, donde los exploraremos en el contexto de archivos, un caso de uso de POO, clases y cambios 3.X, respectivamente.

En segundo lugar, si estás familiarizado con JSON(JavaScript Object Notation) -un formato emergente de intercambio de datos utilizado para bases de datos y transferencias de red-, este ejemplo también puede parecer curiosamente similar, aunque el soporte de Python para variables, expresiones arbitrarias y cambios puede hacer que sus estructuras de datos sean más generales. El módulo de la biblioteca json de Python admite la creación y el análisis sintáctico de texto JSON, pero la traducción a objetos Python suele ser trivial. Busca un ejemplo de JSON que utilice este registro en el Capítulo 9, cuando estudiemos los archivos. Para un caso de uso más amplio, consulta MongoDB, que almacena datos utilizando una serialización de documentos similares a JSON con codificación binaria y lenguaje neutro, y su interfaz PyMongo.

Get Aprender Python, 5ª Edición now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.