Automágica: durante 2017 estoy trabajando bastante en Automágica, mi software para editar libros: Más información - Posts relacionados


8 de mayo: Python Day en Rafaela

El próximo sábado se va a desarrollar en la ciudad de Rafaela un Python Day, un día de charlas para que quienes no conozcan Python puedan acercarse al lenguaje. Vamos a tener muchas charlas introductorias y esperamos pueda aprovechar este evento tanto la comunidad universitaria como la ciudad en general.

http://www.pyday.com.ar/rafaela2010/

Por mi parte voy a estar colaborando con una charla nueva: Entendiendo Decoradores en Python. Esperemos salga bien :)

El Python Day es un evento organizado por PyAr con el apoyo de la Universidad Católica de Santiago del Estero, Departamento Académico Rafaela. El evento dura un día donde se darán pequeños cursos y charlas relacionadas a este lenguaje que de a poco va a haciendo su lugar entre los desarrolladores. Tenemos como intención hacer un espacio en donde cualquier persona interesada pueda acercarse para preguntar, aprender y experimentar con estas herramientas. El evento se llevará a cabo acá a partir de las 9:00 de la mañana. ¿Qué es Python? Python es un lenguaje de programación interpretado creado por Guido van Rossum en el año 1990. En la actualidad Python se desarrolla como un proyecto de código abierto, administrado por la Python Software Foundation. La última versión estable del lenguaje es la 2.6 (01 de octubre de 2008). Fuente: Wikipedia Quiero participar… ¿Qué Hago? Si lo que querés es asistir a las charlas, solo aparecete por la universidad el 8 de mayo a partir de las 09:00 y listo (por favor, si es posible, registrate previamente por web)


La historia de Python: Los orígenes de las características "Funcionales" de Python

El siguiente texto es una traducción del artículo Origins of Python's "Functional" Features de Guido van Rossum publicado en http://python-history.blogspot.com/.

Los orígenes de las características "Funcionales" de Python

Yo nunca considere que Python esté fuertemente influenciado por lenguajes funcionales, no importa lo que la gente diga o piense. Estoy  mucho mas familiarizado con lenguajes imperativos, como C y Algol 68, y aunque hice a las funciones objetos de primera clase, nunca vi a Python como un lenguaje de programación funcional. Sin embargo, tiempo atrás, tenia claro que los usuarios querían hacer mucho mas con las  listas y las funciones

Una operación común en las listas fue la de mapear una función a cada elemento de una lista, creando una nueva lista. Por ejemplo:

def square(x):

    return x*x

vals = [1, 2, 3, 4]

newvals = []

for v in vals:

    newvals.append(square(v))

En los lenguajes funcionales como Lisp y Sheme, operaciones como esa son provistas como funciones incorporadas al lenguaje. Por lo tanto, los nuevos usuarios, familiarizados con este tipo de lenguajes se encontraron a si mismos implementando funcionalidades similares en Python. Por Ejemplo

def map(f, s):

    result = []

    for x in s:

        result.append(f(x))

    return result
def square(x):

    return x*x

vals = [1, 2, 3, 4]

newvals = map(square,vals)

Un detalle sutil del código de arriba es que a mucha gente no le gusto la idea de tener que definir una función separada para la operación  que estaban aplicando a cada elemento de la lista. Lenguajes como Lisp permitían funciones simplemente definidas "al vuelo", al hacer la llamada a la función map. Por ejemplo en Scheme, se pueden crear funciones anónimas y hacer operaciones de asignación en una expresión simple usando lambda, de esta forma:

(map (lambda (x) (* x x)) '(1 2 3 4))

Aunque en Python las funciones son objetos de primera clase, no tenia un mecanismo similar para para crear funciones anónimas.

A finales de 1993, los usuarios estaban proponiendo varias ideas para crear funciones anónimas y funciones para manipular listas como: map(), filter() y reduce().  Por ejemplo, Mark Lutz (autor de "Programming Python") envió este código, para una función que  crea funciones usando exec:

def genfunc(args, expr):

    exec('def f(' + args + '): return ' + expr)

    return eval('f')
# Sample usage

vals = [1, 2, 3, 4]

newvals = map(genfunc('x', 'x*x'), vals)

Entonces Tim Peters lo siguió con una solución que simplificaba un poco mas la sintaxis, permitiendo que los usuarios escriban:

vals = [1, 2, 3, 4]

newvals = map(func('x: x*x'), vals)

Estaba claro que había una demanda de esas funcionalidades. Sin embargo, al mismo tiempo,  me parecía demasiado "hacky" que se creen funciones anónimas como strings, que tenías que procesar usando "exec". Así que en enero de 1994, las funciones map(), filter(), y reduce() fueron agregadas a la biblioteca estándar. Ademas se creo el operador lambda para crear funciones anónimas (como expresión) con una sintaxis mas sencilla. Por Ejemplo:

vals = [1, 2, 3, 4]

newvals = map(lambda x:x*x, vals)

Esas incorporaciones representan una significativa  contribución de código fuente. Desafortunadamente no recuerdo el autor, y  no esta registrado en el SVN. Si es tuyo, ¡dejá un comentario!

Nunca estuve del todo conforme con el uso de la termino "lambda", pero a falta de una mejor y mas obvia alternativa, fue lo adoptado para Python. Después de todo, fue la elección del contribuyente anónimo, y en ese momento los grandes cambios requerían menos discusión que actualmente, para bien o para mal.

Lambda solo pretendía ser una herramienta sintáctica para definir funciones anónimas. Sin embargo, la elección de esa terminología tuvo muchas consecuencias inesperadas. Los usuarios acostumbrados a los lenguajes funcionales esperaban que la semántica fuese igual que en estos. Como resultado, encontraban que la implementación de Python no tenia demasiadas características avanzadas. Un detalle de lambda es que la expresión no puede referirse a variables en el ámbito circundante. Por  ejemplo, si tenes el siguiente código, la función map() se rompería, porque la función lambda se ejecutaría con una referencia indefinida a la variable "a".

def spam(s):

    a = 4

    r = map(lambda x: a*x, s)

Había formas de solucionar este problema, pero involucraban prácticas ilógicas como setear argumentos por defecto y pasar argumentos escondidos en la expresión lambda. Por ejemplo:

def spam(s):

    a = 4

    r = map(lambda x, a=a: a*x, s)

La solución "correcta" a este problema fue que las funciones interiores llevaran, implícitamente, referencias a todas las variables locales del entorno circundante referenciadas por la función. Esto es conocido como "closure", y es un aspecto esencial de los lenguajes funcionales. Sin embargo esa capacidad no se introdujo hasta la versión 2.2 (pero podia ser importada "desde el futuro" en Python 2.1).

Curiosamente, map, filter y reduce, que motivaron originalmente la introducción de lambda y otras características funcionales, fueron, en gran medida reemplazadas por las listas por comprensión y las expresiones generadoras. De hecho, la función reduce fue removida de las funciones incorporadas en Python 3.0 (Sin embargo no es necesario que me manden quejas por la quita de lambda, map o filter: se quedarán en su sitio :-)

Hay que tener en cuenta que, aunque yo no preveía a Python como un lenguaje funcional, la introducción de closures ha sido útil en el desarrollo de muchas otras características avanzadas de programación. Por ejemplo, ciertos aspectos de los nuevos estilos de clases, decoradores y otras funcionalidades dependen de esta característica.

Finalmente, aunque a lo largo de los años se introdujeron varias características de programación funcional, Python no tiene ciertas capacidades que se encuentran en los verdaderos lenguajes de programación funcional. Por ejemplo, Python no realiza ciertos tipos de optimizaciones (como recursión por la cola). En general, por la naturaleza extremadamente dinámica de Python, es imposible de hacer optimizaciones en tiempo de compilación como las de lenguajes como Haskell o ML. Y eso es bueno.

Traducido por Joaquín Sorianello.

Revisado por Juan José Conti.

Si encontrás errores en esta traducción, por favor reportalos en un comentario y los corregiremos a la brevedad.

Todas las traducciones de esta serie pueden encontrarse en La historia de Python.


La historia de Python: El Gran (o Enorme) Renombrado

El siguiente texto es una traducción del artículo The Great (or Grand) Renaming de Guido van Rossum publicado en http://python-history.blogspot.com/.

El Gran (o Enorme) Renombrado

Cuando creé Python, siempre lo imagine como un programa autónomo, enlazado ocasionalmente con bibliotecas de terceros. Por lo tanto, en el código fuente, se definían nombres globales con total libertad, como "object", "getlistitem", "INCREF" y muchos otros mas. Cuando la popularidad de Python se incrementó, la gente comenzó a pedir una versión "embebida", que fuera también una biblioteca enlazable a otras aplicaciones - de una forma similar en la que Emacs incorpora un interprete de Lisp.

Desafortunadamente, la integración se complicaba por conflictos entre los nombres globales de Python y los definidos por la aplicación - "object" era especialmente popular. Para lidiar con ese problemas se eligió una convención, por la cual todos los nombre globales comenzarían con "Py" o "_Py" (para los internos que tenían que ser globales por razones técnicas) o "PY" (para las macros).

Por razones de compatibilidad hacia atrás (ya que había muchos módulos de extensión de terceros) y para facilitar la transición a los desarrolladores del núcleo (que tenían los viejos nombres enquistados en sus mentes) hubieron dos fases. En la fase uno, el enlazador aceptaba los nombres antiguos, pero el código fuente usaba los nuevos, que eran traducidos a los antiguos usando muchas macros del pre procesador de C. En la fase dos, el enlazador veía los nuevos, pero, para beneficio de los módulos de extensión que todavía no habían sido portados, otro conjunto de macros traducía los viejos a los nuevos. En ambas fases el código podía incluir ambos, y funcionar correctamente.

Investigué un poco la historia en los logs de Subversion. Encontré la revisión r4583 del 12 de enero de 1995, que marca el comienzo de la fase dos, introduciendo los nuevos nombres a los archivos de encabezado. Pero en diciembre de 1996 el renombrado de los archivos fuentes ".c" seguia en marcha. En ese momento el renombrado parecía haber cambiado de nombre, los comentarios de registro lo llamaban "El Enorme Renombrado". Las macros de compatibilidad hacia atras fueron finalmente removidos en mayo de 2000, como resultado de la liberación 1.6. El comentario de r15313 celebra este evento.

La mayor parte del crédito se lo llevaron Barry Warsaw y Roger Masse, que participaron en la desagradable tarea de renombrar los contenidos de archivo, tras archivo, tras archivo... (aunque con la ayuda de un script). También ayudaron en la tediosa tarea de agregar test unitarios para gran parte de la biblioteca estándar.

Wikipedia hace referencia a un anterior Gran Renombrado, que aparentemente consistió en el renombre de grupos de USENET. Probablemente lo recordaba de forma inconsciente cuando lo llamé asi. También encontré algunas referencias a un Gran Renombrado posterior en Sphinx, el paquete utilizado para generar la documentación de Python. Zope también tuvo uno, y algunos debates recientes de Py3k utilizan el término para el cambio de PyString a PyBytes (aunque es menor, comparado con los otros).

Los Grandes o Enormes cambios de nombres son a menudo eventos traumáticos para las comunidades de desarrollo de software, porque requieren que los cerebros de los programadores sean recableados, la documentación reescrita y se complica la integración de parches creados antes, pero aplicados después (esto es especialmente problemático cuando existen ramas no renombradas).

Traducido por Joaquín Sorianello.

Revisado por Juan José Conti.

Si encontrás errores en esta traducción, por favor reportalos en un comentario y los corregiremos a la brevedad.

Todas las traducciones de esta serie pueden encontrarse en La historia de Python.


Servidor SMTP para hacer pruebas

Cuando estamos programando, muchas veces necesitamos de un servidor de mails para que nuestro programa envíe todo tipo de mensajes: reportes de error, avisos, passowords luego de una gesistración, "contact us", etc...

Muchas veces no se tiene un servidor SMTP instalado en la computadora de desarrollo, pero si tenemos Python instalado, podemos ejecutar el siguiente comando y tener un servidor de prueba en el que en lugar de enviar los mails porla red, se imprimen por la salida standar:

python -m smtpd -n -c DebuggingServer localhost:25

La historia de Python: Módulos cargados dinámicamente

El siguiente texto es una traducción del artículo Dynamically Loaded Modules de Guido van Rossum publicado en http://python-history.blogspot.com/.

Módulos cargados dinámicamente

La arquitectura de Python permitió, desde un principio, escribir módulos de extensión escritos en C de una forma sencilla. Sin embargo, en los primeros días, la tecnología de carga dinámica era tan oscura que las extensiones tenían que ser enlazadas estáticamente en el interprete de Python, en tiempo de compilación. Para hacer esto, lo módulos de extensión tenían que ser agregados a un script de shell que era usado para generar el Makefile para Python y todos su módulos de extensión.

Aunque este enfoque funcionaba para pequeños proyectos, la comunidad de Python comenzó a producir nuevos módulos de extensión a un ritmo no esperado, y demandaban que los módulos pudiesen ser compilados y cargados en forma separada. Poco después, las interfaces a las APIs de enlace dinámico, propias de cada plataforma, permitieron que la declaración import busque una biblioteca compartida en disco, de forma similar que un archivo ".py". La primera mención de la carga dinámica en los logs del CVS, data de enero de 1992 y el soporte para la mayoría de las plataformas llego a fines de 1994.

El soporte de enlace dinámico probó ser muy útil, pero fue una pesadilla de mantener. Cada plataforma usaba una API diferente y algunas plataformas tenían adicionales. En enero de 1995, el soporte para enlace dinámico fue reestructurado, de forma tal que todo el código fue concentrado en un solo archivo fuente. Sin embargo, este enfoque resulto en un largo archivo abarrotado de directivas condicionales de compilación (#ifdef). En diciembre de 1999, fue reestructurado de nuevo, con la ayuda de Greg Stein, para que el código correspondiente a cada plataforma quede ubicado en un archivo específico para cada una (o familia de estas).

Aunque Python soportaba la carga dinámica de módulos, el procedimiento para construirlos, a menudo seguía siendo un misterio para muchos usuarios. Un número cada vez más grande de usuarios fueron construyendo módulos, especialmente con la introducción de herramientas como SWIG. No obstante, un usuario deseoso de distribuir un modulo de extensión enfrentaba grandes obstáculos para lograr que el modulo compile en todas las combinaciones de plataformas, compiladores y linkers. En el peor escenario posible, un usuario tenía que escribir su propio Makefile y script de configuración para establecer los flags correctos para el compilador y el linker. Además, requería que los usuarios finales tuviesen una distribución de Python con el código fuente.

Finalmente, se creo una herramienta para construir las extensiones, llamada distutils, que permitió construir e instalar los módulos de extensión en cualquier plataforma. Las opciones necesarias para el linker y el compilador están escritas desde el makefile de Python a un archivo de datos, que es consultado por distutils cuando construye módulos de extensión. Escrito en gran parte por Greg Ward, las primeras versiones de distutils fueron distribuidas en forma separada, para dar soporte a versiones viejas de Python. Desde Python 1.6 está integrado en las distribuciones, como un modulo de la biblioteca estándar.

Cabe destacar que distutils hace mucho mas que simplemente construir módulos de extensión desde código fuente en C. Puede también instalar módulos y paquetes de Python puro, crear instaladores ejecutables para Windows y correr herramientas de terceros como SWIG. Desgraciadamente, su complejidad ha causado que sea maldecida por mucha gente y no reciba la atención que se merece a la hora de mantenerla. Como resultado, recientemente, las alternativas de terceros ( especialmente ez_install, también llamada "eggs") se hicieron mas populares, desgraciadamente causando fragmentación en la comunidad de desarrolladores, así como quejas cuando no funcionan. Parece que el problema en toda su generalidad es inherentemente difícil.

Traducido por Joaquín Sorianello.

Revisado por Juan José Conti.

Si encontrás errores en esta traducción, por favor reportalos en un comentario y los corregiremos a la brevedad.

Todas las traducciones de esta serie pueden encontrarse en La historia de Python.


os.path en el settings.py de Django para mayor comodidad

En el archivo de configuración settings.py de un proyecto Django, por lo general tenemos que setear variables como MEDIA_ROOT o STATIC_DOC_ROOT. Su contenido en una instalación Windows suele ser algo como: 'C:\Windows\camino\hasta\mi\projecto'. Y en Linux: '/home/usuario/camino/a/mi/proyecto'. El problema surge cuando el proyecto es desarrollado en varias máquinas a la vez, y con distintos sistemas operativos. Más aún, si hacemos lo anterior, seguramente versionaremos el proyecto y con él, al archivo de configuración. No sería raro que tras una actualización, el archivo se actualice con los valores que puso algún compañero de trabajo.

Mi solución es definir primero una variable para el proyecto:

PROJECT_PATH = os.path.abspath(os.path.dirname(file))

Luego podemos usarla para definir el path absoluto a la carpeta con archivos de media:

MEDIA_ROOT = os.path.join(PROJECT_PATH, 'media')

nuestros templates:

TEMPLATE_DIRS = (

# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".

# Always use forward slashes, even on Windows.

# Don't forget to use absolute paths, not relative paths.

os.path.join(PROJECT_PATH, 'templates')

)

o cualquier otra variable de configuración que requiere una ruta de directorios.

Con esta solución podemos cambiar el proyecto de carpeta, disco o computadora y seguirá funcionando.


Aplicar un decorador a todas las funciones de un módulo en Python

En la lista de PyAr preguntaron si había alguna forma de aplicar un decorador a todos las funciones de un módulo. Envié una solución sin probarla, que al verla unos días más tarde parece bastante buena :)

La comento aquí con un ejemplo. modulo.py contiene definiciones de funciones:

def a():
pass

def b():

print 42

def c():

a()

b()</pre>

y decoradores.py un decorador que imprime el nombre de la función llamada:

def nombrador(f):

    def inner(*a, **kw):

        print "Ejecutando %s" % f.__name__

        return f(*a, **kw)

    return inner

(Si no sabés lo que es un decorador, podés leer mi post Decoradores en Python I: Introducción)

En lugar de modificar las definiciones de funciones en modulo.py para aplicar el decorador a cada una de las funciones, ya sea usando el azúcar sintáctica de Python:

@nombrador

def a():

    ...

o mediante una llamada a la función:

a = nombrador(a)

podemos agregar el siguiente código al final de modulo.py:

for n,v in locals().items():

   if inspect.isfunction(v) and n != 'nombrador':

       locals()[n] = nombrador(v)

Vamos a explicarlo:

la llamada a la función built-in locals retorna un diccionario representando el espacio de nombres local: cada clave es un string representando el nombre de un objeto y cada valor es el objeto en si. Iteramos sobre la lista de pares (key, value) del mencionado dict y por cada uno verificamos si:

a) es una función (inspect.isfunction es apropiado para esto)

b) el nombre no es el del decorador que queremos aplicar (para no aplicar el decorador sobre si mismo!)

Si las condiciones a y b se cumplen, podemos guardar en el diccionario del espacio de nombres, bajo el nombre de la función que cumplió las condiciones, una versión decorada de la misma.

Agregamos algo más de código a modulo.py para que se llame a las funciones cuando lo ejecutemos:

if __name__ == '__main__':

    a()

    b()

    c()

Esta es la salida obtenida:

juanjo@fenix:~/python/muchosdecos$ python modulo.py

Ejecutando a

Ejecutando b

42

Ejecutando c

Ejecutando a

Ejecutando b

42

¿Querés probarlo? Bajá muchos.zip

Nota: para acceder a locals() no se puede utilizar iteritems por que el diccionario cambia durante la ejecución.


Decoradores en Python (III) - Clases decoradoras

Siguiendo con la serie de posts sobre decoradores en Python, y fiel al espíritu que los originó (ir mostrando lo que voy aprendido a medida que necesito resolver problemas específicos o descubro aplicaciones concretas) hoy les traigo un nuevo uso para los decoradores en Python: funciones caché.

Anteriormente: Decoradores I, Decoradores II.

Funciones caché

Una función caché[0], es aquella que siempre que se le pide que compute un resultado para un grupo de parámetros dado, primero se fija en una memoria interna si no realizó ya el cálculo. Si ya lo hizo, retorna el valor computado anteriormente. Si aún no lo hizo, computa el valor, lo guarda en una memoria interna y luego lo retorna.

Esta técnica es muy útil en funciones que requieren un cómputo intensivo y obtener un resultado lleva mucho tiempo. Permita acelerar sustancialmente los tiempos de ejecución a cambio de utilizar más memoria.

La siguiente es una forma de implementarlo en Python para un computo en particular:

cache = {}

def fmem(arg):

    if arg in cache:

        print "Recuperando valor de la memoria"

        return cache[arg]

    else:

        r = (arg ** 10) * (arg ** -5)

        cache[arg] = r

        return r

Como memoria se utiliza un diccionario y el argumento de la función fmem es la clave del diccionario[1].

Este es el resultado de utilizarla en el intérprete interactivo:

>>> fmem(1)

1.0

>>> fmem(2)

32.0

>>> fmem(2)

Recuperando valor de la memoria

32.0

Decoradores con estado

En esta implementación, la técnica de memorización se mezcla con el cálculo que era el objetivo original de la función. Si queremos aplicar la técnica sobre distintas funciones vamos a tener que entrometer la implementación de la caché en todas las funciones. Peor aún, si en el futuro se quiere realizar un cambio en la forma de almacenar y recuperar los valores almacenados, ¡tendríamos que modificar todas las funciones! La forma de resolver estos problemas es implementando un decorador que agregue esta funcionalidad a las funciones decoradas: resolvemos ambos problemas, el de intrución y el de mantenibilidad. Todo el código que provee esta funcionalidad extra es encapsulado en el decorador.

Las funciones decoradoras, como las que vimos en los anteriores artículos, no nos sirven para esta tarea. Necesitamos un decorador que pueda almacenar un estado. Ya que cualquier callable puede ser un decorador, implementaremos el decorador mediante una clase.

Funciones caché con clases decoradoras

La definición de la clase decoradora consiste en dos métodos:

  • un método de inicialización, dónde se inicializa el atributo cache con un diccionario vacío y se guarda una referencia a la función decorada.
  • un método __call__ que será ejecutado cuando se llame a la función decorada.
class mem(object):



    def __init__(self, g):

        self.cache = {}

        self.g = g



    def __call__(self, arg):

        if arg in self.cache:

            print "Recuperando valor de la memoria"

            return self.cache[arg]

        else:

            r = self.g(arg)

            self.cache[arg] = r

            return r

Luego, lo único que resta es decorar todas las funciones que querramos "dotar de memoria" para obtener mejoras de performance en su ejecución:

@mem

def f(arg):

    return (arg ** 10) * (arg ** -5)

La salida obtenida al ejecutar la función decorada en el intérprete interactivo es la misma qué en el ejemplo anterior:

>>> fmem(1)

1.0

>>> fmem(2)

32.0

>>> fmem(2)

Recuperando valor de la memoria

32.0

Más

La implementación del decorador mem solo sirve para decorar funciones que reciben un único argumento. Podemos mejorar su definición para que pueda decorar funciones con cualquier número de argumentos:

class mem2(object):



    def __init__(self, g):

        self.cache = {}

        self.g = g



    def __call__(self, *args):

        if args in self.cache:

            print "Recuperando valor de la memoria"

            return self.cache[args]

        else:

            r = self.g(*args)

            self.cache[args] = r

            return r
@mem2

def f2(arg1, arg2):

    return (arg1 ** 10) * (arg2 ** -5)

Notas

[0] Se puede leer más sobre este concepto en Caching Function Results:Faster Arithmetic by Avoiding Unnecessary Computation de Stephen E. Richardson [SMLI TR-92-1]

[1] Esta implementación tiene la limitación de que si el argumento de la función es un objeto mutable, no podrá ser usado como clave de un diccionario y se lanzará una excepción.


Conjuntos en Python

Cuando estaba preparando mi charla introductoria a Python, le pasé mis slides a un amigo para que me diga su opinión y una de las cosas que me dijo fue

Nunca senti que set sea algo nativo de python, le daria mas importancia a los diccionarios, aunque tal vez tu publico este mas interesado en sets, no lo se.

Me sorprendió el comentario. Para mi, set es un tipo de dato muy útil y poderoso. En este post voy a intentar hacer una apología de set, el tipo de dato que incorpora Python para representar la noción matemática de conjunto.

Presentación

Un objeto set es una colección sin orden de objetos hasheables. Puede contener objetos de todos los tipos inmutables de Python, pero no los contenedores mutables como listas. También puede contener objetos de clases definidas por el usuario (Los objetos instancias de clases definidas por el usuario son por defecto hasheables.).

Los conjuntos se pueden crear, por ejemplo, a partir de una lista. Podemos quitar elementos (al azar o uno en concreto) o agregarlos:

>>> heladera = ['huevo', 'huevo', 'queso', 'leche', 'pera', 'pera', 'pera']

>>> alimentos = set(heladera)

>>> alimentos

set(['queso', 'leche', 'huevo', 'pera'])

>>> alimentos.pop()

'queso'

>>> alimentos.remove('leche')

>>> alimentos

set(['huevo', 'pera'])

>>> alimentos.add('empanada')

>>> alimentos

set(['empanada', 'huevo', 'pera'])

Prueba de pertenencia

Otra función muy común y útil es probar la pertenencia de objetos al conjunto:

>>> 'empanada' in alimentos

True

>>> 'leche' in alimentos

False

>>> 'leche' not in alimentos

True

Iterar sobre conjuntos

Podemos interar sobre conjuntos de la misma forma que lo hacemos sobre listas:

>>> for a in alimentos:

...     "Debo comer " + a

...

'Debo comer empanada'

'Debo comer huevo'

'Debo comer pera'

Operaciones sobre conjuntos

Los conjuntos en Python soportan las operaciones típicas de conjuntos: restas, intersección, unión y diferencia simétrica (los elementos que están en uno de los conjuntos, pero no en ambos). Repasar operaciones con conjuntos.

>>> frutas = set(['banana', 'naranja', 'pera'])

>>> frutas - alimentos

set(['banana', 'naranja'])

>>> alimentos - frutas

set(['huevo', 'empanada'])

>>> frutas & alimentos

set(['pera'])

>>> frutas | alimentos

set(['huevo', 'empanada', 'pera', 'banana', 'naranja'])

>>> frutas ^ alimentos

set(['huevo', 'empanada', 'banana', 'naranja'])

También podemos preguntar sin un conjunto es subconjunto de otro. En los ejemplos se utiliza set(), el conjunto vacío:

>>> alimentos < set()

False

>>> set() < alimentos

True

>>> set() > alimentos

False

>>> alimentos <= alimentos

True

El problema de las dos comisiones

Este problema está basado en un caso real y lo escuché en la charla Escribí menos código, pensá como un (buen) matemático de Gustavo Carmona (FCEYN - UBA) bio y Matías A Graña (FCEyN - UBA) bio.

Se tienen dos archivos de textos con una lista de e-mails en cada uno. Cada archivo tiene los mails de los funcionarios de una comisión; los archivos no están bien depurados, por lo que pueden contener direcciones repetidas; hay funcionarios trabajando en las dos comisiones. Luego de una reunión en la que trabajaron ambas comisiones, se generó un material que se necesita enviar a todos los participantes. ¿Cómo obtener la lista de destinatarios?

archivo1

dir1@mail.com

dir2@mail.com

dir3@mail.com

dir1@mail.com
archivo2

dir21@mail.com

dir23@mail.com

dir3@mail.com

dir1@mail.com

Queremos una tercera lista que tenga la primera más la segunda, pero que no estén repetidos.

El enfoque tradicional que utilizaría un programador para resolver este problema mediante bucles es:

def unionlarga(l1, l2):

    l3 = []

    for x in l1:

        if not x in l3:

            l3.append(x)

    for x in l2:

        if not x in l3:

            l3.append(x)

    return l3

Supongamos que ya tenemos los archivos leídos y almacenamos el contenidos en listas:

>>> unionlarga(archivo1, archivo2)

['dir1@mail.com', 'dir2@mail.com', 'dir3@mail.com',

'dir21@mail.com', 'dir23@mail.com']

La solución es genérica para cualquier lenguaje. Sin embargo, puede lograr una mejor solución utilizando sets en Python:

>>> list(set(archivo1) | set(archivo2))

['dir21@mail.com', 'dir3@mail.com', 'dir2@mail.com',

'dir1@mail.com', 'dir23@mail.com']

Convertimos ambas listas a conjuntos (con lo que se eliminan los repetidos dentro de las listas), realizamos la unión de ambos conjuntos (con lo que se eliminan los repetidos entre listas y finalmente se convierte el resultado en una nueva lista.

Más sobre conjuntos en la referencia del lenguaje.