Chapter 2: El lenguaje Python
El lenguaje 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
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
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
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
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
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
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
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
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
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
>>> 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:
>>> 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
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
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 conlambda
permiten refactorizar funciones existentes en términos de un conjunto distinto de argumentos.cache.ram
ycache.disk
son funciones para manejo de caché de web2py.
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
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
.
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()
ywrite_file()
en el espacio de nombres degluon.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 comandoos.path.join
, del hablamos más adelante.
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
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 ejemplothread
), 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
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
, comochdir
, 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
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
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
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
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'))