Scala vs. Python vs. Lua

abr 09 2013

Hace bastante tiempo que ando comentando cosas de estos tres lenguajes: Scala, Python y Lua. Hasta el momento no he hecho una comparativa entre ellos y creo que es el momento de hacerlo, siempre desde el punto de vista de un programador. Más que llegar a la conclusión de cuál es mejor o peor, quisiera dar una idea de porqué los recomiendo, a los tres, sin decantarme por sólo uno de ellos. Si buscabas razones para quedarte con uno de ellos, tampoco deberías desestimar otros similares como Ruby, Groovy, Haskel, Clojure o Erlang. De todos hay cosas qué aprender.

Python

Quizás Python sea el lenguaje más asequible para un programador que empieza o que busca un segundo lenguaje. Su aprendizaje es sencillo, mientras que su potencia y ubicuidad lo hace ideal desde los pequeños scripts que podamos necesitar en nuestro día a día, hasta escalar a servidores empresariales de tipo medio.

Puede que a muchos disguste python por su identación forzada o por su particular modelo de datos, por citar dos de las características más criticadas. Sin embargo, confía en mí si te digo que python es uno de los mayores compendios de sabiduría que puedes tener al alcance de tus manos. Cualquier cosa que creas extraña o fuera de lugar, seguramente tenga su buena explicación. El sistema colaborativo que hace evolucionar a python (conocido como PEP-Python Enhancement Proposals) consigue que todo el saber de la comunidad python termine decantándose hace un modelo de evolución del lenguaje que lo hace único, con el que mejora calmadamente con cada versión. Operaciones con números grandes, algoritmo MRO para herencia múltiple, estructuras de datos optimizadas (heapq, deque,…), ordenaciones por clave, operaciones sobre secuencias (sum, any, all,…)… son sólo algunos ejemplos de optimizaciones que el usuario usa sin ser realmente consciente de la cantidad de trabajo que le está ahorrando. En python casi siempre hay una forma de hacer las cosas correctamente, y además suele ser la mejor.

Lua

Desde mi punto de vista, considero Lua como un python minimalista. Sin objetos, sin posibilidad de construir tus propios tipos de datos, pero se apaña con un sólo tipo de estrutura table para montar un sistema de herencia y emular algunos tipos de datos. Si lenguajes como python te parece complicados, no comprendes conceptos como la herencia, la creación de tipos o para qué sirven las metaclases, la simplicidad de lua hará que entiendas mejor estos conceptos.

El reducido tamaño del intérprete de Lua lo hace apropiado para ser empotrado en otras aplicaciones. Lo tenemos en gestores de paquetes (RPM), bases de datos (mysql-lua), e IDEs (Scite), aunque quizás sea más famoso por ser el motor de script de juegos como World of Warcraft.

En cuanto a sintáxis, también goza de un minimalismo que, a veces, desearías tuviera python. Posee cierta relajación en la llamada a funciones que permite usarlo para crear DSLs (Lenguajes Específicos del Dominio), aunque quizás su mejor uso sea como lenguaje de descripción de datos en sustitución de xml, yaml o ficheros ini.

Scala

Reconozco que soy un ferviente partidario de la Programación Funcional. Python tiene algún aspecto de este paradigma, pero cada vez parece más diluido dentro del sistema de Clases Abstractas (ABC-Abstract Base Classes) que empiezan a generalizarse en python. La estrategia de python es optimizar el uso de estas clases abstractas, independientemente de las clases que deriven luego de ellas. Aunque es un buen enfoque de optimización, siempre estará limitado a tiempo de ejecución.

Scala posee un potente sistema de tipado estático de datos que posibilita la inferencia del tipo de una operación, lo que permite cierta relajación en el tipado que lo hace muy similar al tipado dinámico. Pero la posibilidad de crear nuevos tipos, ya no sólo de objetos, si no también a partir de funciones o de patrones de código, consigue interfaces más robustos y que sea el compilador quien optimize el código, antes de su ejecución.

Así que tenemos que scala es funcional, con un potente sistema de tipos y, además, 100% compatible con Java. ¿Se puede pedir algo más?

Pues sí. Incorpora el llamado modelo Actor para programación concurrente. Con los actores, en lugar de compartir un espacio común de memoria entre los distintos procesos concurrentes, se establece un sistema de mensajes que son enviados y recibidos. Este modelo se ha mostrado bastante eficaz en sistemas de alta demanda como son algunas webs como twitter o linkedin.

En cuanto a la sintáxis, scala también posee algunas normas relajadas para la creación de DSLs muy similar a lo que se ve en Groovy. Algunos lenguajes DSL se usan en frameworks de creación webs, como Play2, o para crear conjuntos de pruebas (ScalaUnit).

Conclusión

Espero que te haya convencido para que eches un vistazo a algunos de estos lenguajes, aunque los tres sean altamente recomendables. Si tuviera que resumir en pocas líneas lo dicho hasta ahora, sería así:

  • Python: navaja suiza de los lenguajes. Sirve para todo y está presente en cualquier sitio. Es un compendio de sabiduría para hacer las cosas de la mejor forma, aún sin proponértelo.

  • Lua: lenguaje minimalista. Ayuda a comprender mejor algunos conceptos de programación. Es el lenguaje que me gustaría que tuviera todo navegador en lugar de javascript.

  • Scala: funcional y con potente sistema de tipos. Su implementación del modelo actor lo hace idóneo para la creación de sistemas de alta demanda de accesos.

4 responses so far

De Generaciones Perdidas

mar 31 2013

Allá por los 80, en Zaragoza, cuando internet era tan sólo la i del protocolo TCP/IP y los meros mortales se apañaban con sus modems para acceder a alguna BBS local, y donde la única autopista de la información consistía en una cinta semanal de 200 MB. que llegaba desde el CERN de Ginebra, existía un grupo de hackers, en el buen sentido de la palabra1, que abarrotaban las pocas salas de informática que tenía la Universidad de Zaragoza. No existían enseñanza de informática y los conocimientos erán compartidos entre todos sin reservas.

Fue una época convulsa, presagio de los cambios que iban a venir y que lo cambiarían todo. Luchas por el espacio cybernético, espionaje y contraespionaje, virus y antivirus,…un mundo cruel que quedó atrás y que originó una “generación perdida”, una de tantas, pero de un nivel tecnológico sin paragón que hizo puntera a Zaragoza y de la que muy pocos tienen siquiera una mínima noción de su existencia.

A veces me he preguntado si valdría la pena dar a conocer a esta generación y todo lo que hicieron. Pero sus integrantes, una vez rehechas sus vidas ajenas a la universidad, incluso dejando sus estudios en la cuneta, callan y no parece que deseen que se hable del tema. No esperes que yo lo haga aquí, pero si quieres una pequeña muestra de lo que pasó existe una serie de historias contadas en el libro “Sueños Electrónicos. Emprendedores en la Red” de José Carlos Arnal (ISBN: 9788483241172) que puede darte una buena idea, aunque incompleta. Como se suele decir, “existen dos tipos de hackers: los buenos y los famosos”. Si a álguien le interesa el tema, que lea este libro, porque de los buenos hackers nunca se hablará.

En este país nunca aprenderemos. Ya sea por guerras, conflictos políticos o por la avarienta corrupción, seguimos quemando generaciones en la gran pira de calamidades inevitables en la que se ha convertido la historia de España. Sirva este artículo como callado homenaje a esta De-Generación Perdida junto con el que escribió Gaudi en su bitácora hace ya un año.  


  1. Extraer conocimientos a través de la tecnología. 

No responses yet

Mutable o inmutable, he ahí el dilema

mar 22 2013

NOTA: Disponible también como ipynb

Quien se enfrenta a la documentación de python por primera vez se pregunta porqué esa insistencia en mantener tipos de datos duplicados en versiones mutables e inmutables. Tenemos listas y tuplas que casi hacen lo mismo. En python3, tenemos el tipo inmutable bytes y el mutable bytearray. ¿Qué sentido tiene tener “duplicados” algunos tipos en sus dos versiones? La única explicación que se puede encontrar en la documentación es que los tipos inmutables son más apropiados para usarlos como índices en diccionarios. No parece mucha ventaja para la complejidad que aporta.

En este artículo veremos qué implica la mutabilidad de un tipo de dato y en qué puede sernos útil usar un tipo mutable u otro inmutable.

¿Qué es lo que cambia?

Antes de explicar nada, veamos si somos capaces de saber qué está cambiando. Veamos dos códigos muy similares:

>>> a=(1,2,3,4)
>>> a+=(5,6,7)
>>> print(a)
(1, 2, 3, 4, 5, 6, 7)
>>> a=[1,2,3,4]
>>> a+=[5,6,7]
>>> print( a )
[1, 2, 3, 4, 5, 6, 7]

Parece que ambos códigos hagan lo mismo: añadir un fragmento, en sus versiones tupla y lista, respectivamente. Vamos a analizarlo mejor. Para saber qué pasa, usemos la función id(). Esta función devuelve un identificador de un objeto de tal modo que si dos objetos tienen el mismo identificador, entonces son el mismo objeto.

>>> a=(1,2,3,4)
>>> print(id(a))
192021604
>>> a+=(5,6,7)
>>> print(id(a))
189519828
>>> a=[1,2,3,4]
>>> print(id(a))
189780876
>>> a+=[5,6,7]
>>> print(id(a))
189780876

En la versión tupla, se ha creado una nueva tupla para realizar la operación, mientras que en la versión lista se ha usado la misma lista, modificándose con el resultado. Si cambiamos el operador += por una versión más explícita tal vez se vea mejor:

>>> a=(1,2,3,4)
>>> a=a+(5,6,7)
>>> a=[1,2,3,4]
>>> a.extend([5,6,7])

Al operar con tuplas, los operandos no cambian de valor, creándose una nueva tupla como resultado de la operación. Podríamos sustituir toda la operación por el resultado final y el código funcionaría igual. En el caso de las listas, la lista se modifica “in situ” durante la operación. En estos casos, cambiar la expresión por el resultado final no garantiza que el programa funcione igual. Se necesita pasar por todos y cada uno de los estados intermedios para asegurar que todo funcione igual.

Esta propiedad de poder cambiar una expresión por su resultado final es conocida por Transparencia referencial en programación funcional. Por lo general, los tipos inmutables se adecúan mejor a operaciones de cálculo donde el resultado final depende únicamente de los argumentos de entrada. Por otro lado, los tipos mutables son útiles para salvaguardar estados intermedios necesarios para la toma de decisiones durante la ejecución de un programa.

Por lo general, se saber elegir un tipo mutable o su homólogo inmutable es todo un arte. Ante la duda, los tipos inmutables son más fáciles de rastrear. Así mismo, veremos en próximos artículos que los tipos inmutables ayudan bastante en programación concurrente, por si estás pensando en programación multiproceso.

Ejemplos de tipos propios

La mutabilidad e inmutabilidad va más allá de los tipos estándar de python. Nosotros mismos podemos hacer nuestras propias clases mutables o inmutables, según nuestras necesidades.

Pongamos que creamos una clase Point para definir puntos, junto con unas sencillas operaciones para sumar, restar y desplazar. Nuestra idea es poder usar estos objetos en expresiones, por lo que es práctica común que toda operación devuelva el resultado como un punto para seguir encadenando operaciones.

Una versión “mutable” del objeto sería así:

class PointMutable(object):
    def __init__(self, x, y):
        self.x=x
        self.y=y
 
    def __repr__(self):
        return "<Point(%d,%d)>"%(self.x,self.y)
 
    def __sub__(self, other):
        self.x-=other.x
        self.y-=other.y
        return self
   
    def __add__(self, other):
        self.x+=other.x
        self.y+=other.y
        return self
 
    def move(self, dx, dy):
        self.x+=dx
        self.y+=dy
        return self

En todas las operaciones, operamos el objeto consigo mismo y lo retornamos como resultados. Si probamos, vemos que no funciona tal como se esperaba:

>>> p1=PointMutable(1,1)
>>> p2=PointMutable(-1,1)
>>> print p1.move(1,1) - (p1+p2).move(2,2)
<Point(0,0)>

Devuelve <Point<0,0> independientemente de los valores iniciales y de los desplazamientos que demos. Al ser nuestro objeto mutable, cada operación lo va cambiando. Al final, toda la expresión se reduce a una simple resta p1-p1, que sería la última operación y que da siempre <Point(0,0)>. No parece que sea el resultado esperado.

Debemos adoptar una táctica más defensiva: el objeto nunca debe cambiar durante el cálculo. Como resultado de cada operación deberemos devolver una nueva instancia y que el estado de ésta, o sea, sus atributos, no se alteren a lo largo del cálculo:

class PointInmutable(object):
    def __init__(self, x, y):
        self.x=x
        self.y=y
 
    def __repr__(self):
        return "<Point(%d,%d)>"%(self.x,self.y)
 
    def __sub__(self, other):
        return PointInmutable(self.x-other.x,self.y-other.y)
   
    def __add__(self, other):
        return PointInmutable(self.x+other.x,self.y+other.y)
 
    def move(self, dx, dy):
        return PointInmutable(self.x+dx,self.y+dy)
<code>
<code lang="python">
>>> p1=PointInmutable(1,1)
>>> p2=PointInmutable(-1,1)
>>> print p1.move(1,1) - (p1+p2).move(2,2)
<Point(0,-2)>

Siendo perfeccionistas, deberíamos blindar mejor los atributos de la clase para hacerlos de sólo lectura mediante properties.

En este ejemplo hemos podido ver los resultados imprevisibles que podemos tener si abusamos de la mutabilidad. Estos problemas se ven incrementados si hubiera varios hilos de ejecución y cada hilo estuviera modificando las mismas variables comunes. Lamentablemente, es un caso bastante común debido a una mala previsión a la hora de iniciar un proyecto de desarrollo. Pero ésto lo veremos en un próximo artículo.

No responses yet

Mutabilidad de Listas

mar 16 2013

NOTA: puedes visionar este artículo y descargártelo como notebook ipython en http://nbviewer.ipython.org/5177340

Mucha gente, cuando se enfrenta por primera vez al lenguaje python, no entiende bien el concepto de “inmutabilidad” que tanto repite la documentación al tratar de diferenciar algunos tipos contenedores como tuplas, listas, conjuntos y diccionarios.

Por lo general, la gente formada en lenguajes de programación clásicos tiene la idea de que las variables son porciones de memoria donde colocar valores. Que una variable no se éso, variable, resulta un contrasentido. Han visto constantes, pero sólo sirven para inicializar variables y poco más. Si en su carrera hubieran sido formados en algún lenguaje funcional se darían cuenta que hay quienes piensan que las variables que cambian de valor son las raras, que lo más natural es que una variable conserve su valor inicial, o sea, que sea inmutable.

Por poner un ejemplo, el siguiente código está basado en una pregunta reciente en la lista python-es. Tenemos una lista de pares y queremos quitar las parejas repetidas con orden cambiado:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def quitar_dup(lista):

    for item in lista:

        item.reverse()

        if item in lista:
            lista.remove(item)

    return lista

L=[[1,2],[1,3],[2,1],[3,1]]

print quitar_dup(L)  #res: [[1, 3], [3, 1]]

A simple vista, el código parece correcto, pero tenemos dos operaciones que pueden mutar listas: .reverse() y .remove(). De hecho, el resultado es incorrecto: [[1, 3], [3, 1]]

A medida que recorremos la lista en el bucle for, la lista se va modificando, lo que da lugar a resultados inesperados. Si no lo ves bien, basta añadir algunos prints en lugares estratégicos para que comprobar lo que pasa. De hecho, sólo existen dos iteraciones para cuatro elementos que tiene la lista.

Otro tipo de casos son cuando pasamos listas a funciones:

1
2
3
4
5
6
7
8
9
10
11
12
>>> def add(a, l):
...   if a not in l:
...     l+=[a]
...   return l
...
>>> L=[1,2,3]
>>> add(1,L)
[1, 2, 3]
>>> add(4,L)
[1, 2, 3, 4]
>>> L
[1, 2, 3, 4]

Como efecto colateral, la función ha modificado la lista pasada como argumento, algo que no es siempre deseable. El problema se agrava más si empleamos listas en valores por defecto:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> def add(a, l=[]):
...   if a not in l:
...     l+=[a]
...   return l
...
>>> add(1)
[1]
>>> add(2)
[1, 2]
>>> add(3,[])
[3]
>>> add(4)
[1, 2, 4]

Como se puede ver, aunque intentemos resetear el valor por defecto, la función tiene un efecto memoria que es imposible de eliminar. Este efecto es a veces buscado, pero en general debe ser siempre evitado ya que desvirtúa el sentido que tiene dar valores por defecto.

Estos efectos son todavía más perniciosos con la funciones lambda. Al carecer de una clausura como las funciones, la evaluación de una función lambda depende del scope donde han sido definidas. Por ejemplo, observa esta creación de una lista de funciones:

1
2
3
4
5
6
7
fns=[]

for i in range(5):
    fns.append( lambda x: x+i)

print fns[1](10)
print fns[2](10)

Siempre añade 4 al argumento, que es el valor de i al acabar el bucle, independientemente de qué valor tenía esta variable en el momento de crear la función lambda. No es de extrañar que se recomiende dejar de usar estas funciones.

Por último, otro efecto funesto de la mutabilidad de las listas aparece en la creación de listas multidimensionales (aka matrices). Una forma rápida de crear una matriz de 2×2 es: [[0]*2]*2. El problema aquí está en que cuando clonamos listas, en lugar de copiar los elementos, los enlaza entre sí. Quizás se vea mejor si hacemos alguna operación:

>>> l=[[0]*2]*2
[[0, 0], [0, 0]]
>>> l[0][0]
0
>>> l[0][0]=1
>>> l
[[1, 0], [1, 0]]
>>> l[0] is l[1]
True

Los elementos l[0] y l[1] son el mismo elemento. Que los elementos de una lista puedan estar entrelazados resulta muy interante para algunos algoritmos de búsquedas. Pero hay que conocer bien lo que estamos haciendo si no queremos llevarnos alguna sorpresa.

Recomendaciones para hacer código funcional

Copia de listas

En funciones y métodos, si recibimos una lista como argumento, la primera acción defensiva que deberíamos hacer es copiar la lista en una variable local y trabajar solo con la variable local desde ese momento. Con una asignación directa no se realiza una copia, más bien estaríamos enlazando una nueva referenciasin solucionar nada.

La forma consensuada entre programadores python de copiar una lista es con la operación de spliting L[:], aunque sirven otras operaciones idempotentes como L*1 ó L+[]1. Para listas de elementos entrelazados tendremos que acudir a otros mecanismos de copia como los que ofrece el módulo copy, aunque no será frecuente que lo necesitemos.

1
2
3
4
5
def add(a, lista):
  l=lista[:]
  if a not in l:
    l+=[a]
  return l

En cuanto a los argumentos por defecto, lo mejor es no usar nunca una lista para tal cosa. Una buena estrategia defensiva consiste en usar None de esta forma:

1
2
3
4
5
def add(a, lista=None):
  l = [] if lista is None else lista[:]
  if a not in l:
    l+=[a]
  return l

Operaciones inmutables con listas

En cuanto a evitar las operaciones que mutan listas, siempre hay alternativas inmutables de todas estas operaciones. El siguiente cuadro puede servir como referencia:

Mutable Inmutable
L.append(item) L+[item]
L.extend(sequence) L + list(sequence)
L.insert(index, item) L[:index] + [item] + L[index:]
L.reverse() L[::-1]
L.sort() sorted(L)
item = L.pop() item,L = L[-1],L[:-1]
item = L.pop(0) item,L = L[0],L[1:]
item = L.pop(index) item, L = L[item], L[:item]+L[item+1:]
L.remove(item) L=L[:item]+L[item+1:]
L[i:j] = K L[:i] + K + L[j:]

A la hora de decidir qué versión usar, la versión inmutable es más apropiada para programación funcional y resulta incluos más intuitiva de interpretar. No es extraño ver errores de código donde se espera resultados de las operaciones .sort() o .reverse(), que siempre devuelven None. Para el intérprete de python no hay error, pero a veces nos será difícil darnos cuenta de estos errores:

1
2
3
4
5
6
>>> #MODO ERRÓNEO: machacamos la lista con None
>>> l = [3,5,1,2,4]
>>> l_2 = [x*x for x in l.sort()]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not iterable
1
2
3
4
5
>>> #MODO CORRECTO
>>> l = [3,5,1,2,4]
>>> l_2 = [x*x for x in sorted(l)]
>>> l_2
[1, 4, 9, 16, 25]

  1. De hecho, la operación L*1 es más eficiente que L[:]

One response so far

Dobleces en python

feb 27 2013

En el último artículo del blog contaba en qué consistía doblar código1:

“…un código que se pliega sobre sí mismo. Un código que te lleva desde un principio a un final por el camino más corto.”

Para ilustrar este concepto voy a usar un trozo de código python que se ve frecuentemente entre los programadores recién llegados de otros lenguajes:

NOTA: se usará python 3.x para los siguientes ejemplos

def listar(args):
    num=len(args)
    i=0
    while(i<num):
        x=args[i]
        print(format(len(x)," 5d"), x)
        i+=1

fich=open("fichero.txt")
lineas=fich.readlines()
listar(lineas)

Resumen: se define una función para imprimir en pantalla la lista de líneas leídas de un fichero, precedidas con el número de caracteres que tiene la línea.

Quien tenga algo de experiencia con python seguramente vea raro este código, incluso lo califique como “poco pythónico”. Nombrar a la función listar, como verbo, es señal de que el programador proviene de un lenguaje de programación imperativo. El programador ha buscado en python las mismas estructuras de control que tenía en su lenguaje de origen y sólo ha encontrado familiar la estructura while.

Rebuscando un poco más, tal vez encuentre cómo se usan los bucles for en python:

def listar(args):
    num=len(args)
    for i in range(0,num):
        x=args[i]
        print(format(len(x)," 5d"), x)

Un bucle for se caracteriza por concentrar en una sentencia todo el control del bucle, una gran ayuda visual para quien vaya a leer este código. La variable de control solo se modifica en la sentencia for, lo que evita errores.

Analizando más detenidamente, el bucle for itera sobre una secuencia de enteros dada por range(0,num), de donde se sacan los índices con los que acceder a cada elemento de la lista args. Ésta sería la visión clásica de cómo operar con arrays.

Pero esta visión ha evolucionado con el tiempo hasta llegar al concepto de “Colección” que ya poseen casi todos los lenguajes, bien en su sintaxis, bien como librería estándar. Una “Colección” consiste en un grupo de objetos sobre los que puede iterar. range(0,num) sería una colección ordenada de números. El siguiente paso a dar sería iterar directamente sobre la lista:

def listar(args):
    for x in args:
        print(format(len(x)," 5d"), x)

Con este código hemos conseguido un doble objetivo, mejorar la legibilidad y darle más robustez al despreocuparnos por los índices de acceso. Los índices de acceso fuera de límites suelen ser origen de multitud de errores.

Pero tenemos algo más: al no usar índices hemos generalizado el uso de la función por cualquier secuencia, generador o iterador. Concretamente, los objetos files cumplen con el protocolo iterador, por lo que sería posible pasarlo directamente a esta función sin necesidad de volcar todas las líneas del fichero a una lista:

def listar(it):
    for x in it:
        print(format(len(x)," 5d"), x)

listar(open("fichero.txt"))

Con este último doblez hemos ganado concisión. Pero sobre hemos ahorrado recursos ya que no necesitamos leer todo el fichero en memoria. La lectura del fichero se hará progresivamente en el momento que se solicite la siguiente línea, por lo que este código debería funcionar incluso con ficheros enormes, independiente de la cantidad de memoria disponible. Sólo se empleará la memoria suficiente para cachear una pocas líneas para ir renovándolas a medida que se prosiga la lectura del fichero.

Es un buen momento para comparar esta versión del código con la original de la que hemos partido.

Programación Funcional

Entre doblez y doblez, hemos perdido algunas variables intermedias superfluas. Esta manía por deshacerse de variables intermedias es señal de estar aproximándonos a un estilo de programación funcional.

Una posible definición de “Programación Funcional” sería como aquella programación que difiere la evaluación de una expresión hasta el momento último en el que se vaya a usar su valor.

Para este propósito, la expresión no puede depender de factores externos como variables globales o cambios de estado. No sabemos cuándo será evaluada una expresión. Lo único posible es hacer depender el resultado de una expresión en función del valor de otra, lo que se conoce por “Composición de funciones” (y de ahí el nombre de programación funcional).

Este modo de diferir la evaluación es lo que hicimos con el iterador fichero, cuyas líneas no se leían hasta el momento preciso. La pregunta es ¿podemos mejorar la orientación funcional de nuestro código?

La función listar no devuelve nada, tan sólo busca un efecto colateral. Es lo que se conoce en otros lenguajes como “procedimiento” (procedure). En nuestra metáfora de “pliegues”, una función que no devuelve nada la podríamos considerar como un “corte”, ya que no podemos hacer nada más a partir de aquí.

¿Qué pasaría si queremos cambiar la línea que se imprime en pantalla? ¿Y si queremos parar después de imprimir un número de líneas? En este punto, lo mejor es “desdoblar” el código y darle una orientación más funcional:

from itertools import islice

def lineas(it):
    for x in it:
        yield ("{: 5d} {}".format(len(x), x))

it=lineas(open("pr.py"))

for n, l in enumerate(islice(it,5)):
    print(n, "|", l)

La función listar ha pasado a ser el iterador lineas que retorna las líneas ya formateadas. Asimismo, se ha cambiado la función format por el método format de los strings con el que se pueden formatear mejor varios valores a la vez. La impresión de las líneas en pantalla se deja para el último momento, cuando se necesita ver el resultado. Es en este momento cuando se decide cuántas líneas se van a imprimir, que es lo que hace el islice acortando el iterador lineas a 5 iteraciones. También se usa el iterador enumerate para ir enumerando las líneas a medida que las obtenemos.

Como se ve, una orientación funcional permite encadenar varias operaciones sin necesidad de mantener estados intermedios2. Además de lo que supone de ahorro de recursos, no tener que mantener un contexto con los estados intermedios hará más sencillo migrar la ejecución de un proceso a otro en programación concurrente (eg: multihilo). Hoy en día, tal como evolucionan los ordenadores, quien no programe pensando en la ejecución concurrente terminará programando dos veces.


  1. Puede que prefieras usar el término “refactorizar”, pero he pensado que es mejor dejar este término para la programación orientada a objeto y usar “doblez” para dar una idea más afín a la programación funcional. 

  2. En realidad, no es del todo cierto que la función no dependa de estados externos ya que el iterador it que hemos pasado como argumento podría cambiar externamente entre iteraciones. 

5 responses so far

Collage vs. Origami

feb 21 2013

En mis años como programador he visto mucho código. Lo suficiente como para saber qué estilo tiene el programador que lo escribe, cuáles son sus vicios y cuáles son sus errores de concepto.

Nos resulta imposible definir conceptos como “elegancia” o “claridad” en un código. Son conceptos más afines a la Estética que a las Ciencias de la Computación, siendo difíciles de medir objetivamente. Nunca serán parte de una metodología y, sin embargo, se consideran objetivos a alcanzar por todo programador que se precie.

En nuestra simpleza, necesitamos compartimentar todo lo que conocemos en grandes grupos: imperativo vs. dinámico, tipado estático vs. tipado dinámico, estructural vs. orientado a objeto, orientado a objeto vs. funcional, …

En este mundo de versus, podemos hacer una similitud de la programación de código con el versus “Collage vs. Origami”.

  • Un código collage sería un código construido como parches. Secciones de estructuras distintas, con controles que encajen todas las piezas. Todo pensado para que si una pieza no funciona, se “arranca” y se pone otra encima.

  • Un código origami sería un código que se pliega sobre sí mismo. Un código que te lleva desde un principio a un final por el camino más corto. Si algo no funciona, se vuelve atrás, se “desdobla” y se hace un doblez nuevo.

Al igual que el origami, la creación de código requiere muchas veces pararse a pensar qué parte del código se complicó demasiado y actuar sobre esa parte. Si no se entiende bien, una repetición consistente en teclear decenas de veces ese código ayuda a entender mejor lo que hace y el motivo que hacía que fuera tan complicado. Es muy similar a repetir los mismos dobleces de una figura de papel hasta lograr su doblado perfecto.

Como parte de tribunales de selección para puestos informáticos, me he sentido muchas veces molesto por tener que redactar tediosos cuestionarios de preguntas técnicas que nada valoraban la capacidad real del candidato para el futuro trabajo que iba a realizar. El objeto era realizar una fuerte criba entre los candidatos y, sobre todo, ordenarlos por puntos para hacer más sencilla su selección posterior.

Pero era en la posterior prueba práctica donde mejor podía apreciar la valía de un candidato. Una visión del código fuente y la documentación asociada permitía apreciar claramente quiénes eran los adecuados para el puesto. Pero ¿cómo valorar con una puntuación esta percepción?

Puede que para la próxima vez empiece por pedir al candidato que doble el papel de los enunciados de la prueba. Seguramente consiga saber más de él que de las respuestas que pueda darme.

6 responses so far

Estado de situación

feb 19 2013

Hacía mucho que no actualizaba mi blog y creo que ya es momento de contar algunas cosillas. Me gustaría no tener que hablar de la crisis por la que estamos pasando, al menos en España, pero resulta imposible abstraerse de su influencia.

En mi trabajo como informático en una administración pública, soy testigo de cómo se están aplicando medidas de austeridad sin contramedidas que eviten el colapso del sistema. Sin duda alguna, el objetivo de estas medidas no puede ser otro que liquidar el sistema público para su sustitución por otro basado en el libre mercado, menos solidario y más injusto.

Pero no quiero hablar de la crisis en general. Hay otros muchos blogs que lo hacen mucho mejor que yo y con mayor profundidad. Solo quisiera hablar ahora de los cambios que estoy viviendo en mi entorno de trabajo.

Por un lado, todos aquellos proyectos innovadores en los que estaba implicado han sido cancelados o aminorados por los nuevos gestores. Como primera consecuencia de ello, han quedado sin uso todos mis programas hechos en python con los que extraía la información que necesitaba la dirección para la toma de decisiones (MIS). Confío que algún día vuelva a interesar el estado real de la administración donde trabajo, aunque sospecho que para entonces será demasiado tarde.

Se puede afirmar que ahora mismo ya no programo en python. Por si fuera poco, a ésto hay que añadir la cantidad de proyectos que se nos están quedando huérfanos por no poder renovar su mantenimiento o, simplemente, por quebrar y desaparecer las empresas que los llevaban. Todos estos proyectos están cayendo en mi bandeja de tareas pendientes, con lo que ahora he tenido que volver a programar en lenguajes que creía olvidados como Visual Basic, TSQL o PHP. Un gran paso atrás del que no veo que podamos salir en bastantes años.

Por este motivo, cuando un grupo de entusiastas programadores españoles de python unieron esfuerzos para crear una asociación con la que montar la primera PyCon española, no he podido dejar de pensar lo lejos que estoy en estos momentos de considerarme “programador python”. Se me incluyó en un principio entre los “socios fundadores” de la asociación, supongo que debido a mi labor en la lista de python-es todos estos años. Pero pienso que un socio fundador debe ser una especie de socio “compromisario” que trabaje duro para hacer viable la asociación, sobre todo en sus inicios más difíciles. En estos momentos, no me veo capaz de algo así, ni siquiera de mostrar el más mínimo entusiasmo. Por éso mismo, he preferido declinar el ofrecimiento y figurar como un socio más, a pesar de haber podido disgustar a más de uno.

Como puntilla a lo dicho, últimamente estoy dedicando todo mi tiempo disponible al estudio del lenguaje scala. Además de reconciliarme con la programación funcional, me ha hecho férreo defensor del tipado estático frente al tipado dinámico que usa python. No quiero decir de ninguna manera que abandone python, pero preveo que este año lo dedicaré a convertirme en experto programador de scala. Definitivamente, no encajo preparando la PyCon. Mis disculpas por ello.

4 responses so far

Pruebas básicas de python (y scala)

oct 25 2012

Como continuación del artículo “Cómo contratar a un programador de python” voy a dar las soluciones a algunas de las pruebas básicas que comentaba del proceso de selección. Añado también las soluciones sobre cómo sería con scala y de paso comparamos ambos lenguajes1.

Prueba del “Hello, World!”

Desde que se inventó el lenguaje C, la prueba del "Hello, World!" es la prueba que inicialmente caracteriza a un lenguaje. Y en python sería:

print "¡Hola, Mundo!"

Lo que hay que saber es que este código no funciona en python3 y será lo primero que te pregunte el seleccionador. En python3, el comando print se convierte en una función, por lo que se debe añadir los paréntesis:

print("¡Hola, Mundo!")

Con esta introducción, el seleccionador te puede preguntar alguna cosa más sobre el comando print, como si se puede usar con cualquier objeto o si se puede sacar fácilmente una salida tabulada.

En scala, la respuesta también es simple:

println("¡Hola, Mundo!")

println es una función, como es lógico al tratarse de un lenguaje funcional. Un seleccionador te puede preguntar por el sufijo ln, que no es otra cosa que añadir un salto de línea en la salida. Este código es equivalente a:

print("¡Hola, Mundo!\n")

Se puede ver al final una expresión de escape que representa al salto de línea, universalmente aceptada por un buen número de lenguajes (python incluído).

Detector de palíndromos

Un palíndromo sería una frase que resulta igual leída del principio al final, que del final al principio. Algunos ejemplos:

A man, a plan, a canal: Panama!
Dábale arroz a la zorra el Abad

Para hacer un detector de palíndromos en python tenemos que hacer una función que lo primero que haga sea filtrar todos los espacios en blancos y los signos de puntuación de la frase, convertir todas las letras a minúsculas (o mayúsculas) y comprobar luego si la frase es igual en un sentido y otro:

def isPalindrome(frase):
    frase_limpia=[c.lower() from c in frase if c>="A"]
    return frase_limpia==frase_limpia.reverse()

Como primera aproximación puede valer. Se filtra la frase para que sólo aparezcan letras (c>="A"), quitando espacios y signos de puntuación. También se ha convertido a minúsculas con c.lower() para hacer mejor la comparación. El problema es que no convierte las vocales con tilde a vocales sin tilde, con lo que el palíndromo “Dábale arroz a la zorra el Abad” no lo va a detectar bien. El “candidato” debe darse cuenta de ello e intentar explicar algún modo de resolverlo (cuya resolución completa quedaría fuera de la prueba real y del presente artículo2).

En scala sería algo así:

def isPalindrome(frase:String):Boolean={
    val frase_limpia=frase.filter(_>='A').toLowerCase
    frase_limpia==frase_limpia.reverse
}

Aquí todas las operaciones se han hecho con el tipo String, sin necesidad de pasar a una lista como en python. La última sentencia de la función será el resultado que devuelve la función. En sí, esta función sería correcta, pero usar una variable intermedia como frase_limpia para guardar un estado intermedio va en contra de la filosofía de la programación funcional. Un programa funcional intenta retrasar todo lo que se pueda la evaluación del resultado con el fin de evitar posibles efectos colaterales. Lo ideal sería que sólo se hiciera la evaluación en la última sentencia de la función.

Una forma de hacerlo más “funcional”:

def isPalindrome(frase:String):Boolean={
    val f= (s:String)=>s==s.reverse
    val g= (s:String)=>s.filter(_>='A').toLowerCase
    (f compose g)(frase)
}

…o todo junto en una línea:

def isPalindrome(frase:String):Boolean=
    ((s:String)=>s==s.reverse)(frase.filter(_>='A').toLowerCase)

Expresión regular para encontrar teléfonos en un texto

Lo más simple es buscar 9 dígitos seguidos y sin espacios: "\d{9}"

El examinador probablemente quiera que te esfuerces algo más. Te puede decir que esa expresión no sirve para localizar teléfonos como el +34 666 010101 y quiere saber cómo se podría hacer. La cosa se complica bastante, aunque no es imposible. En este momento NUNCA digas que haría falta leerse bien la documentación y que tendrías que investigar un poco, suena muy mal. Lo de investigar da la idea que vas a buscar una solución por internet, que acabarás en un blog como éste y que vas a copiarte la solución sin siquiera leer cómo funciona.

Más vale que busques otra alternativa, aunque sea sin usar expresiones regulares. Lo que realmente interesa al entrevistador es que tengas ideas propias y originales que se puedan aplicar para resolver el problema que se te plantea.

A continuación pongo una posible solución, aunque te aconsejo que la estudies antes de aplicarla literalmente a tu código:

    "((\+34 )?\d{3} ?\d{6})"

Una conversión de lista de tuplas a diccionario

Este es uno de esos casos que cuesta más explicar el problema que su solución. Se pretende ver si el candidato comprende realmente los conceptos que se tratan, si sabe lo que es una tupla y un diccionario, y si tiene claro el concepto de “mapping” por el cuál se contruye el diccionario.

La solución empieza por poner en dependencia el segundo elemento del primero:

lista=[("a",1),("b",2),("c",3)]
dic=dict(lista)

En scala es muy parecido:

val lista=List(("a",1),("b",2),("c",3))
val dic=lista.toMap

Por complicarlo algo más, se puede pedir que se repita el ejercicio, pero con tuplas de más de dos elementos, con el primero como clave del mapping:

lista=[("a",1,2,3),("b",10,20,30),("c",100,200,300)]
dic=dict((x[0],x[1:]) for x in lista)

En scala es algo más complicado. Lo que en python se entiende por tupla (una secuencia inmutable), en scala son en realidad las listas. Las listas mutables, como se entienden en python, son instancias de la clase MutableList. Las tuplas de scala se usan para paso de parámetros y poco más:

val lista=List(List("a",1,2,3),List("b",10,20,30),
  List("c",100,200,300))

val dic=lista map {x => x.head->x.tail} toMap

Aquí se ve el uso de los atributos head y tail para manejar listas que son tan característicos de los lenguajes funcionales.

Las últimas versiones de Scala incorporan la “comparación de patrones” (“pattern matching”) que consiguen hacer algo más legible el código:

val dic=lista map {case h::t => h->t} toMap

No parece que haya variado mucho. La potencia de los patrones está en poder realizar distintas operaciones según el tipo de los parámetros. En otros lenguajes se necesitaría incorporar expresiones condicionales o sobrecarga de operadores.

Por ejemplo, imagina que la lista tuviera elementos extra como cadenas y números:

val lista=List(List("a",1,2,3),List("b",10,20,30),
  List("c",100,200,300),List(1,2,3,4,5),List(1), "Hola", 1.5)

val dic=lista collect {
    case h::t => h->t
    case s:String => s->List()
   } toMap

Vemos el resultado:

//for((k,v)<-dic) println(k,v)
(Hola,List())
(1,List())
(a,List(1, 2, 3))
(b,List(10, 20, 30))
(c,List(100, 200, 300))

En el primer patrón, h::t (llamado de extracción de lista), sólo encajarán los items que son listas; en el segundo patrón s:String sólo encaja la String "Hola"; mientras que el último elemento de la lista, el double 1.5, no coincide con ningún patrón y será filtrado por el método collect.

Si intentamos hacer lo mismo con python nos saldría un código bastante menos legible:

lista=[("a",1,2,3),("b",10,20,30),("c",100,200,300),(1,),"Hola",1.5]

dic=dict( ((x[0],x[1:]) if isinstance(x,tuple) else (x,()))
    for x in lista if isinstance(x, (tuple, basestring)))

Conclusión

Con estos ejemplos tan sólo he querido darte una idea de qué cosas podemos encontrarnos en una prueba de selección. En programación, debería interesar más la capacidad resolutiva del candidato que su nivel de conocimientos. Una solución innovadora o que aporte otro punto de vista no contemplado en un principio suele ser de más valor que dar la solución correcta. Busca distintos ejemplos de resolver los mismos problemas, incluso en lenguajes de programación diferentes, y estarás preparado para todo reto que se te presente.



  1. Si te preguntas porqué scala, la respuesta simple sería porque es el lenguaje que estoy aprendiendo ahora mismo. Pero si realmente te preguntas qué puede aprender un programador python de un lenguaje como scala, no te pierdas el próximo artículo. 

  2. Busca en la lista python-es 

No responses yet

Entornos virtuales en python 3.3

oct 23 2012

Con la nueva versión de python 3.3 se ha incorporado la propuesta PEP-405 que añade al repertorio interno de python la posibilidad de crear entornos virtuales de modo parecido a virtualenv (vimos esta herramienta hace poco al hablar de la instalación de módulos.

Tal como lo define PEP-405: “Los entornos virtuales poseen su propio conjunto de paquetes instalados localmente, segregados del resto de paquetes instalados del sistema”. Para crear y administrar estos entornos virtuales, se incluye el módulo venv, así como el script pyvenv.py.

Para crear un entorno virtual se puede utilizar el script pyvenv (con python 3.3):

$ pyvenv /ruta/al/nuevo/entorno/virtual

En windows, probablemente haya que ejecutar el script que se encuentra en "C:\> Python33\Tools\Scripts\pyvenv.py". Posiblemente sea más sencillo ejecutar directamente el módulo venv:

$ python -m venv /ruta/al/nuevo/entorno/virtual

Entrando dentro del nuevo directorio, activamos el entorno de modo similar a como hacíamos con virtualenv:

$ cd /ruta/al/nuevo/entorno/virtual
$ source bin/activate

Hay que notar que el script activate se debe ejecutar con source ya que necesita cambiar algunas variables del entorno de ejecución actual.

En windows, se debe ejecutar el script Scripts\activate.bat.

C:\> cd entorno_virtual
C:\> Scripts\activate.bat

Una vez activado el entorno veremos que el prompt de la línea de comandos ha cambiado para indicarnos que estamos dentro. Las variables de entorno han cambiado, como puedes comprobar si miras $PATH. A partir de aquí, la instalación de paquetes con easy_install o pip se realizarán dentro del entorno. A diferencia con virtualenv, el módulo venv no instala en nuestro entorno ninguna de estas herramientas, por lo que lo primero que tendremos que hacer será instalarlas:

(py3.3) $ curl -O http://python-distribute.org/distribute_setup.py
(py3.3) $ python distribute_setup.py
(py3.3) $ easy_install pip

Para desactivar el entorno se ejecuta deactivate.

Aunque venv sigue el mismo funcionamiento que virtualenv, tiene notables carencias:

  • No permite configurar versiones distintas de python

  • No replica la instalación python del sistema en la copia local

  • No permite independizar el entorno virtual del la ruta donde se crea (no relocatable)

En definitiva, los entornos virtuales creados con venv no son completamente independientes de la instalación python del sistema como sí puede hacer virtualenv. Esperemos que se amplien las opciones con nuevas versiones de python. De momento, seguiremos con virtualenv.

4 responses so far

Cómo contratar un programador de python

oct 17 2012

En época de crisis se produce un curioso desequilibrio de las cosas. Por un lado, están los emprendedores que se arriesgan mucho más buscando productos innovadores con los que poder competir mejor, alejados de las tendencias impuestas por las grandes empresas del sector. Son las startups, surgidas como respuestas del estancamiento de la economía actual. Por contra, la crisis también nos obliga a deshacernos de todo aquello que se salga de la norma, siguiendo el dictado de recortes indiscriminados, persiguiendo no se sabe bién qué fines. Al final se consigue mermar la creatividad, a costa de imponer la mediocridad en todo lo que se hace.

Centrándonos en la programación python, es paradójico que en estos momentos la carencia de programadores python esté siendo una de las razones que impida crecer a algunas pequeñas empresas. Empresas que podrían ambicionar a tener un equipo de desarrollo acorde a la demanda que están teniendo, terminan por orientar su negocio a las adaptaciones para uno o dos clientes importantes, abandonando la mejora del producto en manos de la comunidad de software libre que lo crea interesante.

Antes de seguir, habría que precisar a qué nos referimos cuando hablamos de programadores python. No basta con saber la sintáxis del lenguaje, del mismo modo que no es suficiente saber coser para ser cirujano. Se está buscando un perfil concreto de programador, capaz de dar soluciones creativas e ingeniosas, y que sea capaz de expresarlas como código python al resto del equipo de desarrolladores.

Si te estás preguntándo si tú encajarías en este perfil, o bien si te preguntas cómo se haría una de estas selecciones de programadores python, en este artículo pretendo dar una idea de las preguntas de un proceso de selección de este tipo, más pensado como “filtrado” de todo perfil no deseado que en ser una exhaustiva evaluación de conocimientos. En un próximo artículo pondré algunas soluciones a estas preguntas para que así tengas tiempo para reflexionar.

1. Filtrado del simple “Hello, World!”

Antes de perder más tiempo, lo primero es preguntar al candidato si realmente sabe de python. No son pocos los que acuden con la idea de que da igual que sea python que cualquiera de los otros lenguajes “populares” como java o visual basic. Por mucho que se insista en que tiene que ser un programador experimentado en python, muchas veces te encuentras que ni es python lo que sabe, ni tampoco es experimentado.

La prueba del “Hello, World!” en python debería ser la primera prueba para descartar a los oportunistas. Otra de las preguntas más elementales sería hablar del “Zen de python”, si no recitarlo entero, al menos una explicación de la regla “Explicit is better than implicit”.

2. Preguntar por muestras de sus trabajos

Todo programador que aprecie debe tener una colección de cosas en las que haya trabajado. No tienen que ser cosas asombrosas. Pueden ser simplemente aquellas muestras de saber dejadas por internet como ayuda a los demás, tal vez un perfil de Stack Overflow o de Python Majibu donde se pueda ver qué clase de comunicador y resolvedor de problemas eres. Tal vez algún repositorio de código abierto donde participes, un blog profesional, twitter,…

Simplemente, viendo la clase de trabajos que hace la gente resulta de mucha ayuda para saber en qué destaca o no un programador.

3. Afín a la cultura de la empresa

La experiencia dice que ser afín a la cultura de la empresa asegura mejores éxitos que el ser un monstruo programando. Un ejemplo común son las empresas de software libre cuando tienden a adoptar ciertas prácticas éticas entre sus empleados como es la de compartir todo el conocimiento.

Tratándose de python, un programador en este lenguaje tiende a adoptar un perfil muy característico. Busca como fin la sencillez y la elegancia del código, antes que la optimización y eficiencia del mismo. Intenta ser “agnóstico” frente a la plataforma, que el código funcione en (casi) cualquier sitio. Sin embargo, es ávido de conocer nuevas librerías que poder emplear en sus desarrollos, siempre con python como lenguaje director de la lógica de la aplicación.

Toda esta actividad se ve plasmada en las “comunidades” python, donde se proponen y discuten sobre estos temas. Destaca la comunidad de “python hispano” como un primer lugar donde buscar perfiles apropiados, pero también se puede acudir a otros foros locales como las comunidades de python en Argentina, Perú, México,… o a foros más especializados de django o zope.

Pero una empresa que se precie, que tenga un buen producto de código abierto que ofrecer, debería ser capaz de formar su propia comunidad como punto de encuentro de los afines al proyecto, que no son sólo programadores. Esta comunidad sirve tanto para difundir la filosofía de la empresa como ser el punto de encuentro del personal afín a ella. Lamentablemente, muchas empresas han creído que una comunidad se monta mediante el bombardeo publicitario y es así como han concebido sus cuentas en las redes sociales como facebook o twitter. De este modo han conseguido consumidores fanáticos, pero muy pocos colaboradores.

A un programador hay que darle código. Las comunidades de programadores se forman alrededor de servicios como los github, bitbucket, sourceforge, launchpad, trac, etc. Si encuentras en tu comunidad un programador que te ha hecho un gran aporte o un hacker que ha dado con una extraña y oculta vulnerabilidad, contrátalo sin más dubitaciones.

4. Prueba de programación

Lo siguiente es pedir la realización de algunos programas sencillos: un detector de palíndromos, crear una expresión regular para encontrar teléfonos en un texto, una conversión de una lista de tuplas a diccionario,…

Con la prueba de programación no se busca, necesariamente, una respuesta perfecta, si no el crear un contexto donde observar al programador en su compresión de los problemas, así cómo su intento de resolverlos. Dentro de las posibilidades, es una prueba que muy bien se podría hacer online, combinando videconferencia y pizarra virtual. Si saltaran demasiadas alarmas, al ser online se podría dar por terminada la prueba en cualquier momento y así ahorrar tiempo tanto al candidato como al seleccionador.

5. Prueba real

Antes de dar el paso definitivo, se puede probar al candidato sobre un caso real como resolver algún ticket del gestor de bugs o crear una extensión sencilla en un plazo determinado. Por supuesto, no debe ser ninguna parte importante, tan sólo un medio de comprobar si el candidato es capaz de cumplir los plazos establecidos para así no desequilibrar al resto del equipo.

Idealmente, sería bueno que esta labor se realizara en las mismas instalaciones de la empresa, con los recursos de la empresa y con un contrato en prácticas.

6. Entrevista personal

Habiendo pasando todos los puntos anteriores, es casi seguro que el candidato es apto para la empresa. La entrevista está orientada más en conocer el perfil comunicativo del candidato con vistas a su integración de un equipo de trabajo. La entrevista empieza por una presentación del candidato sobre su experiencia hasta el momento. A continuación, se le realizan una serie de preguntas encaminadas a conocer del candidato su apasionamiento por lo que hace, su capacidad para comunicarse en grupos pequeños y qué podría aportar dentro de un pequeño equipo de trabajo.

7. Prueba de inglés

Se quiera o no, el inglés es el idioma común del programador, y no sólo porque toda la documentación esté en inglés. Cualquier proyecto de envergadura debe usar el inglés si quiere llamar la atención y conseguir nuevos colaboradores, ya sea en internet como en presentaciones en congresos. También empieza a ser frecuente la creación de equipos multinacionales de desarrollo. El inglés es la lingua franca de nuestros tiempos y comunicarse en inglés es fundamental para ser programador. Al ser una prueba de comunicación, la prueba de inglés podría formar parte de la entrevista personal.

8. Conclusión

Si has leído hasta aquí, espero no haberte aburrido y/o decepcionado mucho. No espero que todo lo dicho sea tomado al pie de letra. Más que una lista de reglas para buscar candidatos, he pretendido que sirva más como consejos para programadores python que buscan trabajo, sepan cómo hacerlo y, sobre todo, que se animen a buscarlos. Si de paso se amplía el número de programadores profesionales de python, mejor todavía. Ganaremos todos.

4 responses so far

Older posts »