Chapter 2: El lenguaje Python

El lenguaje Python

Python

Acerca de Python

Python es un lenguaje de programación multipropósito de alto nivel Su filosofía de diseño enfatiza la productividad del programador y la legibilidad del código. Tiene un núcleo sintáctico minimalista con unos pocos comandos básicos y simple semántica, pero además tiene una enorme y variada librería estándar, que incluye una Interfaz de Programación de Aplicaciones (API) API para muchas de las funciones en el nivel del sistema operativo (OS). El código Python, aunque minimalista, define objetos incorporados como listas enlazadas (list), tuplas (tuple), tablas hash (dict), y enteros de longitud arbitraria (long).

Python soporta múltiples paradigmas de programación, incluyendo programación orientada a objetos (class), programación imperativa (def) y funcional (lambda). Python tiene un sistema de tipado dinámico y manejo automatizado de memoria utilizando conteo de referencias (similar a Perl, Ruby y Scheme).

Python fue publicado por primera vez por Guido Van Rossum en 1991. El lenguaje tiene un modelo abierto de desarrollo basado en la comunidad administrado por la organización sin fines de lucro Python Software Foundation. Existen varios intérpretes y compiladores que implementan el lenguaje Python, incluyendo uno en Java (Jython) pero, en esta corta revisión, vamos a centrarnos en la implementación en C creada por Guido.

Puedes encontrar varios tutoriales, la documentación oficial y la referencia de las librerías del lenguaje en el sitio web oficial de Python. [python]

Para referencia adicional sobre Python, podemos recomendar los libros en ref. [guido] y ref.[lutz].

Puedes saltarte este capítulo si ya tienes experiencia con el lenguaje Python.

Comenzando

shell

Las distribuciones binarias de web2py para Microsoft Windows o Apple OS X vienen empaquetadas con el intérprete de Python incorporado en el mismo archivo de la distribución.

Puedes iniciarlo en Windows con el siguiente comando (escribe en prompt/consola del DOS):

web2py.exe -S welcome

Sobre Apple OS X, ingresa el siguiente comando en una ventana de terminal (suponiendo que estás en la misma carpeta que web2py.app):

./web2py.app/Contents/MacOS/web2py -S welcome

En una máquina con Linux u otro Unix, probablemente ya tengas instalado Python. Si es así, en el prompt de la shell escribe:

python web2py.py -S welcome

Si no tienes Python 2.5 (o las posteriores 2.x) pre-instalado, tendrás que descargarlo e instalarlo antes de correr web2py.

La opción -S welcome de línea de comandos ordena a web2py que ejecute la shell interactiva como si los comandos se ejecutaran en un controlador para la aplicación welcome, la aplicación de andamiaje de web2py. Esto pone a tu disposición casi todas las clases, objetos y funciones de web2py. Esta es la única diferencia entre la línea de comando interactiva de web2py y la línea de comando normal de Python.

La interfaz administrativa además provee de una shell basada en web para cada aplicación. Puedes acceder a la de la aplicación "welcome" en:

http://127.0.0.1:8000/admin/shell/index/welcome

Puedes seguir todos los ejemplos en este capítulo utilizando una shell normal o la shell para web.

help, dir

help
dir

El lenguaje Python provee de dos comandos para obtener documentación sobre objetos definidos en el scope actual, tanto los incorporados como los definidos por el usuario.

Podemos pedir ayuda (help) acerca de un objeto, por ejemplo "1":

>>> help(1)
Help on int object:

class int(object)
 |  int(x[, base]) -> integer
 |
 |  Convert a string or number to an integer, if possible.  A floating point
 |  argument will be truncated towards zero (this does not include a string
 |  representation of a floating point number!)  When converting a string, use
 |  the optional base.  It is an error to supply a base when converting a
 |  non-string. If the argument is outside the integer range a long object
 |  will be returned instead.
 |
 |  Methods defined here:
 |
 |  __abs__(...)
 |      x.__abs__() <==> abs(x)
...

y, como "1" es un entero, obtenemos una descripción de la clase int y de todos sus métodos. Aquí la salida fue truncada porque es realmente larga y detallada.

En forma similar, podemos obtener una lista de métodos del objeto "1" con el comando dir:

>>> dir(1)
['__abs__', ..., '__xor__']

Tipos

type

Python es un lenguaje de tipado dinámico, o sea que las variables no tienen un tipo y por lo tanto no deben ser declaradas. Los valores, sin embargo, tienen tipo. Puedes consultar a una variable el tipo de valor que contiene:

>>> a = 3
>>> print type(a)
<type 'int'>
>>> a = 3.14
>>> print type(a)
<type 'float'>
>>> a = 'hola Python'
>>> print type(a)
<type 'str'>

Python además incluye, como características nativas, estructuras de datos como listas y diccionarios.

str

str
ASCII
UTF8
Unicode
encode

Python soporta el uso de dos diversos tipo de cadenas: ASCII y Unicode. Las cadenas ASCII se delimitan por '...', "..." o por '...' o """...""". Las comillas triples delimitan cadenas multilínea. Las cadenas Unicode comienzan con un u seguido por la cadena conteniendo caracteres Unicode. Una cadena Unicode puede convertirse en una cadena ASCII seleccionando un una codificación por ejemplo:

>>> a = 'esta es una cadena ASCII'
>>> b = u'esta es una cadena Unicode'
>>> a = b.encode('utf8')

Al ejecutar estos tres comandos, la a resultante es una cadena ASCII que almacena caracteres codificados con UTF8. Por diseño, web2py utiliza cadenas codificadas con UTF8 internamente.

Además es posible utilizar variables en cadenas de distintas formas:

>>> print 'el número es ' + str(3)
el número es 3
>>> print 'el número es %s' % (3)
el número es 3
>>> print 'el número es %(numero)s' % dict(numero=3)
el número es 3

La última notación es más explícita y menos propensa a errores, y es la recomendada.

Muchos objetos de Pyhton, por ejemplo números, pueden ser serializados en cadenas utilizando str o repr. Estos dos comandos son realmente similares pero producen una salida ligeramente diferente. Por ejemplo:

>>> for i in [3, 'hola']:
        print str(i), repr(i)
3 3
hola 'hola'

Para las clases definidas por el usuario, str y repr pueden definirse/redefinirse utilizando los operadores especiales __str__ y __repr__. Estos se describirán básicamente más adelante; para mayor información, consulta la documentación oficial de Python [pydocs]. repr siempre tiene un valor por defecto.

Otra característica importante de una cadena de Python es que, como una lista, es un objeto iterable

>>> for i in 'hola':
        print i
h
o
l
a

list

list

Los métodos principales de una lista de Python son append, insert, y delete:

>>> a = [1, 2, 3]
>>> print type(a)
<type 'list'>
>>> a.append(8)
>>> a.insert(2, 7)
>>> del a[0]
>>> print a
[2, 7, 3, 8]
>>> print len(a)
4

Las listas se pueden cortar (slice):

>>> print a[:3]
[2, 7, 3]
>>> print a[1:]
[7, 3, 8]
>>> print a[-2:]
[3, 8]

y concatenar:

>>> a = [2, 3]
>>> b = [5, 6]
>>> print a + b
[2, 3, 5, 6]

Una lista es iterable; puedes recorrerla en un bucle:

>>> a = [1, 2, 3]
>>> for i in a:
        print i
1
2
3

Los elementos de una lista no tienen que ser del mismo tipo; pueden ser de cualquier tipo de objeto de Python.

Hay una situación muy común en la que se puede usar una lista por comprensión o list comprehension. Consideremos el siguiente código:

>>> a = [1,2,3,4,5]
>>> b = []
>>> for x in a:
        if x % 2 == 0:
            b.append(x * 3)
>>> b
[6, 12]

Este código claramente procesa una lista de ítems, separa y modifica un subconjunto de la lista ingresada y crea una nueva lista resultante, y este código puede ser enteramente reemplazado por la siguiente lista por comprensión:

>>> a = [1,2,3,4,5]
>>> b = [x * 3 for x in a if x % 2 == 0]
>>> b
[6, 12]

tuple

tuple

Una tupla es como una lista, pero su tamaño y elementos son inmutables, mientras que en una lista son mutables. Si un elemento de una tupla es un objeto, los atributos del objeto son mutables. Una tupla está delimitada por paréntesis.

>>> a = (1, 2, 3)

Entonces si esto funciona para una lista:

>>> a = [1, 2, 3]
>>> a[1] = 5
>>> print a
[1, 5, 3]

la asignación a un elemento no funciona para una tupla:

>>> a = (1, 2, 3)
>>> print a[1]
2
>>> a[1] = 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

Una tupla, como en la lista, es un objeto iterable. Nótese que una tupla que consista de un elemento debe incluir una coma al final, como se muestra abajo:

>>> a = (1)
>>> print type(a)
<type 'int'>
>>> a = (1,)
>>> print type(a)
<type 'tuple'>

Las tuplas son realmente útiles para ordenar objetos en grupos eficientemente por su inmutabilidad, y los paréntesis son a veces opcionales:

>>> a = 2, 3, 'hola'
>>> x, y, z = a
>>> print x
2
>>> print z
hola

dict

dict

Un dict (diccionario) de Python es una tabla hash que asocia (map) un objeto-clave a un objeto-valor. Por ejemplo:

>>> a = {'k':'v', 'k2':3}
>>> a['k']
v
>>> a['k2']
3
>>> a.has_key('k')
True
>>> a.has_key('v')
False

Las claves pueden ser de cualquier tipo apto para tabla hash (int, string, o cualquier objeto cuya clase implemente el método __hash__). Los valores pueden ser de cualquier tipo. Las claves y valores diferentes en el mismo diccionario no tienen que ser de un único tipo. Si las claves son caracteres alfanuméricos, el diccionario también se puede declarar con una sintaxis alternativa:

>>> a = dict(k='v', h2=3)
>>> a['k']
v
>>> print a
{'k':'v', 'h2':3}

has_key, keys, values y items son métodos útiles:

>>> a = dict(k='v', k2=3)
>>> print a.keys()
['k', 'k2']
>>> print a.values()
['v', 3]
>>> print a.items()
[('k', 'v'), ('k2', 3)]

El método items produce una lista de tuplas, cada una conteniendo una clave y su valor asociado.

>>> a = [1, 2, 3]
>>> del a[1]
>>> print a
[1, 3]
>>> a = dict(k='v', h2=3)
>>> del a['h2']
>>> print a
{'k':'v'}

Internamente, Python utiliza el operador hash para convertir objetos en enteros, y usa ese entero para determinar dónde almacenar el valor.

>>> hash("hola mundo")
-1500746465

Acerca del espaciado

Python usa espaciado/sangría para delimitar bloques de código. Un bloque de código comienza con una línea que finaliza con dos puntos, y continúa para todas las líneas que tengan igual o mayor espaciado que la próxima línea. Por ejemplo:

>>> i = 0
>>> while i < 3:
>>>    print i
>>>    i = i + 1
>>>
0
1
2

Es común el uso de cuatro espacios para cada nivel de espaciado o indentation. Es una buena práctica no combinar la tabulación con el espacio, porque puede resultar (invisiblemente) confuso.

for...in

for

En Python, puedes recorrer objetos iterables en un bucle

>>> a = [0, 1, 'hola', 'python']
>>> for i in a:
        print i
0
1
hola
python

Un atajo usual es xrange, que genera un rango iterable sin almacenar la lista entera de elementos.

>>> for i in xrange(0, 4):
        print i
0
1
2
3

Esto es equivalente a la sintaxis de C/C++/C#/Java:

for(int i=0; i<4; i=i+1) { print(i); }

Otro comando de utilidad es enumerate, que realiza un conteo mientras avanza el bucle:

>>> a = [0, 1, 'hola', 'python']
>>> for i, j in enumerate(a):
        print i, j
0 0
1 1
2 hola
3 python

También hay un keyword range(a, b, c) que devuelve una lista de enteros comenzando con el valor a y con un incremento de c, y que finaliza con el último valor menor a b. Por defecto, a es 0 y c es 1. xrange es similar es similar pero en realidad no genera una lista, sólo un iterator para la lista; que es más apropiado para crear estos bucles.

Se puede salir de un bucle utilizando break

>>> for i in [1, 2, 3]:
         print i
         break
1

Puedes saltar a la próxima iteración del bucle sin ejecutar todo el bloque de código con continue

>>> for i in [1, 2, 3]:
         print i
         continue
         print 'test'
1
2
3

while

while

El bucle while en Python opera básicamente como lo hace en otros lenguajes de programación, iterando una cantidad indefinida de veces y comprobando una condición antes de cada iteración. Si la condición es False, el bucle finaliza.

>>> i = 0
>>> while i < 10:
        i = i + 1
>>> print i
10

No hay una instrucción especial loop...until en Python.

if...elif...else

if
elif
else
El uso de condicional en Python es intuitivo:

>>> for i in range(3):
>>>     if i == 0:
>>>         print 'cero'
>>>     elif i == 1:
>>>         print 'uno'
>>>     else:
>>>         print 'otro'
cero
uno
otro

"elif" significa "else if". Tanto elif como else son partes opcionales. Puede haber más de una elif pero sólo una declaración else. Se pueden crear condicionales complicados utilizando los operadores not, and y or.

>>> for i in range(3):
>>>     if i == 0 or (i == 1 and i + 1 == 2):
>>>         print '0 or 1'

try...except...else...finally

try
except
finally
Exception
Python puede lanzar (throw) - perdón, generar - excepciones (Exception):

>>> try:
>>>     a = 1 / 0
>>> except Exception, e:
>>>     print 'epa: %s' % e
>>> else:
>>>     print 'sin problemas aquí'
>>> finally:
>>>     print 'listo'
epa: integer division or modulo by zero
listo

Si la excepción se genera (raise), es atrapada por la cláusula except, que es ejecutada; no se ejecuta en cambio la cláusula else. Si no se genera ninguna excepción, la cláusula de except no se ejecuta, pero en cambio la de else sí. La cláusula de finally se ejecuta siempre. Puede haber múltiples cláusulas except para distintas excepciones posibles:

>>> try:
>>>     raise SyntaxError
>>> except ValueError:
>>>     print 'error en el valor'
>>> except SyntaxError:
>>>     print 'error sintáctico'
error sintáctico

Las cláusulas else y finally son opcionales.

Aquí mostramos una lista

BaseException
 +-- HTTP (defined by web2py)
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- Exception
      +-- GeneratorExit
      +-- StopIteration
      +-- StandardError
      |    +-- ArithmeticError
      |    |    +-- FloatingPointError
      |    |    +-- OverflowError
      |    |    +-- ZeroDivisionError
      |    +-- AssertionError
      |    +-- AttributeError
      |    +-- EnvironmentError
      |    |    +-- IOError
      |    |    +-- OSError
      |    |         +-- WindowsError (Windows)
      |    |         +-- VMSError (VMS)
      |    +-- EOFError
      |    +-- ImportError
      |    +-- LookupError
      |    |    +-- IndexError
      |    |    +-- KeyError
      |    +-- MemoryError
      |    +-- NameError
      |    |    +-- UnboundLocalError
      |    +-- ReferenceError
      |    +-- RuntimeError
      |    |    +-- NotImplementedError
      |    +-- SyntaxError
      |    |    +-- IndentationError
      |    |         +-- TabError
      |    +-- SystemError
      |    +-- TypeError
      |    +-- ValueError
      |    |    +-- UnicodeError
      |    |         +-- UnicodeDecodeError
      |    |         +-- UnicodeEncodeError
      |    |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
    	   +-- ImportWarning
    	   +-- UnicodeWarning

Para una descripción detallada de cada una, consulta la documentación oficial de Python.

web2py expone sólo una nueva excepción, llamada HTTP. Cuando es generada, hace que el programa devuelva una página de error HTTP (para más sobre este tema consulta el Capítulo 4).

Cualquier objeto puede ser utilizado para generar una excepción, pero es buena práctica generar excepciones con objetos que extienden una de las clases de excepción incorporadas.

def...return

def
return

Las funciones se declaran utilizando def. Aquí se muestra una función de Python típica:

>>> def f(a, b):
        return a + b
>>> print f(4, 2)
6

No hay necesidad (o forma) de especificar los tipos de los argumentos ni el tipo o tipos devueltos. En este ejemplo, se define una función f para que tome dos argumentos.

Las funciones son la primer característica sintáctica descripta en este capítulo para introducir el concepto de "scope" (alcance/ámbito), o "namespace" (espacio de nombres). En el ejemplo de arriba, los identificadores (identifier) a y b son indefinidos fuera del scope de la función f :

>>> def f(a):
        return a + 1
>>> print f(1)
2
>>> print a
Traceback (most recent call last):
  File "<pyshell#22>", line 1, in <module>
    print a
NameError: name 'a' is not defined

Los identificadores definidos por fuera del scope de una función son accesibles dentro de la función; nótese cómo el identificador a es manejado en el siguiente código:

>>> a = 1
>>> def f(b):
        return a + b
>>> print f(1)
2
>>> a = 2
>>> print f(1) # se usa un nuevo valor para a
3
>>> a = 1 # redefine a
>>> def g(b):
        a = 2 # crea un nuevo a local
        return a + b
>>> print g(2)
4
>>> print a # el a global no ha cambiado
1

Si se modifica a, las siguientes llamadas a la función usarán el nuevo valor del a global porque la definición de la función enlaza la ubicación de almacenamiento del identificador a, no el valor de a mismo al momento de la declaración de la función; sin embargo, si se asigna a a dentro de la función g, la a global no es afectada porque la nueva a local protege el valor global. La referencia del scope externo puede ser utilizada en la creación de "cierres" (closures):

>>> def f(x):
            def g(y):
                return x * y
            return g
>>> duplicador = f(2) # duplicador es una nueva función
>>> triplicador = f(3) # triplicador es una nueva función
>>> cuadruplicador = f(4) # cuadruplicador es una nueva función
>>> print duplicador(5)
10
>>> print triplicador(5)
15
>>> print cuadruplicador(5)
20

La función f crea nuevas funciones; y nótese que el scope del nombre g es enteramente interno a f. Los cierres son extremadamente poderosos.

Los argumentos de función pueden tener valores por defecto, y pueden devolver múltiples resultados:

>>> def f(a, b=2):
        return a + b, a - b
>>> x, y = f(5)
>>> print x
7
>>> print y
3

Los argumentos de las funciones pueden pasarse explícitamente por nombre, y esto significa que el orden de los argumentos especificados en la llamada puede ser diferente del orden de los argumentos con los que la función fue definida:

>>> def f(a, b=2):
        return a + b, a - b
>>> x, y = f(b=5, a=2)
>>> print x
7
>>> print y
-3

Las funciones también pueden aceptar un número variable de argumentos en tiempo de ejecución:

>>> def f(*a, **b):
        return a, b
>>> x, y = f(3, 'hola', c=4, test='mundo')
>>> print x
(3, 'hola')
>>> print y
{'c':4, 'test':'mundo'}

Aquí los argumentos no pasados por nombre (3, 'hola') se almacenan en la tupla a, y los argumentos pasados por nombre (c y test) se almacenan en el diccionario b.

En el caso opuesto, puede pasarse una lista o tupla a una función que requiera una conjunto ordenado de argumentos para que los "abra" (unpack):

>>> def f(a, b):
        return a + b
>>> c = (1, 2)
>>> print f(*c)
3

y un diccionario se puede "abrir" para pasar argumentos por nombre:

>>> def f(a, b):
        return a + b
>>> c = {'a':1, 'b':2}
>>> print f(**c)
3

lambda

lambda

lambda presenta una forma de declarar en forma fácil y abreviada funciones sin nombre:

>>> a = lambda b: b + 2
>>> print a(3)
5

La expresión "lambda [a]:[b]" se lee exactamente como "una función con argumentos [a] que devuelve [b]". La expresión lambda es anónima, pero la función adquiere un nombre al ser asignada a un identificador a. Las reglas de espacios de nombres para def también son igualmente válidas para lambda, y de hecho el código de arriba, con respecto a a es idéntico al de la declaración de la función utilizando def:

>>> def a(b):
        return b + 2
>>> print a(3)
5

La única ventaja de lambda es la brevedad; sin embargo, la brevedad puede ser muy conveniente en ciertas ocasiones. Consideremos una función llamada map que aplica una función a todos los ítems en una lista, creando una lista nueva:

>>> a = [1, 7, 2, 5, 4, 8]
>>> map(lambda x: x + 2, a)
[3, 9, 4, 7, 6, 10]

Este código se hubiese duplicado en tamaño si utilizábamos def en lugar de lambda. El problema principal de lambda es que (en la implementación de Python) la sintaxis permite una sólo una expresión simple; aunque, para funciones más largas, puede utilizarse def y el costo extra de proveer un nombre para la función disminuye cuando el tamaño de la función aumenta. Igual que con def, lambda puede utilizarse para "condimentar" una función: las nuevas funciones se pueden crear envolviendo funciones existentes de manera que la nueva función tome un conjunto distinto de argumentos:

>>> def f(a, b): return a + b
>>> g = lambda a: f(a, 3)
>>> g(2)
5

Hay muchas situaciones donde es útil condimentar, pero una de ellas es especialmente a propósito en web2py: el manejo del caché. Supongamos que tenemos una función pesada que comprueba si un argumento es número primo:

def esprimo(numero):
    for p in range(2, numero):
        if (numero % p) == 0:
            return False
    return True

Esta función obviamente consume mucho tiempo de proceso.

Supongamos que tenemos una función de caché cache.ram que toma tres argumentos: una clave, una función y una cantidad de segundos.

valor = cache.ram('clave', f, 60)

La primera vez que se llame, llamará a su vez a la función f(), almacenará la salida en un diccionario en memoria (digamos "d"), y lo devolverá de manera que el valor es:

valor = d['clave']=f()

La segunda vez que se llame, si la clave está en el diccionario y no es más antigua del número de segundos especificados (60), devolverá el valor correspondiente sin volver a ejecutar la llamada a la función.

valor = d['clave']

¿Cómo podemos almacenar en el caché la salida de la función esprimo para cualquier valor de entrada? De esta forma:

>>> numero = 7
>>> segundos = 60
>>> print cache.ram(str(numero), lambda: esprimo(numero), segundos)
True
>>> print cache.ram(str(numero), lambda: esprimo(numero), segundos)
True

La salida es siempre la misma, pero la primera vez que se llama a cache.ram, se llama a esprimo; la segunda vez, no.

Las funciones de Python, creadas tanto con def como con lambda permiten refactorizar funciones existentes en términos de un conjunto distinto de argumentos. cache.ram y cache.disk son funciones para manejo de caché de web2py.

class

class

Como Python es un lenguaje de tipado dinámico, las clases y objetos pueden resultar extrañas. De hecho no necesitas definir las variables incluidas (atributos) al declarar una clase, y distintas instancias de la misma clase pueden tener distintos atributos. Los atributos (attribute) se asocian generalmente con la instancia, no la clase (excepto cuando se declaran como atributos de clase o "class attributes", que vienen a ser las "static member variables" de C++/Java).

Aquí se muestra un ejemplo:

>>> class MiClase(object): pass
>>> miinstancia = MiClase()
>>> miinstancia.mivariable = 3
>>> print miinstancia.mivariable
3

Nótese que pass es un comando que no "hace" nada. En este caso se utiliza para definir una clase MiClase que no contiene nada. MiClase() llama al constructor de la clase (en este caso el constructor por defecto) y devuelve un objeto, una instancia de la clase. El (object) en la definición de la clase indica que nuestra clase extiende la clase incorporada object. Esto no es obligatorio, pero se considera una buena práctica.

He aquí una clase más complicada:

>>> class MiClase(object):
>>>    z = 2
>>>    def __init__(self, a, b):
>>>        self.x = a
>>>        self.y = b
>>>    def sumar(self):
>>>        return self.x + self.y + self.z
>>> miinstancia = MiClase(3, 4)
>>> print miinstancia.sumar()
9

Las funciones declaradas adentro de la clase son métodos. Algunos métodos tienen nombres especiales reservados. Por ejemplo, __init__ es el constructor. Todas las variables son variables locales del método exceptuando las variables declaradas fuera de los métodos. Por ejemplo, z es una "variable de clase", que equivale a una "static member variable" de C++ que almacena el mismo valor para toda instancia de la clase.

Hay que tener en cuenta que __init__ toma 3 argumentos y add toma uno, y sin embargo los llamamos con dos y ningún argumento respectivamente. El primer argumento representa, por convención, el nombre local utilizado dentro del método para referirse a el objeto actual, pero podríamos haber utilizado cualquier otro. self cumple el mismo rol que *this en C++ o this en Java, pero self no es una palabra reservada.

Esta sintaxis es necesaria para evitar ambigüedad cuando se declaran clases anidadas, como una clase que es local a un método dentro de otra clase.

Atributos especiales, métodos y operadores

Los atributos de clase, métodos y operadores que comienzan con doble guión bajo (__) están usualmente como privados (por ejemplo para usarse internamente pero no expuestos fuera de la clase) aunque esta convención no está controlada en el intérprete.

Algunos de ellos son palabras reservadas y tienen un significado especial.

Aquí se muestran, como ejemplo, tres de ellos:

  • __len__
  • __getitem__
  • __setitem__

Se pueden usar, por ejemplo, para crear un objeto contenedor que se comporta como una lista:

>>> class MiLista(object):
>>>     def __init__(self, *a): self.a = list(a)
>>>     def __len__(self): return len(self.a)
>>>     def __getitem__(self, i): return self.a[i]
>>>     def __setitem__(self, i, j): self.a[i] = j
>>> b = MiLista(3, 4, 5)
>>> print b[1]
4
>>> b.a[1] = 7
>>> print b.a
[3, 7, 5]

Entre otros operadores especiales están __getattr__ y __setattr__, que definen los atributos get y set para la clase, y __sum__ y __sub__, que hacen sobrecarga de operadores aritméticos. Para el uso de estos operadores se pueden consultar textos más avanzados en este tema. Ya mencionamos anteriormente los operadores especiales __str__ y __repr__.

Entrada/salida de archivos

file.read
file.write

En Python puedes abrir y escribir en un archivo con:

>>> archivo = open('miarchivo.txt', 'w')
>>> archivo.write('hola mundo')
>>> archivo.close()

En forma similar, puedes leer lo escrito en el archivo con:

>>> archivo = open('miarchivo.txt', 'r')
>>> print archivo.read()
hola mundo

Como alternativa, puedes leer en modo binario con "rb", escribir en modo binario con "wb", y abrir el archivo en modo incremental (append) con "a", utilizando la notación estándar de C.

El comando de lectura read toma un argumento opcional, que es el número de byte. Puedes además saltar a cualquier ubicación en el archivo usando seek.

file.seek

Puedes recuperar lo escrito en el archivo con read

>>> print archivo.seek(5)
>>> print archivo.read()
mundo

y puedes cerrar el archivo con:

>>> archivo.close()

En la distribución estándar de Python, conocida como CPython, las variables son calculadas por referencia ("reference-counting"), incluyendo aquellas que manejan archivos, así que CPython sabe que cuando le conteo por referencia de un archivo abierto disminuye hasta cero, el archivo debería cerrarse y la variable descartada. Sin embargo, en otras implementaciones de Python como PyPy, se usa la recolección de basura en lugar del conteo de referencias, y eso implica que es posible que se acumulen demasiados manejadores de archivos abiertos al mismo tiempo, resultando en un error antes que "gc" tenga la opción cerrarlos y descartarlos a todos. Por eso es mejor cerrar explícitamente los manejadores de archivos cuando ya no se necesitan. web2py provee de dos funciones ayudantes, read_file() y write_file() en el espacio de nombres de gluon.fileutils que encapsulan el acceso a los archivos y aseguran que los manejadores de archivos en uso se cierren oportunamente.

Cuando utilizas web2py, no sabes dónde se ubica el directorio actual, porque eso depende de la forma en que se halla configurado el marco de desarrollo. La variable request.folder contiene la ruta (path) a la aplicación actual. Las rutas pueden concatenarse con el comando os.path.join, del hablamos más adelante.

exec, eval

exec
eval

A diferencia de Java, Python es realmente un lenguaje interpretado. Esto significa que tiene la habilidad de ejecutar comandos de Python almacenados en cadenas. Por ejemplo:

>>> a = "print 'hola mundo'"
>>> exec(a)
'hola mundo'

¿Qué ha ocurrido? La función exec le dice al intérprete que se llame a sí mismo y ejecute le contenido de la cadena pasada como argumento. También es posible ejecutar el contenido de una cadena en el contexto definido por los símbolos de un diccionario:

>>> a = "print b"
>>> c = dict(b=3)
>>> exec(a, {}, c)
3

Aquí el intérprete, cuando ejecuta la cadena a, ve los símbolos definidos en c (b en el ejemplo), pero no ve a c o a. Esto no es equivalente a un entorno restringido, porque exec no impone límites a lo que el código interior pueda hacer; sólo define el conjunto de variables visibles en el código.

Una función relacionada es eval, que hace algo muy parecido a exec, pero espera que el argumento pasado evalúe a un valor dado, y devuelve ese valor.

>>> a = "3*4"
>>> b = eval(a)
>>> print b
12

import

import
random
El verdadero poder de Python está en sus librerías de módulos. Ellos proveen un gran y consistente conjunto de Interfaces de Programación de Aplicaciones (API) para muchas librerías del sistema (a menudo en un modo independiente del sistema operativo).

Por ejemplo, si necesitamos utilizar un número aleatorio, podemos hacer:

>>> import random
>>> print random.randint(0, 9)
5

Esto imprime un entero aleatorio entre 0 y 9 (incluyendo al 9), 5 en el ejemplo. La función randint está definida en el módulo random. Además es posible importar un objeto de un módulo en el espacio de nombres actual:

>>> from random import randint
>>> print randint(0, 9)

o importar todo objeto de un módulo en el espacio de nombres actual:

>>> from random import *
>>> print randint(0, 9)

o importar todo en un nuevo espacio de nombres específico:

>>> import random as myrand
>>> print myrand.randint(0, 9)

En adelante, vamos a usar objetos principalmente definidos en módulos como os, sys, datetime, time y cPickle.

Todo objeto de Python es accesible a través de un módulo llamado gluon y ese es el tema de capítulos posteriores. Internamente, web2py usa muchos módulos de Python (por ejemplo thread), pero sólo en raras ocasiones necesitarás acceder a ellos en forma directa.

En las secciones siguientes describiremos aquellos módulos que son de mayor utilidad.

os

os
os.path.join
os.unlink

Este módulo provee de una interfaz a la API del sistema operativo. Por ejemplo:

>>> import os
>>> os.chdir('..')
>>> os.unlink('archivo_a_borrar')

Algunas de las funciones de os, como chdir, NO DEBEN usarse en web2py, porque no son aptas para el proceso por hilos ("thread-safe").

os.path.join es de gran utilidad; permite concatenar rutas a directorios y archivos en una forma independiente del sistema operativo:

>>> import os
>>> a = os.path.join('ruta', 'sub_ruta')
>>> print a
ruta/sub_ruta

Las variables de entorno del sistema se pueden examinar con:

>>> print os.environ

que es un diccionario de sólo lectura.

sys

sys
sys.path

El módulo sys contiene muchas variables y funciones, pero la que más usamos es sys.path. Contiene una lista de rutas (path) donde Python busca los módulos. Cuando intentamos importar un módulo, Python lo busca en todos los directorios listados en sys.path. Si instalas módulos adicionales en alguna ubicación y quieres que Python los encuentre, necesitas añadir (append) en sys.path la ruta o path a esa ubicación.

>>> import sys
>>> sys.path.append('ruta/a/mis/módulos')

Cuando corremos web2py, Python permanece residente en memoria, y se configura un único sys.path, aunque puede haber varios hilos respondiendo a las solicitudes HTTP. Para evitar la pérdida de espacio en memoria (leak), es mejor comprobar que una ruta ya está presente antes de añadirla:

>>> ruta = 'ruta/a/mis/módulos'
>>> if not ruta in sys.path:
        sys.path.append(ruta)

datetime

date
datetime
time

El uso del módulo datetime es más fácil de describir por algunos ejemplos:

>>> import datetime
>>> print datetime.datetime.today()
2008-07-04 14:03:90
>>> print datetime.date.today()
2008-07-04

De vez en cuando puedes necesitar una referencia cronológica para los datos (timestamp) según el tiempo de UTC en lugar de usar la hora local. En ese caso puedes usar la siguiente función:

>>> import datetime
>>> print datetime.datetime.utcnow()
2008-07-04 14:03:90

El módulo datetime contiene varias clases: date (fecha), datetime (fecha y hora), time (hora) y timedelta. La diferencia entre dos objetos date o dos datetime o dos time es un timedelta:

>>> a = datetime.datetime(2008, 1, 1, 20, 30)
>>> b = datetime.datetime(2008, 1, 2, 20, 30)
>>> c = b - a
>>> print c.days
1

En web2py, date y datetime se usan para almacenar los tipos de datos SQL correspondientes cuando se pasan desde o a la base de datos.

time

time

El módulo time difiere de date o datetime porque representa el tiempo en segundos desde el epoch (comenzando desde 1970)

>>> import time
>>> t = time.time()
1215138737.571

Consulta la documentación de Python para otras funciones de conversión entre tiempo en segundos y tiempo como datetime.

cPickle

cPickle

Este es un módulo realmente poderoso. Provee de funciones que pueden serializar casi cualquier objeto de Python, incluyendo objetos autoreferidos (self-referential). Por ejemplo, vamos a crear un objeto inusual:

>>> class MiClase(object): pass
>>> miinstancia = MiClase()
>>> miinstancia.x = 'algo'
>>> a = [1 ,2, {'hola':'mundo'}, [3, 4, [miinstancia]]]

y ahora:

>>> import cPickle
>>> b = cPickle.dumps(a)
>>> c = cPickle.loads(b)

En el ejemplo, b is una cadena que representa a, y c es una copia de a generada por la deserialización de b.

cPickle puede además serializar y deserializar desde un archivo:

>>> cPickle.dump(a, open('myfile.pickle', 'wb'))
>>> c = cPickle.load(open('myfile.pickle', 'rb'))
 top