Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

Publicaciones sobre programming (publicaciones antiguas, página 71)

rst2pdf 0.90 is out

Yes, af­ter many moon­s, it's out. Here is the (as usu­al) in­com­plete changel­og:

  • Added raw HTML sup­­port, by Dim­itri Christodoulou

  • Fixed Is­­sue 422: Hav­ing no .afm files made font lookup slow.

  • Fixed Is­­sue 411: Some­­times the win­­dows reg­istry has the font's ab­s­path.

  • Fixed Is­­sue 430: Us­ing --­­con­­fig op­­tion caused oth­­er op­­tions to be ig­nored (by charles at cstan­hope dot com)

  • Fixed Is­­sue 436: Add pdf_style_­­path to sphinx (by tyler@­­datas­­tax.­­com)

  • Fixed Is­­sue 428: page num­bers logged as er­rors

  • Added sup­­port for many pyg­­ments op­­tions in code-block (by Joaquin So­ri­anel­lo)

  • Im­­ple­­men­t­ed Is­­sue 404: plan­­tuml sup­­port

  • Is­­sue 399: sup­­port sphinx's tem­­plate path op­­tion

  • Fixed Is­­sue 406: calls to the wrong log­ging func­­tion

  • Im­­ple­­men­t­ed Is­­sue 391: New --­sec­­tion-­­head­­er-depth op­­tion.

  • Fixed Is­­sue 390: the --­­con­­fig op­­tion was ig­nored.

  • Added sup­­port for many pyg­­ments op­­tions in code-block (by Joaquin So­ri­anel­lo)

  • Fixed Is­­sue 379: Wrong style ap­­plied to para­­graphs in de­f­i­ni­­tion­s.

  • Fixed Is­­sue 378: Mul­ti­­line :ad­­dress: were shown col­lapsed.

  • Im­­ple­­men­t­ed Is­­sue 11: Frame­Break (and con­di­­tion­al Frame­Break)

  • The de­scrip­­tion of frames in page tem­­plates was just wrong.

  • Fixed Is­­sue 374: in some cas­es, lit­er­al blocks were split in­­­side a page, or the page­break came too ear­­ly.

  • Fixed Is­­sue 370: warn­ing about sphinx.addnodes.high­­­light­lang not be­ing han­­dled re­­moved.

  • Fixed Is­­sue 369: crash in hy­phen­a­tor when spec­i­­fy­ing "en" as a lan­guage.

  • Com­­pat­i­­bil­i­­ty fix to Sphinx 0.6.x (For python 2.7 doc­s)

This re­lease did not fo­cus on Sphinx bugs, so those are prob­a­bly still there. Hope­ful­ly the next round is at­tack­ing those.

Raspar no duele

Así que vea­mos el si­tio de las Ted Ta­lks. Tie­nen una lin­da ta­bla con in­for­ma­ción de las char­la­s, por si que­rés ha­cer al­go con ella­s.

¿Y có­mo sa­cás esa in­fo? Ha­cien­do "s­cra­pin­g" de la pá­gi­na. ¿Y có­mo ha­ce­mos eso de for­ma in­do­lo­ra? Con Py­thon y Beau­ti­ful­Sou­p.

from BeautifulSoup import BeautifulSoup
import urllib

# Leemos toda la página
data = urllib.urlopen('http://www.ted.com/talks/quick-list').read()
# La parseamos
soup = BeautifulSoup(data)

# Busco la tabla con la data
table = soup.findAll('table', attrs= {"class": "downloads notranslate"})[0]
# Tomo las filas, salteando la primera
rows = table.findAll('tr')[1:]

items = []
# Para cada fila saco los datos
# Y la guardo en algún lado
for row in rows:
    cells = row.findAll('td')
    item = {}
    item['date'] = cells[0].text
    item['event'] = cells[1].text
    item['title'] = cells[2].text
    item['duration'] = cells[3].text
    item['links'] = [a['href'] for a in cells[4].findAll('a')]
    items.append(item)

¡Y ya es­tá! Sor­pren­den­te­men­te in­do­lo­ro.

Escribir, y qué escribir.

Por otro la­do, es­cri­bí una se­rie muy po­pu­lar de pos­ts, lla­ma­da "P­y­Qt en Ejem­plo­s", que (a­di­vi­nen) lle­va mu­cho tiem­po es­tan­ca­da.

El pro­ble­ma con el li­bro es que tra­té de cu­brir de­ma­sia­do te­rreno. Ter­mi­na­do se­ría un li­bro de 500 pá­gi­na­s, y eso in­clu­ye es­cri­bir me­dia do­ce­na de apps de ejem­plo, al­gu­nas de ellas en áreas en las que no soy ex­per­to.

El pro­ble­ma prin­ci­pal con los pos­ts es que el ejem­plo es pe­do­rro (¡a­pp de TO­DO­s!) y ex­pan­dir­la es abu­rri­do.

¡Qué me­jor ma­ne­ra de re­sol­ver el pro­ble­ma que mez­clar las dos co­sas!

Voy a de­jar Py­thon No Muer­de co­mo es­tá, y voy a ha­cer un li­bro nue­vo, que se lla­me Py­Qt No Muer­de. Va a man­te­ner el tono y el len­gua­je del an­te­rio­r, y va a com­par­tir va­rios ca­pí­tu­lo­s, pe­ro se va a en­fo­car en de­sa­rro­llar apps Py­Q­t, en vez de apun­tar a me­tas de­ma­sia­do am­bi­cio­sas. Es­pe­ro que sea de unas 200 pá­gi­na­s.

Ten­go per­mi­so de la su­pe­rio­ri­dad (mi se­ño­ra) pa­ra tra­ba­jar en es­to un par de ho­ras al día tem­prano a la ma­ña­na. Tal vez avan­ce, tal vez no. Co­mo siem­pre, yo no pro­me­to, ex­pe­ri­men­to.

Sacar la basura trae sus problemas

Es­to no de­be­ría sor­pren­der­te:

>>> a = [1,2]
>>> b = [3,4]
>>> a is b
False
>>> a == b
False
>>> id(a) == id(b)
False

Des­pués de to­do, a y b son co­sas dis­tin­ta­s. Sin em­bar­go:

>>> [1,2] is [3,4]
False
>>> [1,2] == [3,4]
False
>>> id([1,2]) == id([3,4])
True

Re­sul­ta que si uno usa li­te­ra­le­s, una de esas co­sas no es co­mo las de­má­s.

Pri­me­ro la ex­pli­ca­ció­n. Cuan­do uno no tie­ne más re­fe­ren­cias a un da­to, va a ser "gar­ba­ge co­llec­te­d", la me­mo­ria se li­be­ra pa­ra que se pue­da usar pa­ra otra co­sa.

En el primer caso, las variables a y b guardan referencia a las listas. Es decir que tienen que existir todo el tiempo, ya que yo podría decir print a y python tiene que poder responderme con el valor de a.

En el segundo caso, uso literales, lo que quiere decir que no hay referencias a las listas después de que se usan. Cuando python evalúa id([1,2]) == id([3,4]) evalúa primero el lado izquierdo del ==. Después de que termina con eso, no hace falta mantener el [1,2] a mano, así que se borra. Entonces, al evaluar el lado derecho, crea [3,4].

Por pura casualidad, lo pone en exactamente el mismo lugar en que estaba el [1,2], asi que id devuelve el mismo valor. Esto sirve para recordar dos cosas:

  1. a is b es usual­men­te (pe­ro no siem­pre) equi­va­len­te a id(a) == id(­b)

  2. La re­­co­­­le­c­­ción de ba­­su­­ra tie­­ne efe­c­­tos se­­cun­­da­­rios que en una de esas no es­­pe­­ra­­ba­s.

The problem is is. Is it not?

Al­gu­no­s, por al­gu­na ra­zó­n, ha­cen es­to:

>>> a = 2
>>> b = 2
>>> a == b
True
>>> a is b
True

Y des­pué­s, cuan­do ven es­to, se sor­pren­den:

>>> a = 1000
>>> b = 1000
>>> a == b
True
>>> a is b
False

Se sorprenden porque "2 es 2" es más intuitivo que "1000 no es 1000". Podría atribuirlo a una tendencia innata al platonismo, pero en realidad es porque is no es eso.

El operador is es (en CPython) apenas una comparación de direcciones de memoria. Si los objetos a y b son el mismo cacho de memoria, entonces "son" el otro. Como python crea de antemano una cantidad de enteros pequeños, cada 2 que creás no es un nuevo 2, sino otra vez el 2 de la última vez.

Es­to fun­cio­na por dos mo­ti­vo­s:

  1. Los en­­te­­ros son so­­­lo le­c­­tu­­ra. Po­­­dés te­­ner mu­­chas va­­ria­­bles que "co­n­­tie­­nen" el mis­­mo 2, po­r­­que no lo pue­­den ro­m­­pe­­r.

  2. En py­tho­n, la asig­na­ción es tan só­lo crear alia­ses. No se ha­ce una co­pia de 2 cuan­do se ha­ce a = 2, so­la­men­te se di­ce "a es otro nom­bre pa­ra es­te 2 que ten­go acá".

Esto sorprende a la gente que viene de otros lenguajes, por ejemplo C o C++. En esos lenguajes, una variable int a nunca usaría la misma memoria que int b porque justamente, una variable es un pedazo de memoria, y se puede cambiar el contenido. En C y C++, los enteros son mutables. Este 2 no es ese 2, a menos que lo hagas intencionalmente con punteros.

De he­cho, la for­ma en que la asig­na­ción fun­cio­na en py­thon lle­va a otras sor­pre­sas que son más in­te­re­san­tes en la vi­da rea­l. Por ejem­plo:

>>> def f(s=""):
...     s+='x'
...     return s
...
>>> f()
'x'
>>> f()
'x'
>>> f()
'x'

Eso no sor­pren­de na­da. Aho­ra, ha­ga­mos un pe­que­ño cam­bio:

>>> def f(l=[]):
...     l.append('x')
...     return l
...
>>> f()
['x']
>>> f()
['x', 'x']
>>> f()
['x', 'x', 'x']

Y eso sí es sorprendente, si no lo esperabas. Sucede porque las listas son mutables. El argumento por default se define cuando la función se define, y cada vez que llamás f() estás usando y devolviendo la misma l. Antes, también usábamos siempre la misma s pero como los strings son inmutables, nunca cambiaba, y devolvíamos una nueva cada vez.

Podés comprobar que no te miento, obviamente que usando is. Y ya que estamos, eso no es un problema para listas. Es un problema para los objetos de cualquier clase que vos definas, a menos que los hagas inmutables. Así que seamos cuidadosos con los argumentos por defecto, ¿ok?

Volviendo al problema original de que 1000 is not 1000, lo sorprendente es que en realidad, no es interesante. Los enteros son fungibles. No te importa que sea el mismo entero, solo que sean iguales.

Com­pro­bar iden­ti­dad de en­te­ros es co­mo si me pres­ta­ras $1 y cuan­do te lo de­vuel­vo, en vez de ver si es una mo­ne­da de $1, te fi­ja­ras si es la mis­ma mo­ne­da. Sim­ple­men­te no im­por­ta. Lo que que­res es un 2, un 1000 o una mo­ne­da de $1.

Además, el reultado de 2 is 2 depende de la implementación de python. No hay motivo, en realidad, mas allá de una optimización, para que sea True.

Es­pe­ran­do que es­to acla­re el te­ma, les de­jo un úl­ti­mo frag­men­to de có­di­go:

.. code-block:: pycon
>>> a = float('NaN')
>>> a is a
True
>>> a == a
False

UP­DA­TE: Mu­chos co­men­ta­rios ite­re­san­tes en re­ddit y una con­ti­nua­ción chi­qui­ta acá


Contents © 2000-2020 Roberto Alsina