Juanjo Conti: El pelo en el jabón (remasterizado)

   Publicado:

Para el 3er SLAM de poesía oral de Santa Fe rescaté un texto de mi primer libro y lo pulí un poco más. Estoy bastante conforme con el resultado, y en su presentación oral tubo buena aceptación.

El pelo en el jabón

Probablemente un pelo en el jabón sea uno de los objetos más limpios del universo. Sin embargo, cuando uno —con su cuerpo transpirado y el pelo sucio— se dirige a la ducha para descargar ahí toda la mugre del día —del cuerpo y del alma— y se encuentra un pelo en el jabón…

¡Ah! que desazón y que violencia, que sentimiento de violación a la intimidad de las gotas de agua que están cayendo sobre nosotros.
Es que es tal la relación que se tiene con el jabón, ese pan blanco protector y confidente, que el solo hecho de encontrar un pelo incrustado, cual fosil en piedra, nos recuerda que el vínculo que nos une a él, no es inmaculado.

Más personas frotan su cuerpo transpirado en él.

Y entonces, entre parientes y amigos, empezamos a buscar sospechosos.

Lo medimos, estudiamos su color, ¿rubio oscuro o castaño claro? ¿De qué parte del cuerpo de ese vil rufián será el pelo? Demasiado corto para cabellera de mujer, demasiado largo para pelo de pierna de hombre.

La cadena de deducciones se congela en el cerebro y el estómago se nos revuelve. Con las uñas y precisión quirúrgica nos animamos, lo sujetamos y lo retiramos de su soporte pastoso. Lo sostenemos ante nuestros ojos para examinarlo mejor. Reflexionamos. Una nueva inspección ocular. Parece que sí. Falsa alarma. Se trataba de un pedazo de hilo que se escapó del calzoncillo mientras lo lavábamos rasguñando su textil composición la noche anterior. Ahora sí, fuera de peligro podemos bañarnos tranquilos. Pero… ¿qué sucede? Se terminó el agua caliente.

Diego Sarmentero: Development Journal: Week 12

   Publicado: I have forgotten about this for a while, but I was crazy busy doing the release of "MagneBot" and then I got really excited working on "Into The Light"!

This week was awesome for "Into The Light", I had the script and lots of notes about what I wanted to do with this game, but there were just that: "ideas". Now I'm starting to put the pieces together and make the game a reality.

Here are some videos of the progress for this week:







Also, I have finally created the website for "Mind Echoes"!!
So, it was a really productive week, I learned a lot about lots of stuff and mostly I was able to finish with the mechanics for the player and the flare (probably some improves will be done in the future, but everything working smoothly for now).


Now it's time to start building some levels :D

Roberto Alsina: El Sutil Arte de Citar

   Publicado:

Los pe­rio­dis­tas te­le­vi­si­vos sue­len te­ner un cier­to com­ple­jo de in­fe­rio­ri­dad con lo­s ­que me­dios im­pre­so­s. Por eso so­lés ver gen­te que con­du­ce exi­to­sos ci­clos po­lí­ti­co­s ­re­cor­dar que ellos el pe­rio­dis­mo lo ma­ma­ron en la re­dac­ción del se­ma­na­rio "El Man­gru­llo­" ­de Ve­na­do Tuer­to, y que eso de ga­nar mu­cha gui­ta apa­re­cien­do en te­le lo ha­cen por­que no les que­da otra.

De ahí que cuan­do (po­né­le) Ma­jul ha­ce una no­ta y la le­van­ta un me­dio im­pre­so, siem­pre ­la re­twi­tea. Por ejem­plo, acá mues­tra, or­gu­llo­so, que su no­ta es ci­ta­da por el Cro­nis­ta ­Co­mer­cia­l:

Aho­ra bien, si uno va y lee la no­ta... re­sul­ta que di­ce exac­ta­men­te lo con­tra­rio que el tí­tu­lo.

Ci­to la no­ta:

"Con Rabbani no tenemos ningún intercambio de dinero."

Sí ad­mi­te re­ci­bir di­ne­ro de Irán:

Muchas veces las universidades de Teherán contribuían con nosotros y le decíamos "necesitamos tanto para hacer el trabajo religioso"

Sin em­bar­go, Irán no es Ra­bba­ni, y Ra­bba­ni no es Irán. No ten­go idea de si Kha­lil re­ci­be di­ne­ro de ­Ra­bba­ni, y no es de lo que es­toy ha­blan­do en es­te mo­men­to. Lo que sí veo es que no lo di­jo. Y que es­e tí­tu­lo es men­ti­ra, y el tweet es men­ti­ra, y Ma­jul re­pi­te una men­ti­ra.

Y lo ha­ce a sa­bien­da­s.

Co­mo di­ría Luis Ma­ju­l... ¿Que sen­tís, Luis Ma­ju­l?

Roberto Alsina: La Escalada Honestística

   Publicado:

Ano­che ví Ani­ma­les Suel­to­s, el pro­gra­ma de Fan­tino. Lo pri­me­ro es fe­li­ci­tar a quien co­rres­pon­da, ­sea Fan­ti­no, Amé­ri­ca TV, o el pú­bli­co, que ha con­ver­ti­do un pro­gra­ma don­de Co­co Si­ly se­guía a­fa­nan­do con "La cáte­dra del ma­cho" ad eter­num en un pro­gra­ma de en­tre­vis­tas po­lí­ti­ca­s.

Se­rá cri­ti­ca­ble, Fan­tino ten­drá la ac­ti­tud en­tre­vis­ta­do­ra más irri­tan­te del uni­ver­so ("ex­pli­ca­me co­mo a un ni­ño" !?!) ­pe­ro es me­jo­r, en el sen­ti­do de más im­por­tan­te, que es­cu­char chis­tes de bo­rra­chos (en el sen­ti­do de chis­tes que pa­re­ce que los con­ta­ra un ti­po bo­rra­cho­).

Más allá del ra­cis­mo oca­sio­nal ("e­so pa­sa en paí­ses co­mo Ugan­da, Ni­ge­ria"), ayer tu­vo do­s en­tre­vis­tas in­te­re­san­tes, una con Er­nes­to San­z, el úl­ti­mo ex­po­nen­te de que eso de que ­la UCR no se do­bla era ver­so, y De Nar­váe­z, el úl­ti­mo mohi­cano me­ne­mis­ta.

Y una de las co­sas in­te­re­san­tes, fue que le pre­gun­tó la mis­ma co­sa a los do­s. ¿Cuán­to­ ­creen que se ro­bó du­ran­te el kir­ch­ne­ris­mo­?.

Pri­me­ro, Sanz ti­ró "más de mil mi­llo­nes de dó­la­res".

Y co­mo de­cir lo mis­mo o de­cir me­nos no tie­ne gra­cia, De Nar­váez salió a es­ca­la­r. Es­ta es la cuen­ta que sacó:

  1. El PBI de Ar­gen­ti­na es 300 mil mi­llo­nes de dó­la­res.
  2. El pre­su­pues­to del es­ta­do na­cio­nal es la mi­ta­d, 150 mil mi­llo­nes de dó­la­res.
  3. Se ro­ba por lo me­nos el 10% del pre­su­pues­to
  4. Se ro­ba­ron 15 mil mi­llo­nes de dó­la­res.

Eso es mu­cho, pe­ro De Nar­váez se ol­vi­dó de mul­ti­pli­car­lo por los años que lle­van en el go­bier­no, con lo que la ci­fra, en rea­li­da­d, se­ría de 180 mil mi­llo­nes de dó­la­res.

Ca­be des­ta­car que eso es sin con­tar co­rrup­ción a ni­vel pro­vin­cial y mu­ni­ci­pa­l, pe­ro­ ­bue­no, no in­fle­mos más de lo que in­fló él.

Lo que cues­ta, tal ve­z, en­ten­de­r, es exac­ta­men­te cuan­ta pla­ta es eso. Así que vea­mo­s al­gu­nos ejem­plo­s.

Con 250.000 dó­la­res te com­prás una lin­da ca­sa, en un lin­do ba­rrio. Esa pla­ta al­can­za ­pa­ra 720.000 ca­sas así. Eso es más que la canti­dad de in­mue­bles de la Ciu­dad Au­tó­no­ma ­de Bue­nos Ai­res.

Se­gún Nordhei­me­r.­co­m, con 1.000.000 de dó­la­res te com­prás unas 150 hec­tá­reas en Car­los Ca­sa­res, ­ple­na pam­pa hú­me­da. Eso quie­re de­cir que con lo que di­ce De Nar­váe­z, se pue­den com­pra­r 27 MI­LLO­NES de hec­tá­reas en la pam­pa hú­me­da. Eso es el 90% de la pro­vin­cia de Bue­nos Ai­res.

En Ar­gen­ti­na ha­y, mas o me­no­s, 40 mi­llo­nes de per­so­na­s. Esa pla­ta al­can­za pa­ra dar­le 4500 ­dó­la­res a ca­da hom­bre mu­jer y ni­ño del país.

Un pa­que­te de 100 bi­lle­tes de 100 dó­la­res (o sea, 10.000 dó­la­res) mi­de 15.24­cm x 5cm x 1.1­cm. ­Si api­la­mos la pla­ta que di­ce De Nar­váe­z, da una al­tu­ra de 198 km.

¡Cla­ro, po­de­mos aco­mo­dar­la me­jo­r! El vo­lu­men de ese di­ne­ro es 1.508.760.000 cen­tí­me­tros cú­bi­co­s, o, re­don­dean­do, un mi­llon y me­dio de li­tro­s. Es de­cir que po­de­mos lle­nar una pi­le­ta olím­pi­ca has­ta ­más de la mi­ta­d, su­po­nien­do que no de­ja­mos nin­gún es­pa­cio en­tre bi­lle­tes.

O, po­dría­mos lle­nar 22.3 con­tai­ners stan­dard de 40 pie­s, si ca­da uno tie­ne un vo­lu­men in­terno de 67,5 me­tros cú­bi­cos co­mo di­ce Wiki­pe­dia.

Un bi­lle­te de 100 dó­la­res pe­sa exac­ta­men­te un gra­mo, así que en to­tal son 1800 to­ne­la­das de di­ne­ro­. ­Pa­ra trans­por­tar ese di­ne­ro se ne­ce­si­tan 3 avio­nes Jum­bo de car­ga.

Aho­ra bien: si real­men­te us­ted cree que Nes­tor y Cris­ti­na afa­na­ron esa canti­dad de di­ne­ro­...

Pién­se­lo, y tra­te de com­pa­ti­li­zar esa canti­dad de di­ne­ro con su teo­ría cons­pi­ra­ti­va de ca­be­ce­ra. En­ton­ce­s, una vez que en­tien­da cuan­to di­ne­ro es es­to­...

  1. Tam­bién creés que lo te­nían/­tie­nen guar­da­do en una bó­ve­da en la ca­sa?
  2. Tam­bién creés que la va­li­ja de An­to­ni­ni Wil­son con 800.000 dó­la­res les im­por­ta­ría?
  3. Tam­bién creés que lo sa­ca­ron en va­li­jas en avio­nes de lí­nea por arre­glos con la adua­na?
  4. Tam­bién creés que ne­ce­si­tan que Báez les pa­gue un mi­llon­ci­to de pe­sos por al­go?

No es­pe­ro que na­die aban­do­ne sus teo­ría­s. Tan só­lo me con­for­mo con que me­jo­ren su con­sis­ten­cia in­ter­na.

Diego Sarmentero: MagneBot Released!

   Publicado: Finally!! MagneBot is here!!

My first steps in Indie Game development :D

It's a really simple game (simple as in simplicity), but difficult to play to test your reaction skills!


You can get it from:

"Join MagneBot on this crazy surfing experience through the space station!

What is at the end of the tunnel in this space station?
No one knows, but you can be sure that MagneBot is going to try to find out!!

Help MagneBot collect as much power as you can, but be careful, nothing is as easy as it looks.
Also... Why is MagneBot in his underwear? Dude, get some pants!"








    Martín Gaitán: Sergio Massa y #LaGente

       Publicado:

    Anoche me reí mucho con el hashtag #LaGente, que se viralizó mientras Alejandro Fantino entrevistaba, una vez más, al inefable candidato presidencial Sergio Massa.

    Me acordé entonces de un post de Zulko, cuyo blog es un compilado de gemas ñoñamente divertidas. Allí muestra cómo recortar automáticamente los pedacitos de un video que mencionen una palabra o frase, basándose en las marcas de tiempo del archivo de subtítulos, utilizando su maravillosa biblioteca Moviepy y un poco de Python. Más o menos lo que hace videogrep, pero más prolijo.

    La herramienta youtube-dl (que también es genial y hecha en Python), permite no sólo bajar videos de youtube y los subtitulos existentes, sino que también puede bajar el "subtitulo automático". En general son bastante malos pero es suficientemente efectivo para encontrar pequeñas frases.

    Todo sea por "la gente": manos a la obra

    Lo primero que necesitamos es una lista de videos donde Sergio Massa hable. Hice una búsqueda, decidí ignorar algunos (parodias, por ejemplo) y generé una lista. Hay varias maneras de obtener este listado de las primeras paginas de resultados, yo utilicé el rústico y efectivo webscrapping:

    In [1]:
    from pyquery import PyQuery
    links = []
    skip = ('M0yuFHbhYLY','TLmMh9Qvmic', 'rY4Hwvn6GlA')
    
    for page in range(1, 5):
        pq = PyQuery('https://www.youtube.com/results?search_query=entrevista+sergio+massa&page=%s' % page)
        pq.make_links_absolute()
        links.extend([pq(a).attr('href') for a in pq('a.yt-uix-tile-link') if pq(a).attr('href').split('v=')[1] not in skip])
    links
    
    Out[1]:
    ['https://www.youtube.com/watch?v=8pP8G3fSAcY',
     'https://www.youtube.com/watch?v=g6QSwxUo1aw',
     'https://www.youtube.com/watch?v=_9FN6CI8fD4',
     'https://www.youtube.com/watch?v=5wqwNDpkZOo',
     'https://www.youtube.com/watch?v=V865E4mBiHU',
     'https://www.youtube.com/watch?v=TPrGNJnMS9U',
     'https://www.youtube.com/watch?v=SVTl11hG9Gs',
     'https://www.youtube.com/watch?v=Df_dwb5XHQM',
     'https://www.youtube.com/watch?v=sptBkyfq1VU',
     'https://www.youtube.com/watch?v=tzjz1xrNu3k',
     'https://www.youtube.com/watch?v=k-CGbuOo8do',
     'https://www.youtube.com/watch?v=_L-B_wHsEec',
     'https://www.youtube.com/watch?v=iFOABIQdo9Q',
     'https://www.youtube.com/watch?v=WOlRIKGrBWY',
     'https://www.youtube.com/watch?v=a-mCgN6W9ek',
     'https://www.youtube.com/watch?v=x5vhchv3zAY',
     'https://www.youtube.com/watch?v=bi5eK7i59w0',
     'https://www.youtube.com/watch?v=VNHV3D_6o4E',
     'https://www.youtube.com/watch?v=MWVZ6JDU9V8',
     'https://www.youtube.com/watch?v=v-JmdgVZqVc',
     'https://www.youtube.com/watch?v=FBFHpdxsyYU',
     'https://www.youtube.com/watch?v=WXmTc83l1sQ',
     'https://www.youtube.com/watch?v=GfNgds5vS60',
     'https://www.youtube.com/watch?v=UHRa34A6rDg',
     'https://www.youtube.com/watch?v=xVU-EjnuksU',
     'https://www.youtube.com/watch?v=-IXymTZZM6o',
     'https://www.youtube.com/watch?v=tzvwDTPyTHQ',
     'https://www.youtube.com/watch?v=a19z6EVWpQ4',
     'https://www.youtube.com/watch?v=rAOvF8X_nzM',
     'https://www.youtube.com/watch?v=wtvl4esdMGU',
     'https://www.youtube.com/watch?v=1YPHDDH1Az0',
     'https://www.youtube.com/watch?v=w7TnghsrJUo',
     'https://www.youtube.com/watch?v=qBT-6HpSrwc',
     'https://www.youtube.com/watch?v=JM-xblTxLGc',
     'https://www.youtube.com/watch?v=kMymsVsmETY',
     'https://www.youtube.com/watch?v=K1-dfiVfbOI',
     'https://www.youtube.com/watch?v=VnoiHVlR-So',
     'https://www.youtube.com/watch?v=hMTzJyLiXE4',
     'https://www.youtube.com/watch?v=VGQPNQ1Bhkg',
     'https://www.youtube.com/watch?v=0oR4z7SsY14',
     'https://www.youtube.com/watch?v=Cl4r8h_Hlak',
     'https://www.youtube.com/watch?v=gJFmek-YgYo',
     'https://www.youtube.com/watch?v=9VQ7Ov5W_tM',
     'https://www.youtube.com/watch?v=rKwKImVrYu4',
     'https://www.youtube.com/watch?v=LJwj9SHC9EU',
     'https://www.youtube.com/watch?v=-08OEpFThiw',
     'https://www.youtube.com/watch?v=BPJBl5y2P2g',
     'https://www.youtube.com/watch?v=MvkXlg9ZbL4',
     'https://www.youtube.com/watch?v=7KgIa4fX_Ng',
     'https://www.youtube.com/watch?v=upNLrHtzeBI',
     'https://www.youtube.com/watch?v=Y-norf1BKAs',
     'https://www.youtube.com/watch?v=QMvAl_fxQSA',
     'https://www.youtube.com/watch?v=3os_uXUOvcM',
     'https://www.youtube.com/watch?v=ZE_aChIEELo',
     'https://www.youtube.com/watch?v=iKI-8ceuR-A',
     'https://www.youtube.com/watch?v=CASdYLquQII',
     'https://www.youtube.com/watch?v=5cvyi1CcpYs',
     'https://www.youtube.com/watch?v=NVEw-YIAy5A',
     'https://www.youtube.com/watch?v=yMXn04-GQTY',
     'https://www.youtube.com/watch?v=RCCzZGcGg5k',
     'https://www.youtube.com/watch?v=FqMFKGsXLOE',
     'https://www.youtube.com/watch?v=MVOvQb8KBm0',
     'https://www.youtube.com/watch?v=ENvWfMwnJ_0',
     'https://www.youtube.com/watch?v=bs7xGm293Vs',
     'https://www.youtube.com/watch?v=7OvrK-U-axI',
     'https://www.youtube.com/watch?v=VHeWqPqs4vo',
     'https://www.youtube.com/watch?v=nVOEi9FESn8',
     'https://www.youtube.com/watch?v=eikTWAvFwTE',
     'https://www.youtube.com/watch?v=BU2amn3QdWk',
     'https://www.youtube.com/watch?v=GiB1pOuEvqg',
     'https://www.youtube.com/watch?v=GAPN17lTJ9c',
     'https://www.youtube.com/watch?v=4Ja1uZbMM8E',
     'https://www.youtube.com/watch?v=F1dAfCR4rc0',
     'https://www.youtube.com/watch?v=334O9xh-CQY',
     'https://www.youtube.com/watch?v=KgNmw3sJ0g8',
     'https://www.youtube.com/watch?v=-SQSue4-PLk',
     'https://www.youtube.com/watch?v=HPE4PHlYySo']
    

    Luego, el paso lento: bajar los videos. Al parecer, Youtube no genera un subtitulo automático para videos demasiado largo, así que limité hasta 30 minutos.

    In [2]:
    for link in links:
        !youtube-dl --write-auto-sub --sub-lang es --max-filesize 30.00m {link}
    

    Con el material crudo disponible (aunque puede ser que no se hayan encontrado subtitulos para todos los videos), podemos copiar descaradamente partes del código de Zulko (levemente adaptado)

    In [3]:
    import re
    import os
    import glob
    import random
    from moviepy.editor import VideoFileClip, concatenate, TextClip, CompositeVideoClip
    
    
    def convert_time(timestring):
        """ Converts a string into seconds """
        nums = [float(t) for t in re.findall(r'\d+', timestring)]
        return 3600 * nums[0] + 60*nums[1] + nums[2] + nums[3]/1000
    
    
    def get_time_texts(file):
        with open(file) as f:
            lines = f.readlines()
    
        times_texts = []
        current_times , current_text = None, ""
        for line in lines:
            times = re.findall("[0-9]*:[0-9]*:[0-9]*,[0-9]*", line)
            if times != []:
                current_times = [convert_time(t) for t in times]
            elif line == '\n':
                times_texts.append((current_times, current_text))
                current_times, current_text = None, ""
            elif current_times is not None:
                current_text = current_text + line.replace("\n"," ")
        return times_texts
    
    def find_word(word, times_texts, padding=.4):
        """ Finds all 'exact' (t_start, t_end) for a word """
        matches = [re.search(word, text)
                   for (t,text) in times_texts]
        return [(t1 + m.start()*(t2-t1)/len(text) - padding,
                 t1 + m.end()*(t2-t1)/len(text) + padding)
                 for m,((t1,t2),text) in zip(matches, times_texts)
                 if (m is not None)]
    
    
    def get_subclips(video_path, cuts):
        video = VideoFileClip(video_path)
        return [video.subclip(start, end) for (start,end) in cuts]
    
    
    def get_all_subclips_for(word, pattern='*.mp4', sub_ext='.es.srt', shuffle=True):
        subclips = []
        for mp4 in glob.glob(pattern):
            sub = os.path.splitext(mp4)[0] + sub_ext
            try:
                times = find_word(word, get_time_texts(sub))
            except IOError:
                # ignore video if it hasn't subtitle
                continue
            cuts = get_subclips(mp4, times)
            subclips.extend(cuts)
        if shuffle:
            random.shuffle(subclips)
        return subclips
    

    La función get_all_subclip recibe la frase a buscar y devuelve un listado de segmentos donde, muy probablemente, se pronuncia.

    In [4]:
    gente = get_all_subclips_for('la gente')
    len(gente)
    
    Out[4]:
    77
    

    El problema es que aunque es muy probable que sea Sergio Massa el que diga "la gente" en sus entrevistas, a veces es el entrevistador, a veces youtube entendió mal al desgrabar y a veces el código recortador la pifia. Por este motivo hay que descartar los segmentos que no sirven.

    Se me ocurrió hacerlo visualmente: los pegué todos, superponiendo el índice al que corresponde cada segmento, para luego anotar los que no sirven y filtrarlos en otra pasada.

    In [5]:
    def make_preview(subclips):
        subclips_ = []
        for (i, clip) in enumerate(subclips):
            txt_clip = TextClip(str(i),fontsize=70, color='white')
            txt_clip = txt_clip.set_pos('center').set_duration(clip.duration)
            clip = CompositeVideoClip([clip, txt_clip])
            subclips_.append(clip)
    
        final = concatenate(subclips_, method='compose')
        final.write_videofile('preview.webm', codec='libvpx', fps=24)
    
    In [6]:
    make_preview(gente)
    
    [MoviePy] >>>> Building video preview.webm
    [MoviePy] Writing audio in previewTEMP_MPY_wvf_snd.ogg
    [MoviePy] Done.
    [MoviePy] Writing video preview.webm
    [MoviePy] Done.
    [MoviePy] >>>> Video ready: preview.webm
    
    
    

    El resultado me permitió hacer el tamizado

    In [7]:
    ignore = [2, 3, 8, 12, 17, 19, 25, 28, 32, 36, 38, 40, 41, 44, 49, 55, 56, 61, 62, 66, 73, 74]
    subclips_cleaned = [i for j, i in enumerate(gente) if j not in ignore]
    

    Aunque no tengo idea de edición de videos, y porque de verdad creo que es un demamogo impresentable que no debería presidir ni una junta vecinal, quería darle un toque final, con una pequeña frase

    In [8]:
    import numpy as np
    from moviepy.video.tools.segmenting import findObjects
    
    def arrive(screenpos,i,nletters):
        v = np.array([-1,0])
        d = lambda t : max(0, 3-3*t)
        return lambda t: screenpos-400*v*d(t-0.2*i)
    
    screensize = (640,360)
    txtClip = TextClip('Yn tragr ab rf obyhqn'.decode('rot13'), color='white', font="Amiri-Bold", kerning=5, fontsize=50)
    cvc = CompositeVideoClip( [txtClip.set_pos('center')],
                            size=screensize)
    
    letters = findObjects(cvc) # a list of ImageClips
    
    def moveLetters(letters, funcpos):
        return [ letter.set_pos(funcpos(letter.screenpos,i,len(letters)))
                  for i,letter in enumerate(letters)]
    
    ending = CompositeVideoClip(moveLetters(letters, arrive), size=screensize).subclip(0, 10)
    
    In [9]:
    # le damos una mezcladita más
    random.shuffle(subclips_cleaned)
    subclips_cleaned.append(ending)
    make_final(subclips_cleaned, 'massa_lagente_final.webm')
    
    [MoviePy] >>>> Building video massa_lagente_final.webm
    [MoviePy] Writing audio in massa_lagente_finalTEMP_MPY_wvf_snd.ogg
    [MoviePy] Done.
    [MoviePy] Writing video massa_lagente_final.webm
    [MoviePy] Done.
    [MoviePy] >>>> Video ready: massa_lagente_final.webm
    
    
    

    Y este es el resultado:

    In [ ]:

    Facundo Batista: Metaclasses in Python, the easy way (a real life example)

       Publicado:


    They say that metaclasses make your head explode. They also say that if you're not absolutely sure what are metaclasses, then you don't need them.

    And there you go, happily coding through life, jumping and singing in the meadow, until suddenly you get into a dark forest and find the most feared enemy: you realize that some magic needs to be done.


    The necessity

    Why you may need metaclasses? Let's see this specific case, my particular (real life) experience.

    It happened that at work I have a script that verifies the remote scopes service for the Ubuntu Phone, checking that all is nice and crispy.

    The test itself is simple, I won't put it here because it's not the point, but it's isolated in a method named _check, that receives the scope name and returns True if all is fine.

    So, the first script version did (removed comments and docstrings, for brevity):

        class SuperTestCase(unittest.TestCase):

            def test_all_scopes(self):
                for scope in self._all_scopes:
                    resp = self._check(scope)
                    self.assertTrue(resp)

    The problem with this approach is that all the checks are inside the same test. If one check fails, the rest is not executed (because the test is interrupted there, and fails).

    Here I found something very interesting, the (new in Python 3) subTest call:

        class SuperTestCase(unittest.TestCase):

            def test_all_scopes(self):
                for scope in self._all_scopes:
                    with self.subTest(scope=scope):
                        resp = self._check(scope)
                        self.assertTrue(resp)

    Now, each "sub test" internally is executed independently of the other. So, they all are executed (all checks are done) no matter if one or more fail.

    Awesome, right? Well, no.

    Why not? Because even if internally everything is handled as independent subtest, from the outside point of view it still is one single test.

    This has several consequences. One of those is that the all-inside test takes too long, and you can't know what was going on (note that each of these checks hit the network!), as the test runner just show progress per test (not subtest).

    The other inconvenient is that there is not a way to call the script to run only one of those subtests... I can tell it to execute only the all-inside test, but that would mean to execute all the subtests... which, again, takes a lot of time.

    So, what I really needed? Something that allows me to express the assertion in one test, but that in reality it were several methods. So, I needed something that, from a single method, reproduce them so the class actually had several ones. This is, write code for a class that Python would find different. This is, metaclasses.


    Metaclasses, but easy

    Luckily, since a couple of years ago (or more), Python provides a simpler way to achieve the same that could be done with metaclasses. This is: class decorators.

    Class decorators, very similar to method decorators, receive the class that is defined below itself, and its response is considered by Python the real definition of the class. If you don't have the concept, you may read a little here about decorators, and a more deep article about decorators and metaclasses here, but it's not mandatory.

    So, I wrote the following class decorator (explained below):

        def test_multiplier(klass):
            """Multiply those multipliable tests."""
            for meth_name in (x for x in dir(klass) if x.startswith("test_")):
                meth = getattr(klass, meth_name)
                argspec = inspect.getfullargspec(meth)

                # only get those methods that are to be multiplied
                if len(argspec.args) == 2 and len(argspec.defaults) == 1:
                    param_name = argspec.args[1]
                    mult_values = argspec.defaults[0]

                    # "move" the usefult method to something not automatically executable
                    delattr(klass, meth_name)
                    new_meth_name = "_multiplied_" + meth_name
                    assert not hasattr(klass, new_meth_name)
                    setattr(klass, new_meth_name, meth)
                    new_meth = getattr(klass, new_meth_name)

                    # for each of the given values, create a new method which will call the given method
                    # with only a value at the time
                    for multv in mult_values:
                        def f(self, multv=multv):
                            return new_meth(self, **{param_name: multv})

                        meth_mult_name = meth_name + "_" + multv.replace(" ", "_")[:30]
                        assert not hasattr(klass, meth_mult_name)
                        setattr(klass, meth_mult_name, f)

            return klass

    The basics are: it receives a class, it returns a slightly modified class ;). For each of the methods that starts with "test_", I checked those that had two args (not only 'self'), and that the second argument were named.

    So, it would actually get the method defined in the following structure and leave the rest alone:

        @test_multiplier
        class SuperTestCase(unittest.TestCase):

            def test_all_scopes(self, scope=_all_scopes):
                resp = self.checker.hit_search(scope, '')
                self.assertTrue(resp)

    For that kind of method, the decorator will move it to something not named "test_*" (so we can call it but it won't be called by automatic test infrastructure), and then create, for each value in the "_scopes" there, a method (with a particular name which doesn't really matter, but needs to be different and is nice to be informative to the user) that calls the original method, passing "scope" with the particular value.

    So, for example, let's say that _all_scopes is ['foo', 'bar']. Then, the decorator will rename test_all_scopes to _multiplied_test_all_scopes, and then create two new methods like this::

        def test_all_scopes_foo(self, multv='foo'):
            return self._multiplied_test_all_scopes(scope=multv)

        def test_all_scopes_foo(self, multv='bar'):
            return self._multiplied_test_all_scopes(scope=multv)

    The final effect is that the test infrastructure (internally and externally) finds those two methods (not the original one), and calls them. Each one individually, informing progress individually, the user being able to execute them individually, etc.

    So, at the end, all gain, no loss, and a fun little piece of Python code :)

    Manuel Kaufmann (Humitos): PyDay en Asunción, Paraguay

       Publicado:

    Hace un poco más de una semana que estamos en Paraguay con @EllaQuímica y hemos conocido gente hermosa a través de las calles, la comunidad de CouchSurfing y también Python. Nuestro primer contacto por estos lares fue Javier, con quien hablé en la Python Conference Argentina 2014 y quedamos de juntarnos si es que mi viaje me llevaría por Asunción.

    Una vez cerca de Asunción, me puse en contacto con él y también hice un poco de ruido en diferentes listas de correo de la zona (MiSol y Python Argentina) donde me pusieron en contacto con Mauro también. Así, se fue sumando gente y hoy somos un grupo de 13 personas con muchas ganas de formar (y formando) una comunidad "Python Paraguay".

    organizadores.thumbnail.jpeg

    Parte del comitée de organizadores

    Como primer evento de esta comunidad emergente, estamos organizando, junto a SENATICs, un #PyDay en Asunción, Paraguay el Sábado 28 de Marzo desde las 9:30hs en L'Office Santos, ubicado en Gral. Santos 1170.

    Difusión del evento y de la comunidad "Python Paraguay":

    Facundo Batista: Novedades pythónicas: fades, CDPedia, Django y curso

       Publicado:


    Algunas, varias y sueltas.

    A nivel de proyectos, le estuvimos metiendo bastante con Nico a fades. La verdad es que la versión 2 que sacamos la semana pasada está piolísima... si usás virtualenvs, no dejes de pegarle una mirada.

    Otro proyecto con el que estuve es CDPedia... la parte de internacionalización está bastante potable, y eso también me llevó a renovar la página principal que te muestra cuando la abrís, así que puse a tirar una nueva versión de la de español, y luego seguirá una de portugués (¡cada imagen tarda como una semana!).

    Hace un rato subí a la página de tutoriales de Python Argentina el Tutorial de Django en español (¡gracias Matías Bordese por el material!). Este tuto antes estaba en un dominio que ahora venció, y nos pareció interesante que esté todo en el mismo lugar, facilita que la gente lo encuentre.

    Finalmente, empecé a organizar mi Segundo Curso Abierto de Python. Esta vez lo quiero hacer por la zona de Palermo, o alrededores (la vez pasada fue en microcentro). Todavía no tengo reservado un lugar, y menos fechas establecidas, pero el formato va a ser similar al anterior. Con respecto al sitio, si alguien conoce un buen lugar para alquilar "aulas", me avisa, :)

    Manuel Kaufmann (Humitos): Verduras y precios paraguayos

       Publicado:

    Más allá de todo lo nuevo que tiene un país diferente a donde uno ha nacido, sus costumbre, sus ideas y su cultura en general. También está el tema de los precios y su moneda.

    Es un poco complicado al principio acostumbrarse a una moneda en donde todo se maneja en miles y por otro lado todo el mundo es millonario (el salario mínimo es de 1.800.000 gs) ;)

    Además, para nosotros, que llevamos moneda Argentina todo nos sale mucho más caro. Así que, hacer esa conversión de Guaraníes, Dólares y luego a Pesos es bastante frustrante. Más vale pagar con los ojos cerrados y disfrutar de todos los días. Pero claro, ¿cuánto tiempo puede durar esa dinámica? Un poco hay que pensar, por lo menos, no comer nada de lo que es extremadamente caro: ¡Lechuga!.

    ¿Quién hubiera pensado que la lechuga común iba a ser tan cara en algún momento?

    Para poder hacer un análisis de qué cosas nos conviene comer / comprar, empecé a tomar algunas fotografías de los precios de las frutas y las verduras en el Supermecado Stock de Paraguay:

    IMG_20150304_194103.thumbnail.jpg

    Pimiento, repollo, coliflor, cebolla colorada, zapallo, remolacha, etc

    IMG_20150304_194116.thumbnail.jpg

    Puerro, achicoria, apio, espinaca, remedio, cebollita de hoja, lechuga, etc

    IMG_20150304_194124.thumbnail.jpg

    Manzana, durazno, mburucuya, melon, uva, etc

    IMG_20150304_194148.thumbnail.jpg

    Mandioca, papa, cebolla, etc

    IMG_20150304_194337.thumbnail.jpg

    Repollo, zanahoria, locote, tomate

    Nota

    Acá hierven la mandioca y la usan como acompañamiento de las carnes, como si fuera un pan. Y comen muchísima mandioca, está por todos lados.

    IMG_20150304_194226.thumbnail.jpg

    Naranja, limón, manzana coleg, banana

    IMG_20150304_194238.thumbnail.jpg

    Mandarina, pomelo, batata, banana de oro, piña brasil, batata

    Saquen sus propias conclusiones y, si llegan a algún algoritmo para reducir los gastos, me avisan que lo implementamos de toque.

    Share