Python. Convertir un string en una tupla, lista o diccionario

El método más conocido para convertir una cadena (o string) en una tupla, lista o diccionario es usar la función eval():

texto = "((1, 2), (3, 4))"
print eval(texto)[0]

Que dará como resultado:

(1, 2)

que es el resultado que buscamos (el primer elemento).

Por curiosidad podemos probar a imprimir el primer elemento con:

print texto[0]

Y ahora podemos hacer lo mismo con un diccionario:

texto = "{ 'uno': 1, 'dos': 2 }"
print eval(texto)['uno']

y el resultado será:

1

Si probásemos a usar la cadena sin eval tendríamos un error:

print texto['uno']

dará como resultado:

TypeError: string indices must be integers, not str

Una forma más segura de convertir cadenas a tuplas, listas o diccionarios

La función eval() puede ser una fuente de problemas si el texto proviene, por ejemplo, de un usuario malintencionado. Para hacer la conversión de manera más segura es más conveniente usar la función literal_eval():

import ast
texto = "{ 'uno': 1, 'dos': 2 }"
print ast.literal_eval(texto)['uno']

¿Existe substring, substr o algo similar en python?

En Python hay algunas cosas que sorprenden a los recién llegados que tienen experiencia en otros lenguajes de programación. Una habitual es que no hay switch en Python. Otra que también despista bastante es que no existe una función como substr.

¿Cómo se puede extraer parte de una cadena o string en Python?

La solución para hacer esto es usar el siguiente método:

cadena[comienzo:fin]

Donde cadena es una variable que contiene una cadena, comienzo es un entero con el que indicamos desde qué posición queremos a extraer y con fin marcamos el final. La cadena extraída será desde comienzo hasta fin-1.

Nota: la primera posición de la cadena es la posición 0.

Por ejemplo:

cadena = "Hola amiguetes"
print(cadena[0:1]) # Esto dará como resultado 'H'

El resultado es ‘H’ porque estamos pidiendo desde el elemento 0 (el primero) hasta el elemento 0 (recordemos que es fin-1).

cadena = "Hola amiguetes"
print(cadena[1:1]) # Esto dará como resultado ''

Aquí el resultado es una cadena vacía puesto que empezamos en 1 y terminamos en 0 (1-1).

cadena = "Hola amiguetes"
print(cadena[1:2]) # Esto dará como resultado 'o'

Aquí recogemos desde la posición 1 hasta la 1 (2-1). Por eso sale sólo la ‘o’. A veces resulta confuso porque el segundo número (fin) no indica la longitud de la subcadena que queremos sino la posición del último elemento de la misma.

Y aquí os dejo otros ejemplos:

print(cadena[0:6]) # 'Hola a'
print(cadena[0:]) # Si no indicamos fin recogemos la cadena hasta el final 'Hola amiguetes'
print(cadena[5:]) # 'amiguetes'
print(cadena[:5]) # 'Hola'
print(cadena[:]) # 'Hola amiguetes'
print(cadena[-8]) # 'm' recoge el octavo elemento empezando por el final
print(cadena[-8:]) # 'miguetes' desde el octavo empezando por el final hasta el final
print(cadena[:-4]) # 'Hola amigu' Desde el principio hasta el cuarto elemento empezando por el final

Más información: http://docs.python.org/release/2.3.5/whatsnew/section-slices.html

Python: Cómo sincronizar procesos y bloquear acceso a recursos

Una situación relativamente habitual cuando trabajamos con procesos es que dos o más tienen que acceder simultáneamente a un recurso que sólo puede usar uno a la vez. Un ejemplo podría ser el de una cámara de seguridad. Imaginemos dos procesos: uno de ellos, cuando se detecta movimiento, toma un vídeo, el otro proceso saca una foto de manera automática cada cinco minutos. Si el primer proceso está grabando vídeo cuando el segundo quiere sacar la foto no podrá hacerlo y tendremos un problema.

¿Cómo evitamos que dos procesos independientes accedan al mismo recurso a la vez en Python?

Para solucionar este problema podemos usar bloqueos de ficheros. Os dejo un ejemplo para que lo veáis de manera sencilla. En este caso vamos a tener dos procesos (prueba_bloqueo_A.py y prueba_bloqueo_B.py) que intentan acceder simultáneamente a un recurso limitado.

La “magia” se consigue gracias a fcntl.

Código de prueba_bloqueo_A.py:

import fcntl
import time
 
filename = "/tmp/prueba.tmp"
 
handle = open(filename, 'w')
 
for n in range(1,3):
	# bloqueo durante 15 segundos
	print("El proceso A solicita acceso al recurso compartido")
	fcntl.flock(handle, fcntl.LOCK_EX)
	# Aquí comenzaria el codigo que accede al recurso compartido.
	# En su lugar pongo un codigo de ejemplo.
	for i in range(1,10):
		print ("Proceso A: " + str(i))
		time.sleep(1)
	# Fin del codigo que accede al recurso compartido.
	fcntl.flock(handle, fcntl.LOCK_UN)
	print("Recurso liberado por el proceso A")
	time.sleep(1)
handle.close()

Código de prueba_bloqueo_B.py:

import fcntl
import time
 
filename = "/tmp/prueba.tmp"
 
handle = open(filename, 'w')
 
for n in range(1,3):
	# bloqueo durante 15 segundos
	print("El proceso B solicita acceso al recurso compartido")
	fcntl.flock(handle, fcntl.LOCK_EX)
	# Aquí comenzaria el codigo que accede al recurso compartido.
	# En su lugar pongo un codigo de ejemplo.
	for i in range(1,10):
		print ("Proceso B: " + str(i))
		time.sleep(1)
	# Fin del codigo que accede al recurso compartido.
	fcntl.flock(handle, fcntl.LOCK_UN)
	print("Recurso liberado por el proceso B")
	time.sleep(1)
handle.close()

Para verlos en acción puedes teclear en un terminal lo siguiente:

python prueba_bloqueo_A.py & python prueba_bloqueo_B.py

Los dos procesos se ejecutarán de manera concurrente. La salida puede ser similar a ésta:

El proceso B solicita acceso al recurso compartido
Proceso B: 1
El proceso A solicita acceso al recurso compartido
Proceso B: 2
Proceso B: 3
Proceso B: 4
Proceso B: 5
Proceso B: 6
Proceso B: 7
Proceso B: 8
Proceso B: 9
Recurso liberado por el proceso B
Proceso A: 1
El proceso B solicita acceso al recurso compartido
Proceso A: 2
Proceso A: 3
Proceso A: 4
Proceso A: 5
Proceso A: 6
Proceso A: 7
Proceso A: 8
Proceso A: 9
Recurso liberado por el proceso A
...

¿Por qué digo que la salida puede ser “similar a ésta” y no “igual a ésta”? Porque, dado que trabajamos en un sistema multiproceso no tenemos garantía de que el proceso A sea más rápido o se ejecute antes que el proceso B.

Con este resultado vemos que ambos procesos solicitan acceso al recurso compartido, pero sólo uno de ellos lo conseguirá (el que antes lo solicite). El otro se queda esperando a que el primero termine. Luego será el primero el que espere a que termine el segundo.

La clase bloqueo (lock)

En este post (en inglés) Chris, además de dar un completo repaso a los bloqueos de procesos en Python, nos muestra una sencilla clase para controlas los bloqueos. Es lo mismo que hemos hecho más arriba pero de un modo más elegante:

Contenido del fichero bloqueos.py:

import fcntl
 
class Bloqueo:
 
	def __init__(self, fichero):
		self.handle = open(fichero, 'w')
 
	def solicitar(self):
		fcntl.flock(self.handle, fcntl.LOCK_EX)
 
	def liberar(self):
		fcntl.flock(self.handle, fcntl.LOCK_UN)
 
	def __del__(self):
		self.handle.close()

Y os dejo los anteriores scripts modificados para usar esta clase:

Contenido del fichero bloqueo_A.py:

import time
from bloqueos import Bloqueo
 
bloqueo = Bloqueo("/tmp/prueba.tmp")
 
for n in range(1,3):
	# bloqueo durante 15 segundos
	print("El proceso A solicita acceso al recurso compartido")
	bloqueo.solicitar()
	for i in range(1,10):
		print ("Proceso A: " + str(i))
		time.sleep(1)
	print("Recurso liberado por el proceso A")
	bloqueo.liberar()
	time.sleep(1)

Contenido de bloqueo_B.py:

import time
from bloqueos import Bloqueo
 
bloqueo = Bloqueo("/tmp/prueba.tmp")
 
for n in range(1,3):
	# bloqueo durante 15 segundos
	print("El proceso B solicita acceso al recurso compartido")
	bloqueo.solicitar()
	for i in range(1,10):
		print ("Proceso B: " + str(i))
		time.sleep(1)
	print("Recurso liberado por el proceso B")
	bloqueo.liberar()
	time.sleep(1)

Para ver el resultado teclear en un terminal:

python bloqueo_A.py & python bloqueo_B.py

Por último queda avisar que debemos tener cuidado y evitar que un proceso se quede colgado bloqueando un recurso de manera indefinida. No debemos olvidar los habituales try-except.

PyQT: Abrir una ventana emergente

Os dejo aquí otro ejemplo de cómo abrir una ventana emergente en PyQT. La ventana emergente es del tipo QDialog:

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
 
class Principal(QWidget): 
    def __init__(self, *args): 
        QWidget.__init__(self, *args)
 
        contenedor = QVBoxLayout()
        self.setLayout(contenedor)
 
        btnAbrir = QPushButton("Abrir ventana",None)
        contenedor.addWidget(btnAbrir)
        self.connect(btnAbrir, SIGNAL("clicked()"), self.abrir)
 
        btnSalir = QPushButton("Salir",None)
        contenedor.addWidget(btnSalir)
        self.connect(btnSalir, SIGNAL("clicked()"), self.salir)
 
    def abrir(self):
        ventana = Secundaria().exec_()
 
    def salir(self):
        exit()
 
class Secundaria(QDialog):
    def __init__(self, parent=None):
        QDialog.__init__(self, parent)
 
        contenedor = QVBoxLayout()
        self.setLayout(contenedor)
 
        btnSalir = QPushButton("Salir",None)
        contenedor.addWidget(btnSalir)
        self.connect(btnSalir, SIGNAL("clicked()"), self.salir)
 
    def salir(self):
        exit()
 
if __name__ == "__main__":
    app = QApplication(sys.argv)
    principal = Principal()
    principal.show()
    sys.exit(app.exec_())

PyQT (y IV): Ejemplo de QCheckbox

Como ya es costumbre este miércoles toca un nuevo mini artículo sobre PyQT. Hoy vamos a ver un ejemplo de QCheckbox:

Ejemplo PyQT Checkbox

Ejemplo PyQT Checkbox

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
 
class Controles(QWidget): 
    def __init__(self, *args): 
        QWidget.__init__(self, *args)
 
        contenedor = QVBoxLayout()
        self.setLayout(contenedor)
 
        self.label = QLabel("Checkbox desactivado")
        contenedor.addWidget(self.label)
 
        self.checkbox = QCheckBox("Click para cambiar texto")
        contenedor.addWidget(self.checkbox)
        self.connect(self.checkbox, SIGNAL("stateChanged(int)"), self.cambiar)
 
        btnSalir = QPushButton("Salir",None)
        contenedor.addWidget(btnSalir)
        self.connect(btnSalir, SIGNAL("clicked()"), self.salir)
 
    def cambiar(self):
        if self.checkbox.isChecked():
            self.label.setText('Checkbox activado')
        else:
            self.label.setText('Checkbox desactivado')
 
    def salir(self):
        exit()
 
if __name__ == "__main__":
    app = QApplication(sys.argv)
    controles = Controles()
    controles.show()
    sys.exit(app.exec_())

Como detalle a destacar en este ejemplo hemos asociado el método self.cambiar a la señal stateChanged (que es la que se produce cuando se marca o desmarca la casilla):

self.connect(self.checkbox, SIGNAL("stateChanged(int)"), self.cambiar)

Es muy importante no olvidar el (int).

Otro punto importante es el método isChecked() que nos permite saber en todo momento si el checkbox está marcado o no:

if self.checkbox.isChecked():

Error en Python+PyQT: expected string, QString found

Esto ya me lo han consultado un par de personas así que escribo este post para poner la solución. Es posible que te encuentres con un error parecido a éstos:

TypeError: sequence item 0: expected string, QString found
TypeError: popen() argument 1 must be string, not QString

El problema se debe a que la una función espera un parámetro tipo string pero le damos uno de tipo QString. QString es el tipo de string que se usa en PyQT (por ejemplo en un QLineEdit). La solución pasa por usar la función str(). Por ejemplo:

nombre = str(self.NombreLineEdit.text())

Y nombre ya es de tipo string en lugar de QString.

Python: sacar las iniciales de un nombre

A veces me maravillo con lo que se puede hacer en Python con un poco de imaginación. Os dejo aquí un sencillo ejemplo que permite extraer la iniciales de un nombre. Por ejemplo:

Luisa Puerros Cebollas -> LPC
Paco Larvas -> PL

Y aquí el código:

import re
 
m = re.findall('([A-Z])[A-Za-z]* *', "Luisa Puerros Cebollas")
iniciales = "".join(m)
print(m, iniciales)

La expresión regular de findall nos buscará las palabras separadas por un espacio y devolverá una lista con las iniciales. Con join juntamos los elementos de la lista en una única cadena.

PyQT (y III): Ejemplo de getOpenFileName y getSaveFileName

En esta nueva entrega os dejo un ejemplo del manejo de diálogos para selección de ficheros. Este ejemplo no hace nada con los ficheros seleccionados (no los abre realmente ni los guarda). Sin embargo tiene unas características interesantes:

  • El ejemplo recuerda el último fichero que se ha abierto y al guardar propone sobreescribir el fichero.
  • Al abrir o guardar el nombre del fichero aparece en el título de la ventana.
  • Cuando abrimos o guardarmos recuerda la última carpeta en la que hemos abierto o guardado.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
 
class Controles(QWidget):
 
    ruta = ""
    fichero_actual = ""
 
    def __init__(self, *args): 
        QWidget.__init__(self, *args)
 
        self.setWindowTitle("Programa Ejemplo")
 
        contenedor = QVBoxLayout()
        self.setLayout(contenedor)
 
        btnAbrir = QPushButton("Abrir",None)
        contenedor.addWidget(btnAbrir)
        self.connect(btnAbrir, SIGNAL("clicked()"), self.abrir)
 
        btnGuardar = QPushButton("Guardar",None)
        contenedor.addWidget(btnGuardar)
        self.connect(btnGuardar, SIGNAL("clicked()"), self.guardar)
 
        btnNuevo = QPushButton("Nuevo",None)
        contenedor.addWidget(btnNuevo)
        self.connect(btnNuevo, SIGNAL("clicked()"), self.nuevo)
 
        btnSalir = QPushButton("Salir",None)
        contenedor.addWidget(btnSalir)
        self.connect(btnSalir, SIGNAL("clicked()"), self.salir)
 
    def abrir(self):
        nombre_fichero = QFileDialog.getOpenFileName(self, "Abrir fichero", self.ruta)
        if nombre_fichero:
            self.fichero_actual = nombre_fichero
            self.setWindowTitle(QFileInfo(nombre_fichero).fileName())
            self.ruta = QFileInfo(nombre_fichero).path()
 
            # TODO - Aqui va el codigo
 
    def guardar(self):
        if self.fichero_actual:
            ruta_guardar = self.fichero_actual
        else:
            ruta_guardar = self.ruta
 
        nombre_fichero = QFileDialog.getSaveFileName(self, "Guardar fichero", ruta_guardar)
        if nombre_fichero:
            self.fichero_actual = nombre_fichero
            self.setWindowTitle(QFileInfo(nombre_fichero).fileName())
            self.ruta = QFileInfo(nombre_fichero).path()
 
            # TODO - Aqui va el codigo
 
    def nuevo(self):
        self.fichero_actual = ""
        self.ruta = ""
        self.setWindowTitle("Programa Ejemplo")
 
    def salir(self):
        exit()
 
if __name__ == "__main__":
    app = QApplication(sys.argv)
    controles = Controles()
    controles.show()
    sys.exit(app.exec_())

PyQT (y II): Ejemplo de qLabel, qLineEdit y qButton

En esta segunda entrega de PyQT vamos a ver un ejemplo que usa una etiqueta (qLabel), un control para edición de texto (qLineEdit) y un botón (qButton).

En este ejemplo tenemos un qLineEdit cuyo contenido podemos borrar al hacer click en el botón borrar:

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
 
class Controles(QWidget): 
    def __init__(self, *args): 
        QWidget.__init__(self, *args)
 
        contenedor = QVBoxLayout()
        self.setLayout(contenedor)
 
        label = QLabel("Texto: ")
        contenedor.addWidget(label)
 
        self.qle_texto = QLineEdit()
        contenedor.addWidget(self.qle_texto)
 
        btnBorrar = QPushButton("Borrar",None)
        contenedor.addWidget(btnBorrar)
        self.connect(btnBorrar, SIGNAL("clicked()"), self.borrar)
 
        btnSalir = QPushButton("Salir",None)
        contenedor.addWidget(btnSalir)
        self.connect(btnSalir, SIGNAL("clicked()"), self.salir)
 
    def borrar(self):
        self.qle_texto.setText("")
 
    def salir(self):
        exit()
 
if __name__ == "__main__":
    app = QApplication(sys.argv)
    controles = Controles()
    controles.show()
    sys.exit(app.exec_())

PyQT: Ejemplo de botón

Con este artículo voy a empezar una pequeña serie sobre programación en PyQT (Python+QT).

Voy a comenzar con un sencillo programa que tiene únicamente un botón para salir:

Ejemplo de QButton

Resultado del programa.

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
 
class Ventana(QWidget): 
    def __init__(self, *args): 
        QWidget.__init__(self, *args)
 
        contenedor = QVBoxLayout()
        self.setLayout(contenedor)
 
        btnSalir = QPushButton("Salir", None)
        contenedor.addWidget(btnSalir)
        self.connect(btnSalir, SIGNAL("clicked()"), self.salir)
 
    def salir(self):
        exit()
 
if __name__ == "__main__":
    app = QApplication(sys.argv)
    ventana = Ventana()
    ventana.show()
    sys.exit(app.exec_())

Vamos a ver la clase Controles paso a paso.

Primero creamos un Layout donde va a ir el botón:

contenedor = QVBoxLayout()

y lo ponemos como el layout principal:

self.setLayout(contenedor)

Creamos el botón con el texto “Salir”:

btnSalir = QPushButton("Salir",None)

Lo añadimos al layout principal:

contenedor.addWidget(btnSalir)

Y por último asociamos el evento que se produce al hacer click en el botón con el método (o función) salir:

self.connect(btnSalir, SIGNAL("clicked()"), self.salir)

La función salir simplemente se encarga de cerrar el programa:

def salir(self):
    exit()