7. La computación con listas

Recuerda la cadena de muestra del capítulo de las expresiones regulares:

1
2
3
4
C = ('La libertad, Sancho, es uno de los más preciosos dones '
'que a los hombres dieron los cielos; con ella no pueden igualarse '
'los tesoros que encierran la tierra y el mar: por la libertad, '
'así como por la honra, se puede y debe aventurar la vida.')

Al trabajar con findall() con esta cadena, has visto muchos casos de una serie de cadenas entre corchetes, como la que hay abajo:

1
2
3
4
>>> U = C.decode('utf8')
>>> from re import findall, UNICODE
>>> findall(r'\b\w{2}\b', U, UNICODE)
[u'La', u'es', u'de', u'no', u'la', u'el', u'la', u'la', u'se', u'la']

En este capítulo corto, aprendes las propiedades fundamentales de este objeto y como manejarlos.

7.1. ¿Qué es una lista?

Una lista de Python es una secuencia de objectos separados por comas entre corchetes, []. Como el ejemplo principal del capítulo, convierte la cadena U a una lista de todas sus palabras con findall():

1
2
3
>>> L = findall(r'\b\w+\b', U, UNICODE)
>>> L
[u'La', u'libertad', u'Sancho', u'es', u'uno', u'de', u'los', u'm\xe1s', u'preciosos', u'dones', u'que', u'a', u'los', u'hombres', u'dieron', u'los', u'cielos', u'con', u'ella', u'no', u'pueden', u'igualarse', u'los', u'tesoros', u'que', u'encierran', u'la', u'tierra', u'y', u'el', u'mar', u'por', u'la', u'libertad', u'as\xed', u'como', u'por', u'la', u'honra', u'se', u'puede', u'y', u'debe', u'aventurar', u'la', u'vida']

Podrías pensar que una cadena es una lista de caracteres y tendrías razón para el español normal, pero en el español pitónico, la palabra lista se refiere exclusivamente a una secuencia de objetos delimitados con corchetes.

Hasta el momento has visto muchas listas de cadenas, pero hay listas de números íntegros y reales también:

1
2
>>> LNI = [1, 2, 3]
>>> LNR = [1.2, 2.2, 3.3]

Hay además listas de listas:

>>> LL = [LNI, LNR, ['a', 'b', 'c']]

De hecho todos los objetos de Python se pueden agregar en una lista. Y hasta se pueden mezclar tipos:

>>> mezcla = ['a', 1, 2.2]

7.2. Métodos para listas

7.2.1. ¿Qué métodos de cadenas se aplican a listas?

Ya que una lista es una secuencia, se puede entrar a los métodos de cadenas que manejan propiedades secuenciales:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
>>> len(L)
>>> sorted(L)
>>> len(sorted(L))
>>> set(L)
>>> sorted(set(L))
>>> len(set(L))
>>> L+'!'
>>> len(L+'!')
>>> L*2
>>> len(L*2)
>>> L.count('los')
>>> L.index('Sancho')
>>> L.rindex('Sancho')
>>> L[2:]
>>> L[:2]
>>> L[-2:]
>>> L[:-2]
>>> L[2:-2]
>>> L[-2:2]
>>> L[:]
>>> L[:-1]+['!']

Lo único que no se permite es hacer un corte por pasos:

1
2
3
4
>>> L[2:10]
[u'Sancho', u'es', u'uno', u'de', u'los', u'm\xe1s', u'preciosos', u'dones']
>>> L[2:2:10]
[]

Es una desilusión que Python no te avisa con un error.

7.2.2. Como convertir entre cadenas y listas con split() y join()

Hay dos métodos para convertir una cadena en una lista:

1
2
3
4
5
6
7
8
>>> C1 = 'Miguel Cervantes'
>>> C2 = 'Miguel_Cervantes'
>>> C3 = 'Cervantes'
>>> C1.split()
>>> C2.split('_')
>>> s = '_'
>>> C2.split(s)
>>> list(C3)

El método de split() parte una cadena en cualquier espacio. Si no hay espacios, hay que poner el carácter de la división. Este separador se puede plasmar en un variable. Si no hay posibilidad de encontrar un separador, list() separa todos los caracteres. Como list() es una palabra reservada de Python, no la puedes aprovechar para tu propio nombre de variable.

La tarea inversa, de convertir una lista en una cadena, se efectúa con join():

1
2
3
4
5
6
>>> L1 = ['Miguel', 'Cervantes']
>>> ''.join(L1)
>>> ' '.join(L1)
>>> '_'.join(L1)
>>> s = '_'
>>> s.join(L)

join() requiere un carácter prefijado como separador. Si la cadena nula se pone, como en 2, las cadenas se unen sin interrupción. Si no, se indica el separador en el prefijo directamente o por medio de un variable.

El diagrama que hay abajo visualiza la relación inversa entre partir y unir:

_images/7-SplitJoin.png

7.2.3. ¿Qué métodos se permiten con una lista pero no con una cadena?

Hasta el momento te puede parecer que una cadena y una lista son muy parecidas, si no intercambiables, pero no es así. ¿Se puede aplicar alguna de estas operaciones a una cadena?:

1
2
3
4
5
6
7
8
>>> L1.append('de Saavedra')
>>> del L1[2]
>>> L1.insert(1, 'de Saavedra')
>>> L1.remove('de Saavedra')
>>> L1[0] = 'Miguelito'
>>> L1.append('de Saavedra')
>>> L1.pop(2)
>>> L1.reverse()

Puedes tratar de aplicarlas a C1, pero todas van a fallar. Sabes por qué, ¿no?

Es porque las operaciones de arriba cambian membresía, pero la membresía de una cadena es inalterable – o como se dijo, una cadena es inmutable. Pero por lo que se ve, la membresía de una lista es ‘alterable’ – o sea, una lista es mutable.

Véase 5.6.4. Mutable Sequence Types en la documentación para más operaciones mutables sobre una lista.

7.3. Tokenización

La mutabilidad de las listas las hace más adecuadas para hacer frente a una situación de análisis verdadera en que los datos cambian sobre la marcha. Es tanto así que el primer paso en la computación textual es convertir una cadena textual a una lista de palabras. Este proceso se llama tokenización, o sea, dividir una cadena en los casos de palabras que la componen. En esta sección, vas a revisar la tokenización que efectúa una expresión regular y luego pasar a mi alternativa preferida, la tokenización con el Natural Language Toolkit.

7.3.1. La tokenización con expresiones regulares

Ya sabes hacer la tokenización de un documento con el patrón \b\w+\b:

1
2
3
4
5
6
7
8
9
>>> C = open('Gitanilla.txt', 'r').read()
>>> U = C.decode('utf8')
>>> from re import findall, UNICODE
>>> palabras = findall(r'\b\w+\b', U, UNICODE)

>>> len(palabras)
12697
>>> palabras[:50]
[u'LA', u'GITANILLA', u'Parece', u'que', u'los', u'gitanos', u'y', u'gitanas', u'solamente', u'nacieron', u'en', u'el', u'mundo', u'para', u'ser', u'ladrones', u'nacen', u'de', u'padres', u'ladrones', u'cr\xedanse', u'con', u'ladrones', u'estudian', u'para', u'ladrones', u'y', u'finalmente', u'salen', u'con', u'ser', u'ladrones', u'corrientes', u'y', u'molientes', u'a', u'todo', u'ruedo', u'y', u'la', u'gana', u'del', u'hurtar', u'y', u'el', u'hurtar', u'son', u'en', u'ellos', u'como']

Aunque sencillo, la desventaja de este procedimiento es que descarta la puntuación. La puntuación puede proveer información valerosa, pero en vez de modificar el patrón para incluirla, voy a pasar a otro recurso que es mucho más poderoso.

7.4. Como manejar un fichero con NLTK

Uno de los motivos por los cuales abogo por el Natural Language Toolkit es que te alivia la carga de prepara un texto para análisis computacional. Lo consigue con un módulo de lectores de corpus, que hacen un procesamiento inicial para ciertas tareas o formatos. La mayoría son especializados para corpus particulares, así que vas a empezar con el más básico, el PlaintextCorpusReader.

7.4.1. Como pre-procesar un texto con el PlaintextCorpusReader

El PlaintextCorpusReader necesita saber tres datos: dónde está el fichero, cómo se llama y qué codificación tiene, como argumentos:

Note

PlaintextCorpusReader(sendero_al_fichero, nombre_del_fichero_con_sufijo, encoding=’‘)

Si el fichero está en el directorio de trabaja actual, el argumento de localización se puede dejar en blanco con la cadena nula ''. De momento sólo tienes un fichero, Gitanilla.txt, que está en UTF-8, encoding='utf-8'. Para probarlo, importa NLTK y de él importa el PlaintextCorpusReader con from nltk.corpus import PlaintextCorpusReader. NTLK tokeniza la cadena proveniente de Gitanilla.txt con el método de words():

1
2
3
>>> from nltk.corpus import PlaintextCorpusReader
>>> texlector = PlaintextCorpusReader('', 'Gitanilla.txt', encoding='utf-8')
>>> palabras = texlector.words()

Ahora puedes inspeccionar la tokenización:

1
2
3
4
>>> len(palabras)
14879
>>> palabras[:50]
[u'LA', u'GITANILLA', u'Parece', u'que', u'los', u'gitanos', u'y', u'gitanas', u'solamente', u'nacieron', u'en', u'el', u'mundo', u'para', u'ser', u'ladrones', u':', u'nacen', u'de', u'padres', u'ladrones', u',', u'cr\xedanse', u'con', u'ladrones', u',', u'estudian', u'para', u'ladrones', u'y', u',', u'finalmente', u',', u'salen', u'con', u'ser', u'ladrones', u'corrientes', u'y', u'molientes', u'a', u'todo', u'ruedo', u',', u'y', u'la', u'gana', u'del', u'hurtar', u'y']

Date cuenta que hay 2000 mil ‘palabras’ más porque la tokenización retiene la puntuación como cadenas sueltas.

7.4.2. Más métodos de PlaintextCorpusReader

Aquí hay ejemplos de los otros métodos de PlaintextCorpusReader:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
>>> texlector.fileids()
['Gitanilla.txt']
>>> texlector.raw()[:150]
u'LA GITANILLA\r\n\r\nParece que los gitanos y gitanas solamente nacieron en el mundo para\r\nser ladrones: nacen de padres ladrones, cr\xedanse con ladrones, es'
>>> texlector.sents()[:2]
[[u'LA', u'GITANILLA'], [u'Parece', u'que', u'los', u'gitanos', u'y', u'gitanas', u'solamente', u'nacieron', u'en', u'el', u'mundo', u'para', u'ser', u'ladrones', u':', u'nacen', u'de', u'padres', u'ladrones', u',', u'cr\xedanse', u'con', u'ladrones', u',', u'estudian', u'para', u'ladrones', u'y', u',', u'finalmente', u',', u'salen', u'con', u'ser', u'ladrones', u'corrientes', u'y', u'molientes', u'a', u'todo', u'ruedo', u',', u'y', u'la', u'gana', u'del', u'hurtar', u'y', u'el', u'hurtar', u'son', u'en', u'ellos', u'como', u'acidentes', u'inseparables', u',', u'que', u'no', u'se', u'quitan', u'sino', u'con', u'la', u'muerte', u'.']]
>>> texlector.abspath('Gitanilla.txt')
FileSystemPathPointer('/Users/harryhow/Documents/pyScripts/Gitanilla.txt')
>>> texlector.root
FileSystemPathPointer('/Users/harryhow/Documents/pyScripts')
>>> texlector.encoding('Gitanilla.txt')
'utf-8'
>>> texlector.readme()
IOError: No such file or directory: u'/Users/harryhow/Documents/pyScripts/README'

7.4.3. Resumen de los métodos de PlaintextCorpusReader

Main PlaintextCorpusReader methods
fileids() los ficheros del archivo
open(fileid) abrir un fichero
raw(), raw(fileids=[f1,f2,f3]) la cadena del fichero actual u otros
words(), words(fileids=[f1,f2,f3]) la tokenización del fichero actual u otros
sents(), sents(fileids=[f1,f2,f3]) las oraciones del fichero actual u otros
abspath(fileid) la localización del fichero
root() la localización de la carpeta del fichero
encoding(fileid) la codificación del fichero
readme() un documento readme del archivo

7.5. Summary

7.6. Further practice

¿Cómo se puede modificar la expresión regular de tokenización para que incluya la puntuación?

7.7. Further reading

7.8. Appendix

Footnotes


Last edited: February 10, 2015