157 cosas de IPython que no sabías y nunca preguntaste (II)

En la primera entrega vimos como usar la ayuda de IPython y como personalizar nuestras propias funciones mágicas de ayuda.

En esta segunda entrega vamos a hablar del uso de la historia dentro de IPython.

[Inciso: Os recomiendo que veáis lo que viene a continuación en el notebook o en nbviewer. WordPress.com cada vez me parece más limitado para mostrar el contenido que mostramos en este blog]

IPython guarda la historia de los comandos que se usan en cada línea/celda de cada sesión bajo un determinado perfil. Esta información se guarda en una base de datos sqlite. Por defecto, se guardan el inicio y fin de sesión, los comandos usados en cada sesión y algunos metadatos más. IPython también se puede configurar para que almacene los outputs.

Historia

La base de datos con la historia se guarda en la carpeta que obtenéis haciendo lo siguiente:

from IPython.utils import path
path.locate_profile()

'/home/kiko/.config/ipython/profile_default'

Y la base de datos se guardará en esa carpeta con el nombre history.sqlite.

Una forma alternativa de obtener la ruta a la historia bajo el perfil actual es usando lo siguiente:

get_ipython().history_manager.hist_file

'/home/kiko/.config/ipython/profile_default/history.sqlite'

Acceso a determinadas celdas de la historia

Para reusar comandos ya usados podemos hacer uso de la historia que guarda IPython en cada sesión.

  • Podemos usar las teclas de cursor hacia arriba o hacia abajo para recorrer los últimos comandos usados (esto no funciona en el notebook pero sí en la consola de IPython).
  • Si escribimos algo en la línea de comandos y pulsamos el cursor hacia arriba nos mostrará solo lo que comience por lo ya escrito en la línea de comandos (nuevamente, esto no funciona en el notebook pero sí en la consola de IPython).
  • En las sesiones interactivas, el input y el output se guarda en las variables In y Out. Poniendo el índice de la línea usada nos volverá a ejecutar esa línea, en el caso de que usemos la variable In o nos mostrará el output en caso de que usemos la variable Out. In es una lista mientras que Out es un diccionario. En el caso de que no haya output para un número de línea nos dará un KeyError. Por ejemplo, veamos las siguientes celdas de código:
In?

Nos mostrará en pantalla lo siguiente (esto puede variar en función de lo que hayáis escrito previamente en la consola o el notebook, lo siguiente solo sirve como ejemplo):

Type:       list
String Form:['', "get_ipython().magic('pinfo In')", "get_ipython().magic('pinfo In')"]
 Length:     3
 Docstring:
list() -> new empty list
list(iterable) -> new list initialized from iterable's items

Mientras que si hacemos lo mismo para Out obtendremos la siguiente info (esto puede variar en función de lo que hayáis escrito previamente en la consola o el notebook, lo siguiente solo sirve como ejemplo):

Type:       dict
String Form:{}
Length:     0
Docstring:
dict() -> new empty dictionary
dict(mapping) -> new dictionary initialized from a mapping object's
    (key, value) pairs
dict(iterable) -> new dictionary initialized as if via:
d = {}
for k, v in iterable:
    d[k] = v
dict(**kwargs) -> new dictionary initialized with the name=value pairs
    in the keyword argument list.  For example:  dict(one=1, two=2)

Si ahora hacemos lo siguiente:

a = 2
print(a)

Nos mostrará un 2 en pantalla pero no lo guarda en el Output puesto que solo es visualización. Por tanto, lo siguiente:

Out

Nos mostrará (esto puede variar en función de lo que hayáis escrito previamente en la consola o el notebook, lo siguiente solo sirve como ejemplo):

{1: '/home/kiko/.config/ipython/profile_default',
 2: '/home/kiko/.config/ipython/profile_default'}

Vemos que en Out se encuentra lo que hemos obtenido en las primeras celdas de código. Si ahora queremos ver otro output podemos hacer lo siguiente:

a

Y, como no lo hemos mostrado con un print, se añade al Output.

Out
{1: '/home/kiko/.config/ipython/profile_default',
 2: '/home/kiko/.config/ipython/profile_default/history.sqlite',
 7: 2}

Vemos que ya tenemos algún valor para el Out y ya podremos acceder a ese valor por si lo quisiéramos usar en alguna otra celda. Por ejemplo, de la siguiente forma:

b = Out[7]
print(b)

Nos mostraría 2 en pantalla.

Como el In siempre lo tendremos, en lugar de ser un diccionario es una lista y podemos acceder al valor de la celda usando el índice de la misma. Por ejemplo:

for celda in In:
    print(celda)

Nos mostraría lo siguiente (o algo aproximado a lo siguiente):

from IPython.utils import path
path.locate_profile()
get_ipython().history_manager.hist_file
get_ipython().magic('pinfo In')
a = 2
print(a)
Out
a
Out
b = Out[7]
print(b)
for celda in In:
    print(celda)

También podemos acceder a los tres últimos outputs de las tres últimas celdas usando _, __, ___, que nos mostrará el output de la última, penúltima o antepenúltima celdas usadas, respectivamente.

print('antepenúltima:n', ___, 'nn')
print('penúltima:n', __, 'nn')
print('última:n', _, 'nn')
antepenúltima:
 /home/kiko/.config/ipython/profile_default 
penúltima:
 /home/kiko/.config/ipython/profile_default/history.sqlite 
última:
 2

Si queremos acceder a los inputs de las últimas celdas podemos usar algo parecido pero de la siguiente forma, _i, _ii o _iii para la última, penúltima o antepenúltima celda de input:

print('antepenúltima:n', _iii, 'nn')
print('penúltima:n', _ii, 'nn')
print('última:n', _i, 'nn')
antepenúltima:
 print(b) 
penúltima:
 for celda in In:
    print(celda) 
última:
print('antepenúltima:n', ___, 'nn')
print('penúltima:n', __, 'nn')
print('última:n', _, 'nn')

Análogamente a lo visto anteriormente, podemos usar _n o _in para mostrar, respectivamente, el output o el input de la celda n. Por ejemplo, para ver el input y el output de la celda anterior (en este caso sería la 11) podemos hacer lo siguiente:

print('El input de la celda 11 es:')
print(_i11)

Que nos mostrará:

El input de la celda 11 es:
for celda in In:
    print(celda)

Y para el output:

print('Te he engañado. No existe output para la celda 11')
print('Si intentas acceder al valor _11 obtendrás un NameError ya que no existe la variable')
print('pero te puedo enseñar el de la celda 7:')
print(_7)
Te he engañado. No existe output para la celda 11
Si intentas acceder al valor _11 obtendrás un NameError ya que no existe la variable
pero te puedo enseñar el de la celda 7:
2

Lo anterior es equivalente a usar In[n] o Out[n]. Una tercera alternativa, además, sería usar _ih[n] para los inputs y _oh[n] para los outputs.

In[11]

Mostrará:

'for celda in In:n    print(celda)'

Mientras que:

_ih[11]

Nos mostrará lo mismo:

'for celda in In:n    print(celda)'

Acceso a bloques de historia

Para acceder a toda la historia de la sesión actual podemos usar las funciones mágicas %history o %hist, que es un alias.

Podemos obtener toda la historia o solo una porción. Por ejemplo, el siguiente comando nos mostrará la historia desde la celda 1 a la 10 en la sesión actual:

%hist 1-10
from IPython.utils import path
path.locate_profile()
get_ipython().history_manager.hist_file
In?
a = 2
print(a)
Out
a
Out
b = Out[7]
print(b)

Si, además de acceder a las celdas 1 a 10, queremos acceder a celdas sueltas podemos usar la siguiente notación para acceder a las celdas 12 y 14 (además de a las 10 primeras).

%hist 1-10 12 14
from IPython.utils import path
path.locate_profile()
get_ipython().history_manager.hist_file
In?
a = 2
print(a)
Out
a
Out
b = Out[7]
print(b)
print('antepenúltima:n', ___, 'nn')
print('penúltima:n', __, 'nn')
print('última:n', _, 'nn')
print('El input de la celda 11 es:')
print(_i11)

Si ahora queremos acceder a todas las celdas donde hayamos usado, por ejemplo, un comando que incluya 'a = 1' podemos hacer uso de la opción -g (similar a grep) de la siguiente forma (la salida dependerá de vuestra historia):

%hist -g a = 1
29/129: a = 1
29/133: a = 1
29/136: a = 1.1
29/138: a = 1
29/142: a = 1.1
29/145: a = 1
29/147: a = 1
29/149: a = 1
29/151: a = 1
51/7: a = 1
185/1: a = 1
187/19: %hist -g a = 1
187/20: %hist -g [a = 1]
187/21: %hist -g a = 1
188/20: %hist -g a = 1
189/2: a = 1
190/1: a = 1
190/3: a = 1
190/4: a = 1
190/10: a = 1
201/1:
code = """
def hello():
    if a = 1:
        print(a)
    elseif a =2:
        print(a)
    else:
        print('kk')
    return None
"""
201/3:
code = """
def hello():
    if a = 1:
        print(a)
    elif a =2:
        print(a)
    else:
        print('kk')
    return None
"""
201/5:
code = """
def hello():
    if a = 1:
        print(a)
    elif a = 2:
        print(a)
    else:
        print('kk')
    return None
"""
201/14:
a = 1
b = exec(code)
201/15:
a = 1
b = exec(code)
print(b)
201/16:
a = 1
b = exec(code)
print(c)
201/17:
a = 1
b = exec(code)
print(b)
201/43: def a():a = 1;return a
201/45: def a():a = 1;b=2return a
201/46: def a():a = 1;b=2;return a,b
201/81:
a = 1
code = """
def hello():
    if a == 1:
        print('a=1')
    elif a == 2:
        print('a=2')
    else:
        if a == 3:
            print('a=3')
    return None
"""
201/85:
a = 1
code = """
def hello():
    if a == 1:
        print('a=1')
    elif a == 2:
        print('a=2')
    else:
        if a == 3:
            print('a=3')
    return None
"""
201/93:
a = 1
code = """
def hello():
    if a == 1:
        return('a=1')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
"""
201/135:
a = 2
code = """
a = 1
def hello():
    # esto es un comentario de mierda
    if a == 1:
    # esto es otro comentario
        return('a=1 # esto no sería un comentario')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
hello()
"""
201/157:
a = 2
code = """
a = 1
def hello():
    """Hola"""
    # esto es un comentario de mierda
    if a == 1:
    # esto es otro comentario
        return('a=1 # esto no sería un comentario')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
class A():
    """Hola"""
    def __init__(self):
        "Hola"
    def _kk(self):
        'Adios'
hello()
help(A)
"""
201/158:
a = 2
code = """
a = 1
def hello():
    '''Hola'''
    # esto es un comentario de mierda
    if a == 1:
    # esto es otro comentario
        return('a=1 # esto no sería un comentario')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
class A():
    """Hola"""
    def __init__(self):
        "Hola"
    def _kk(self):
        'Adios'
hello()
help(A)
"""
201/159:
a = 2
code = """
a = 1
def hello():
    '''Hola'''
    # esto es un comentario de mierda
    if a == 1:
    # esto es otro comentario
        return('a=1 # esto no sería un comentario')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
class A():
    "Hola"
    def __init__(self):
        "Hola"
    def _kk(self):
        'Adios'
hello()
help(A)
"""
201/174:
code = """
a = 1
def hello():
    '''
    Hola
    '''
    # esto es un comentario de mierda
    if a == 1:
    # esto es otro comentario
        return('a=1 # esto no sería un comentario')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
class A():
    "Hola"
    def __init__(self):
        "Hola"
    def _kk(self):
        'Adios'
hello()
help(A)
"""
201/219:
code = """
a = 1
def hello():
    '''
    Hola
    '''
    # esto es un comentario de mierda
    if a == 1:
    # esto es otro comentario
        return('a=1 # esto no sería un comentario')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
class A():
    "Hola"
    def __init__(self):
        "Hola"
    def _kk(self):
        'Adios'
print(hello())
help(A)
"""
201/230:
code = """
a = 1
def hello():
    '''
    Hola
    '''
    # esto es un comentario de mierda
    if a == 1:
    # esto es otro comentario
        return('a=1 # esto no sería un comentario')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
class A():
    "Hola"
    def __init__(self):
        "Hola"
    def _kk(self):
        'Adios'
        pass
print(hello())
help(A)
"""
201/233:
code = """
a = 1
def hello():
    '''
    Hola
    '''
    # esto es un comentario de mierda
    if a == 1:
    # esto es otro comentario
        return('a=1 # esto no sería un comentario')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
class A():
    "Hola"
    def __init__(self):
        "Hola"
    def _kk(self):
        'Adios'
        pass
print(hello())
help(A)
"""
201/237:
code = """
a = 1
def hello():
    '''
    Hola
    '''
    # esto es un comentario de mierda
    if a == 1:
    # esto es otro comentario
        return('a=1 # esto no sería un comentario')
    elif a == 2:
        return('a=2')
    else:
        if a == 3:
            return('a=3')
    return None
class A():
    "Hola"
    def __init__(self):
        "Hola"
        pass
    def _kk(self):
        'Adios'
        pass
print(hello())
help(A)
"""
202/2: a = 1
202/3: a = 1
202/4: a = 1
202/5:
a = 1
print(a)
214/20: %hist -g a = 1
  20: %hist -g a = 1

[Gracias a la historia sé que escribo demasiado código estúpido... :-(]

Pero esta busqueda no se restringe a la historia de la sesión actual sino que buscará en toda la historia almacenada por IPython bajo el perfil que estemos usando. El anterior output indica la sesión, el número de línea/celda de esa sesión y el código usado en esa línea/celda con la siguiente notación:

Sesión/celda: Código_introducido_en_la_celda

En este caso, podéis ver que en la última línea no se indica el número de sesión puesto que se refiere a la sesión actual:

Si usamos la opción -o también obtendremos la historia con el output incluido. Podéis ver el siguiente ejemplo para ver como funciona:

%hist -o
from IPython.utils import path
path.locate_profile()
'/home/kiko/.config/ipython/profile_default'
get_ipython().history_manager.hist_file
'/home/kiko/.config/ipython/profile_default/history.sqlite'
In?
a = 2
print(a)
Out
{1: '/home/kiko/.config/ipython/profile_default',
 2: '/home/kiko/.config/ipython/profile_default/history.sqlite'}
a
2
Out
{1: '/home/kiko/.config/ipython/profile_default',
 2: '/home/kiko/.config/ipython/profile_default/history.sqlite',
 7: 2}
b = Out[7]
print(b)
for celda in In:
    print(celda)
print('antepenúltima:n', ___, 'nn')
print('penúltima:n', __, 'nn')
print('última:n', _, 'nn')
print('antepenúltima:n', _iii, 'nn')
print('penúltima:n', _ii, 'nn')
print('última:n', _i, 'nn')
print('El input de la celda 11 es:')
print(_i11)
print('Te he engañado. No existe output para la celda 11')
print('Si intentas acceder al valor _11 obtendrás un NameError ya que no existe la variable')
print('pero te puedo enseñar el de la celda 7:')
print(_7)
In[11]
'for celda in In:n    print(celda)'
_ih[11]
'for celda in In:n    print(celda)'
%hist 1-10
%hist 1-10 12 14
%hist -g a = 1
%hist -o

Otra cosa interesante es la opción -p, que coloca un prompt delante de cada línea de la historia que se muestra. Esto puede ser útil para, por ejemplo, escribir doctests.

En el siguiente ejemplo vamos a usar la opción -p junto con la opción -o:

%hist -po 1-10
>>> from IPython.utils import path
... path.locate_profile()
...
'/home/kiko/.config/ipython/profile_default'
>>> get_ipython().history_manager.hist_file
'/home/kiko/.config/ipython/profile_default/history.sqlite'
>>> In?
>>> a = 2
>>> print(a)
>>> Out
{1: '/home/kiko/.config/ipython/profile_default',
 2: '/home/kiko/.config/ipython/profile_default/history.sqlite'}
>>> a
2
>>> Out
{1: '/home/kiko/.config/ipython/profile_default',
 2: '/home/kiko/.config/ipython/profile_default/history.sqlite',
 7: 2}
>>> b = Out[7]
>>> print(b)

Si queremos guardar la historia o parte de la historia en un fichero para, por ejemplo, los doctests, podemos usar la opción -f.

Con la siguiente línea de código vamos a guardar el input, el output y vamos a colocar la línea del prompt de las 10 primeras celdas en un fichero llamado kk.txt:

%hist 1-10 -pof kk.txt

Si queremos acceder a la historia de una sesión anterior podemos usar lo siguiente:

%hist ~1/1-10
from IPython.utils import path
path.locate_profile()
get_ipython().history_manager.hist_file
In?
a = 2
print(a)
Out
a
Out
b = Out[7]
print(b)

De esta forma accederemos a las 10 primeras líneas de la sesión anterior. Si queremos acceder a las 10 primeras líneas de la penúltima sesión podemos hacer:

%hist ~2/1-10
from IPython.utils import path
path.locate_profile()
get_ipython().history_manager.hist_file
In?
a = 2
print(a)
Out
a
Out
b = Out[9]

Si, además, queréis numerar las celdas usadas podéis usar la opción -n:

%hist ~2/1-10 -n
213/1:
from IPython.utils import path
path.locate_profile()
213/2: get_ipython().history_manager.hist_file
213/3: In?
213/4: a = 2
213/5: print(a)
213/6: Out
213/7: a
213/8: Out
213/9: b = Out[9]

Algunos de los comandos usados no son aceptados por un intérprete Python cualquiera, como por ejemplo los comandos mágicos que empiezan por %. Por ello, podemos obtener los comandos ya traducidos a código Python ejecutable usando la opción -t de la historia:

%hist 1-10 -t
from IPython.utils import path
path.locate_profile()
get_ipython().history_manager.hist_file
get_ipython().magic('pinfo In')
a = 2
print(a)
Out
a
Out
b = Out[7]
print(b)

En la tercera línea podéis ver que en lugar de escribir %pinfo In ha escrito get_ipython().magic('pinfo In').

Acceso a la historia de los directorios usados

_dh (también podemos usar %dhist) nos da información de los directorios recorridos. Por ejemplo, voy a recorrer varios directorios y después veremos la historia de los directorios recorridos:

cd /home/kiko/pyprojs
/home/kiko/pyprojs
pwd
'/home/kiko/pyprojs'
cd /home/kiko/pyprojs/ipython-master/nb/
/home/kiko/pyprojs/ipython-master/nb

Si ahora escribimos:

%dhist
Directory history (kept in _dh)
0: /home/kiko/pyprojs/ipython-master/nb
1: /home/kiko/pyprojs
2: /home/kiko/pyprojs/ipython-master/nb

O algo, más o menos, equivalente:

_dh

En este caso nos devuelve una lista:

['/home/kiko/pyprojs/ipython-master/nb',
 '/home/kiko/pyprojs',
 '/home/kiko/pyprojs/ipython-master/nb']

Si solo quiero saber el directorio del que partí en la sesión de IPython en la que me encuentro puedo hacer lo siguiente:

_dh[0]

Y obtengo:

'/home/kiko/pyprojs/ipython-master/nb'

Y esto es todo de momento. Podéis combinar muchas cosas de las vistas aquí con cosas como %macro, %edit, %pastebin,... Si da tiempo, algo muy caro últimamente, hablaremos sobre algunas cosas que se me ocurren en próximas entregas.

Saludos y hasta la próxima entrega.

P.D.: Si veis alguna errata podéis usar los comentarios o mandar algún commit al repositorio de los notebooks.

157 cosas de IPython que no sabías y nunca preguntaste (I)

Voy a inaugurar una nueva serie de entradas dedicadas a IPython hablando de cosas que no son tan evidentes o que la gente no suele usar pero que están ahí y son tremendamente útiles una vez que las conoces y las incluyes en tu flujo de trabajo.

Inicialmente se iba a llamar '305928 cosas sobre IPython que no sabías (o sí) y que nunca tuviste tiempo de preguntar o de leer en la documentación o de leer en el código o nunca te hicieron falta o te estabas tomando unas cañas o estabas haciendo otras cosas más seductoras e inconfesables o {0}' pero me pareció un título un poco largo. Finalmente lo he reducido a 157 y pico, más o menos, para vuestra salud mental y la mia. El nombre está inspirado de forma muy libre en el nombre de la charla dada por Victor Terrón en la PyConES 2013 (¿que no la has visto? venga, a hacer los deberes, rápido. Y después vuelve por aquí).

Primero de todo, [mode taliban ON] se escribe IPython, las dos primeras en mayúscula y no iPython o ipython o aipaizon o IPhyton (sic) [mode taliban OFF]. Por tanto, recordadlo si no queréis que la vena de la frente de Mathias Bussonier se hinche.

Todos sabéis lo que es IPhyton (:-)), si no es así le puedes echar un vistazo a la documentación que está en la página oficial o visitar este vídeo que preparó JuanLu sobre el notebook. Si, aun así, sois tan vagos como yo y no queréis ver nada de lo anterior os cuento, como brevísimo resumen, que IPython es una consola interactiva con super poderes y magia negra incluida.

Ayuda

Ayuda estándar de IPython

Vamos a empezar con cosas muy sencillas como el uso de la ayuda que ofrece IPython. Normalmente, para obtener ayuda de un objeto se usa el comando help(), en IPython se puede usar la ayuda usando símbolos de interrogación. Si se usa un solo símbolo de interrogación se obtiene información general del objeto mientras que si se usan dos símbolos de interrogación se puede acceder a la implementación misma del objeto (solo en el caso de que haya sido programado en Python). Por ejemplo, veamos la ayuda del objeto calendar dentro de la biblioteca calendar disponible en la librería estándar y programada en Python.

from calendar import calendar
?calendar

Nos daría la siguiente información (os saltará una ventana en la parte inferior del navegador):

Type:       method
String Form:<bound method TextCalendar.formatyear of <calendar.TextCalendar object at 0xb6b6082c>>
File:       /usr/local/lib/python3.3/calendar.py
Definition: calendar(self, theyear, w=2, l=1, c=6, m=3)
Docstring:  Returns a year's calendar as a multi-line string.
Class Docstring:
method(function, instance)
Create a bound instance method object.

[Nota] El comando anterior es equivalente a hacer calendar?,
y también es equivalente a usar %pinfo calendar.

Si ahora usamos el doble signo de interrogación obtendremos información mucho más detallada como el fichero en el que se encuentra la función y el código usado para implementarla, entre otras cosas:

??calendar

La siguiente información saldrá en una ventana en la parte inferior del navegador

Type:       method
String Form:<bound method TextCalendar.formatyear of <calendar.TextCalendar object at 0xb6b6082c>>
File:       /usr/local/lib/python3.3/calendar.py
Definition: calendar(self, theyear, w=2, l=1, c=6, m=3)
Source:
    def formatyear(self, theyear, w=2, l=1, c=6, m=3):
        """
        Returns a year's calendar as a multi-line string.
        """
        w = max(2, w)
        l = max(1, l)
        c = max(2, c)
        colwidth = (w + 1) * 7 - 1
        v = []
        a = v.append
        a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
        a('n'*l)
        header = self.formatweekheader(w)
        for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
            # months in this row
            months = range(m*i+1, min(m*(i+1)+1, 13))
            a('n'*l)
            names = (self.formatmonthname(theyear, k, colwidth, False)
                     for k in months)
            a(formatstring(names, colwidth, c).rstrip())
            a('n'*l)
            headers = (header for k in months)
            a(formatstring(headers, colwidth, c).rstrip())
            a('n'*l)
            # max number of weeks for this row
            height = max(len(cal) for cal in row)
            for j in range(height):
                weeks = []
                 for cal in row:
                    if j >= len(cal):
                        weeks.append('')
                    else:
                        weeks.append(self.formatweek(cal[j], w))
                a(formatstring(weeks, colwidth, c).rstrip())
                a('n' * l)
        return ''.join(v)
Class Docstring:
method(function, instance)
Create a bound instance method object.

[Nota] El comando anterior es equivalente a hacer calendar??
y también es equivalente a usar %pinfo2 calendar.

Si ahora usamos la ayuda usual disponible, función help() de la siguiente forma help(calendar), veremos que la información es bastante más escueta que la obtenida mediante IPython.

help(calendar)

El resultado será:

Help on method formatyear in module calendar:
formatyear(self, theyear, w=2, l=1, c=6, m=3) method of calendar.TextCalendar instance
Returns a year's calendar as a multi-line string.

Uso de wildcards o comodines

Con el signo de interrogación también podemos usar wildcards para obtener todos los objetos que cumplen el criterio. Por ejemplo, nos acordamos que el otro día usamos una función que empezaba por ca pero no nos acordamos del nombre completo. Podemos buscar todos los objetos que se encuentran en el namespace de la siguiente forma:

ca*?

Y IPython nos dará lo siguiente:

calendar
callable

Esto no es excesivamente útil ya que IPython ofrece autocompletado con la tecla de tabulación y llegaríamos al mismo resultado de forma sencilla. Pero, ¿y si nos acordamos que el objeto usado terminaba por ar en lugar de empezar por ca? En este caso, el autocompletado no nos resultaría de mucha ayuda. Pero podríamos usar lo siguiente:

*ar?

Lo anterior nos daría lo siguiente:

calendar

Pero no, no era lo que buscaba. En realidad estaba buscando un objeto que contenía ar. Lo podemos buscar de la siguiente forma.

*ar*?

Lo anterior nos daría lo siguiente:

BytesWarning
DeprecationWarning
FutureWarning
ImportWarning
KeyboardInterrupt
PendingDeprecationWarning
ResourceWarning
RuntimeWarning
SyntaxWarning
UnicodeWarning
UserWarning
Warning
bytearray
calendar
vars

Algunas funciones mágicas de ayuda

También le podéis echar un ojo a %pdef, %pdoc, %psource o %pfile para obtener información de diverso tipo sobre el objeto de turno.

Creación de nuestras propias funciones mágicas de ayuda

La ayuda del notebook sale en una zona inferior de la ventana del navegador. En general me resulta más incómoda que si se imprimiese como un ouput estándar dentro del navegador mismo. Con la siguiente receta, un poco modificada por mí, podemos hacer que determinada información salga inline:

Receta hecha por jiffyclub

## Importamos distintas funciones del módulo oinspect de IPython.
## Muchas de estas funciones son un wrapper sobre las funciones del
##   módulo inspect de la stdlib.
from IPython.core.oinspect import (getsource, getdoc,
                                   find_file, find_source_lines)
## Importamos lo siguiente para mostrar la información en pantalla
from IPython.display import display, HTML
## Importamos lo siguiente para convertir nuestra ayuda a magig functions
from IPython.core.magic import Magics, magics_class, line_magic
## Los siguientes imports serán usados para resaltar la sintáxis del código
##   fuente.
## Es necesario tener instalada la librería pygments.
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter
## Llamamos a la InteractiveShell y obtenemos el namespace
##   del usuario (user_ns) que es un diccionario con los
##   objetos disponibles
ip = get_ipython()
my_ns = ip.user_ns
@magics_class
class KikoMagic(Magics):
    def __init__(self, shell):
        super(KikoMagic, self).__init__(shell)
    @line_magic
    def kdoc(self, obj):
        """
        Retrieve the info of an object and display this info in an output.
        """
        if obj in my_ns.keys():
            print("Doc info for {}:n".format(obj))
            print(getdoc(my_ns[obj]))
        else:
            print("There is no info for {}".format(obj))
    @line_magic
    def kfile(self, obj):
        """
        Retrieve the file where the object is implemented.
        """
        if obj in my_ns.keys():
            print("{} implemented in file:n".format(obj))
            print(find_file(my_ns[obj]))
        else:
            print("We can't not find the file for {}".format(obj))
    @line_magic
    def ksourceline(self, obj):
        """
        Retrieve the first line in the source file where
        the object is implemented.
        """
        if obj in my_ns.keys():
            print("The implementation of {}".format(obj))
            print("starts at line {}".format(find_source_lines(my_ns[obj])))
            print("in file {}".format(find_file(my_ns[obj])))
        else:
            print("We can't not find the file for {}".format(obj))
    @line_magic
    def ksource(self, obj):
        """
        Retrieve the info and the source of an object and
        display the info in an output.
        """
        formatter = HtmlFormatter(linenos=False, cssclass="source", nobackground=True)
        template = """</pre>
<style><!--
{}
--></style>
<pre>{}"""
 src = getsource(my_ns[obj])
 html = highlight(src, PythonLexer(), formatter)
 css = formatter.get_style_defs()
 display(HTML(template.format(css,html)))
 @line_magic
 def khelp(self, obj):
 self.kdoc(obj)
 print("")
 self.ksourceline(obj)
 print("")
 self.ksource(obj)
## Registramos las nuevas funciones mágicas para que estén
## disponibles en el nb.
ip.register_magics(KikoMagic)

Dos de las funciones mágicas solo funcionarán en el notebook y no en la consola de IPython ya que estamos haciendo uso de display(HTML(...)) y la qtconsole, por ejemplo, no es capaz de mostrar el código html de forma correcta. Ahora usamos las nuevas funciones mágicas que acabamos de crear:

  • kdoc
%kdoc calendar

Que nos devolverá:

Doc info for calendar:
Returns a year's calendar as a multi-line string.
  • kfile
%kfile calendar

Que nos devolverá:

calendar implemented in file: /usr/local/lib/python3.3/calendar.py
  • ksourceline
%ksourceline calendar

que nos devolverá:

The implementation of calendar starts at line 334 in file /usr/local/lib/python3.3/calendar.py
  • ksource
%ksource calendar

que nos devolverá:

def formatyear(self, theyear, w=2, l=1, c=6, m=3):
        """
        Returns a year's calendar as a multi-line string.
        """
        w = max(2, w)
        l = max(1, l)
        c = max(2, c)
        colwidth = (w + 1) * 7 - 1
        v = []
        a = v.append
        a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
        a('n'*l)
        header = self.formatweekheader(w)
        for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
            # months in this row
            months = range(m*i+1, min(m*(i+1)+1, 13))
            a('n'*l)
            names = (self.formatmonthname(theyear, k, colwidth, False)
                     for k in months)
            a(formatstring(names, colwidth, c).rstrip())
            a('n'*l)
            headers = (header for k in months)
            a(formatstring(headers, colwidth, c).rstrip())
            a('n'*l)
            # max number of weeks for this row
            height = max(len(cal) for cal in row)
            for j in range(height):
                weeks = []
                for cal in row:
                    if j >= len(cal):
                        weeks.append('')
                    else:
                        weeks.append(self.formatweek(cal[j], w))
                a(formatstring(weeks, colwidth, c).rstrip())
                a('n' * l)
        return ''.join(v)
  • khelp
%khelp calendar

Que nos devolverá:

Doc info for calendar:
Returns a year's calendar as a multi-line string.
The implementation of calendar
starts at line 334
in file /usr/local/lib/python3.3/calendar.py
    def formatyear(self, theyear, w=2, l=1, c=6, m=3):
        """
        Returns a year's calendar as a multi-line string.
        """
        w = max(2, w)
        l = max(1, l)
        c = max(2, c)
        colwidth = (w + 1) * 7 - 1
        v = []
        a = v.append
        a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
        a('n'*l)
        header = self.formatweekheader(w)
        for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
            # months in this row
            months = range(m*i+1, min(m*(i+1)+1, 13))
            a('n'*l)
            names = (self.formatmonthname(theyear, k, colwidth, False)
                     for k in months)
            a(formatstring(names, colwidth, c).rstrip())
            a('n'*l)
            headers = (header for k in months)
            a(formatstring(headers, colwidth, c).rstrip())
            a('n'*l)
            # max number of weeks for this row
            height = max(len(cal) for cal in row)
            for j in range(height):
                weeks = []
                for cal in row:
                    if j >= len(cal):
                        weeks.append('')
                    else:
                        weeks.append(self.formatweek(cal[j], w))
                a(formatstring(weeks, colwidth, c).rstrip())
                a('n' * l)
        return ''.join(v)

¡¡Eyyy, qué chulo!! Acabamos de personalizar un poco el notebook de IPython de forma muy sencilla.

Si solo queremos hacer una función mágica lo podemos hacer de forma un poco más sencilla que la vista anteriormente. En el siguiente código se muestra cómo (receta hecha por Brian Granger y que he actualizado a las últimas versiones de IPython para hacerla funcionar):

from IPython.core.oinspect import getdoc
ip = get_ipython()
my_ns = ip.user_ns
## Definimos la función que hace lo que queremos. La siguiente
##   función hace lo mismo que la función mágica kdoc que hemos
##   implementado anteriormente
def new_magic(obj):
    if obj in my_ns.keys():
        print(getdoc(my_ns[obj]))
    else:
        print("No info found for {}".format(obj))
## En la siguiente línea definimos la anterior función como
##   una 'line magic' que se llamará 'my_new_magic'
ip.register_magic_function(new_magic, 'line', "my_new_magic")

Ahora la vamos a hacer funcionar:

%my_new_magic calendar

que nos devolverá:

Returns a year's calendar as a multi-line string.

Resumen

Hemos visto como:

  • usar la ayuda de IPython, más avanzada que el uso de la función built-in help(),
  • uso de comodines (wildcards),
  • las funciones mágicas útiles para obtener ayuda de Python,
  • como crear nuestras propias funciones mágicas de ayuda (o de lo que queráis).

Si se os ocurre algo para completar esta información podéis hacer un pull request a nuestro repo de notebooks y actualizaremos la información.

Entrevista a Fernando Pérez, creador de IPython

Hoy os traemos una novedad desde Pybonacci: inauguramos la sección de entrevistas con Fernando Pérez, que ha accedido a charlar un rato con nosotros sobre el pasado, presente y futuro de IPython, la ciencia abierta y alguna que otra cosa más.

Enlaces

  • Web de IPython http://ipython.org/
  • Beca de la Fundación Sloan http://ipython.org/sloan-grant.html
  • Etiqueta ipython en Stack Overflow http://stackoverflow.com/tags/ipython
  • Lista de correo ipython-dev http://mail.scipy.org/mailman/listinfo/ipython-dev
  • Información para desarrolladores https://github.com/ipython/ipython/wiki/Dev:-Index
  • Chat de desarrolladores http://www.hipchat.com/ghtNzvmfC

Publicando directamente en wordpress.com con python

Este es un notebook de prueba publicado directamente en http://pybonacci.org (aunque se puede publicar en cualquier plataforma wordpress que tenga habilitado xml-rpc) desde un ipython notebook (ipynb de ahora en adelante).

Este ipynb consta de celdas con código python, celdas con texto formateado con markdown, con output de celdas con imágenes que se muestran 'inline', con celdas que enlazan a videos de youtube, con celdas que enlazan a imágenes online, con celdas que enlazan a páginas web,...

  1. Las celdas con código python se mostrarán como si en wordpress se envolviese el código con las etiquetas
  1. Las celdas con texto formateado con markdown se transforman a html con la ayuda de http://pypi.python.org/pypi/Markdown/
  1. Los output de imágenes (por ejemplo, matplotlib) se suben directamente a wordpress como imágenes y se enlazan en la entrada
  1. Los output con videos de youtube se enlazan con la etiqueta de wordpress
  1. Los output de imágenes online harán una copia de la imagen que estéis enlazando y la subirán a vuestro wordpress por lo que tened cuidado si enlazáis imágenes con copyright y/o posibles restricciones.
  1. Los output de HTML de ipython se convierten en un enlace en lugar de meter un frame en wordpress.
  1. Podéis añadir lo que queráis...
## Ejemplo de celda que muestra una imagen inline
import matplotlib.pyplot as plt
plt.plot([1,2,3,4,3,2,1])

La salida del anterior código mostrará lo siguiente

## Ejemplo de celda que muestra un video de youtube
from IPython.display import YouTubeVideo
YouTubeVideo("9sEI1AUFJKw")

La salida del anterior código mostrará lo siguiente

## Ejemplo de celda que muestra una imagen enlazada como output
from IPython.display import Image
Image("http://pybonacci.org/wp-content/uploads/2012/11/pybofractal1.png")

La salida del anterior código mostrará lo siguiente

## Ejemplo de celda que muestra un frame html como output
from IPython.display import HTML
HTML('')

La salida del anterior código mostrará lo siguiente
Link a http://ipython.org

Todo esto es solo una prueba de concepto. Tengo que estudiarme nbconvert o ver si se podría integrar el script que ha creado esta entrada (pero bien hecho) a partir de este ipynb directamente en los menus de ipynb para poder publicar directamente un ipynb en wordpress (y otros) directamente con un par de clicks.

Nos vemos próximamente con más novedades.

Saludos.

El notebook de IPython

Introducción

Ya hablamos en su momento de IPython, un intérprete de Python con multitud de características avanzadas que lo hacían indispensable para ejecutar sesiones interactivas. Hoy vamos a continuar con lo que habíamos dejado a medias, y vamos a dedicar un artículo al notebook de IPython, una herramienta que está verdaderamente revolucionando la manera en que se utiliza Python en ámbitos científicos y conferencias sobre el lenguaje, como se ha demostrado en la reciente PyData NYC 2012 que se ha celebrado en Nueva York (recuerda que en Pybonacci hemos seleccionado un resumen de charlas de la PyData NYC 2012).

https://twitter.com/dfm/status/262245974334918656

Aquí incluimos un vídeo que hemos grabado para mostrar las características fundamentales de IPython, porque ya se sabe que un vídeo vale más que mil imágenes ;) No olvides suscribirte a nuestro canal en YouTube para futuras creaciones.

El notebook de IPython

El notebook de IPython es una interfaz web para IPython, inspirada en los notebooks de Mathematica y Sage. Como puedes leer en esta retrospectiva histórica escrita por Fernando Pérez, la idea de crear una interfaz de este tipo ya existía desde los inicios del proyecto IPython, allá por 2001, y después de muchos años, varios intentos fallidos y habiendo aprendido de la experiencia del propio notebook de Sage, que surgió antes pero estaba mucho menos pulido, fue finalmente presentado en la conferencia EuroSciPy 2011.

Continue reading

Sage: software matemático libre como alternativa

Seguro que muchos de vosotros ya conocéis Sage: un proyecto cuyo nada ambicioso objetivo es

Crear una alternativa de código abierto, libre y viable a Magma, Maple, Mathematica y MATLAB.

Desde luego hay que admitir que como declaración de intenciones no está mal. Hechas las presentaciones, ¿qué más podemos decir sobre Sage?

Sage comenzó en 2004 como un proyecto personal de William Stein, profesor de matemáticas en la Universidad de Washington, quien, como explica en su blog, estaba frustrado por no poder solucionar las limitaciones de Magma al no ser un programa libre. Stein se dio cuenta de que, aunque crear un sistema como Magma o Maple llevaría años con un equipo de desarrolladores voluntarios partiendo de cero, ya había numerosos paquetes de código abierto escritos en diferentes lenguajes enfocados a diversas áreas matemáticas. Así que decidió unir todos estos paquetes utilizando Python (en este momento son cerca de 100) para crear un enorme software matemático para crear Sage. En esto se diferencia de otros proyectos como SymPy, del que ya hemos hablado en este blog.

Continue reading