Jekyll2021-02-10T19:17:53-03:00https://matiasb.github.io/feed.xmlC@rPeDi3mMatias Bordese, Python and Django developer, everyday hacker.Matias Bordese\#QuedateJugandoEnCasa2021-02-10T10:00:00-03:002021-02-10T10:00:00-03:00https://matiasb.github.io/es/quedate-jugando-en-casa<p>Algunas opciones para jugar con amigos, cada cual desde su casa, “casi” como estando en el mismo lugar. La nueva normalidad, dicen.</p>
<hr />
<p>En el último año, y dadas las circunstancias ampliamente conocidas, hemos tenido la oportunidad de probar distintas alternativas para jugar con amigos, manteniendo la distancia (además de servir de excusa para reencontrarnos con gente lejana!). Aquí un breve repaso de las que mejor nos funcionaron. Vale aclarar que las opciones listadas corren seguro en Linux, pero también deberían funcionar bajo Windows.</p>
<h4 id="dominion"><a href="https://dominion.games">Dominion</a></h4>
<p>Implementación oficial del juego de mesa, muy buena, y en constante evolución. Basta registrarse, gratis, para acceder al set de cartas base. Hay dos niveles de suscripción para habilitar extensiones (que son muchas). También existe una comunidad en Discord alrededor del juego, bastante activa, que organiza campeonatos permanentemente (<a href="https://dominionleague.org/player_database?player=matiasb">participé una vuelta</a>, no me fue tan bien :-P).
La interfaz está en varios idiomas, la versión en castellano está en <a href="http://"https://dominion.games/?lang=es"">fase beta</a>.</p>
<h4 id="jcloisterzone"><a href="https://jcloisterzone.com/en/">JCloisterZone</a></h4>
<p>Implementación open-source (en Java) de Carcassone, con todas sus expansiones. Automatiza la aplicación de las reglas, y lleva los puntajes. La interfaz de juego es simple pero a la vez hace fácil jugar el juego como en la vida misma, con el plus de que resuelve las tareas tediosas. Se puede jugar online con el server por defecto (se satura a veces), o levantar una instancia como host y hacer que el resto se conecte a la misma. Recomendable, tanto el juego (que está bueno como introducción a los juegos de mesa), como esta implementación.</p>
<h4 id="pioneers"><a href="http://pio.sourceforge.net/">Pioneers</a></h4>
<p>Implementación open-source del Settlers de Catan, otro juego de mesa popular y relativamente simple de aprender a jugar. Para jugar online hace falta que alguien corra el juego como servidor (y abrir algún puerto). La interfaz es sencilla, no super amigable (el comerciar materiales es difícil de entender a primera vista), pero automatiza lo que hace falta resolver y provee distintas variantes. En cualquier caso está bastante completa y funciona bien, menos ostentosa (pero gratis/libre) que la <a href="https://catanuniverse.com/en/game/">implementación oficial</a>.</p>
<h4 id="tetrio"><a href="https://tetr.io/">tetr.io</a></h4>
<p>Una versión para jugar online al Tetris, con distintas modalidades tanto para jugar solo como multijugador (en modo <em>last-man standing</em>). Se actualiza frecuentemente, con muchas opciones, también tiene un cliente de escritorio. Recomendable para los fanáticos de los tetrominos que caen. Busco rivales, mi usuario: matiasb.</p>
<h4 id="netgamesio"><a href="https://netgames.io/">netgames.io</a></h4>
<p>Varios juegos simples, pensados para jugar desde el teléfono aunque compatibles con un navegador en el escritorio también. Interfaz bien sencilla (sólo en inglés), pero funcional. Permite jugar algún juego rápido para entrar en calor antes de algo más largo.</p>
<h4 id="drawful-2"><a href="https://www.jackboxgames.com/drawful-two/">Drawful 2</a></h4>
<p>El único pago de la lista. Juego para dibujar mediante el teléfono mientras el resto intenta adivinar, en modo todos contra todos. Disponible en Steam, se puede jugar online si se comparte la pantalla usando algún sistema de videollamadas. Existen sets de palabras/frases personalizados (lo que se llaman episodios), aunque no hay mucha variedad en español (alguien está haciendo la <a href="https://github.com/rmatelec/drawful-2-ES">traducción del set oficial</a>). Divertido, y partidas cortas.</p>
<h4 id="pes-6"><a href="https://en.wikipedia.org/wiki/Pro_Evolution_Soccer_6">PES 6</a></h4>
<p>Para aquellos que nos gustan los juegos de fútbol y no tenemos la última consola o la PC más potente, está la opción de levantar un <a href="https://sites.google.com/site/fiveservercom/home">servidor compatible</a> (hay algunos públicos dando vueltas también) con el PES6 (para el que existen varios parches que lo “actualizan”). Anda muy bien! Y además, el PES6 corre sin problemas usando wine.</p>
<h4 id="board-game-arena"><a href="https://boardgamearena.com/">Board Game Arena</a></h4>
<p>Plataforma para jugar juegos de mesa online. Con sólo registrarnos tenemos acceso a una gran variedad de juegos, aunque algunos requieren una suscripción. La interfaz es simple, en múltiples idiomas. Los juegos más populares o conocidos suelen requerir una cuenta premium, pero si no tenemos drama en jugar con desconocidos podemos sumarnos a la partida de otro (es decir, es necesario que sólo uno de los jugadores pague la suscripción para poder jugar a los juegos premium). Hemos jugado Carcassone (premium), Saboteurs, Love Letter (premium), Cinco.</p>
<p>Alternativamente, hay algunas otras plataformas para juegos de mesa con la diferencia de que lo que proveen es un motor para simular la física de los juegos, sin automatizar tanto ni aplicar reglas, y la “sensación” se aproxima más a jugar el juego en la vida real (como ser <a href="https://tabletopia.com/">tabletopia</a>, o <a href="https://store.steampowered.com/app/286160/Tabletop_Simulator/">tabletop simulator</a>).</p>
<h4 id="steam-remote-play"><a href="https://store.steampowered.com/remoteplay">Steam Remote Play</a></h4>
<p>Steam permite compartir un juego con multijugador local vía internet, haciendo streaming de nuestra instancia del juego y habilitando a nuestros amigos a interactuar desde su lugar. Basta que quien hace de host de la partida tenga y corra el juego, y hace falta una buena internet (además de algunos puertos abiertos).</p>
<p>Quién juega?</p>Matias BordeseJugando con amigos, cada uno desde su casaDe .py a PyPI2020-08-18T10:00:00-03:002020-08-18T10:00:00-03:00https://matiasb.github.io/es/de.py-a-pypi<blockquote>
<p><small>Post basado en la <a href="https://www.dropbox.com/s/j0ovxbygd30869i/201711-PyConAr-De.py.a.PyPI.pdf?dl=0">charla del mismo nombre (PyConAr 2017)</a>, actualizado a 2020</small></p>
</blockquote>
<p>Tenemos nuestro script, módulo o paquete Python que consideramos útil y queremos compartirlo con el mundo. Para ello necesitamos seguir una serie de pasos de tal manera que todo aquel al que le resulte interesante nuestro proyecto, pueda obtenerlo, instalarlo y usarlo. Y por qué no, eventualmente contribuir para mejorarlo.</p>
<p>Independientemente del lenguaje, la necesidad de reusar código es un escenario usual: nos ahorra escribir el mismo código múltiples veces y/o le sirve a otros para resolver un determinado problema y poder concentrarse en lo que realmente quieren construir. Incluso en un mismo proyecto es útil a nivel organizativo, permitiendo mantener cada parte por separado.</p>
<p>En este post, la idea es contar, a grandes rasgos, cómo empaquetar código (principalmente librerías o herramientas), para su distribución a través de <code class="language-plaintext highlighter-rouge">pip</code> y <a href="#pypi">PyPI</a>. Nótese que los usuarios de un paquete en PyPI van a necesitar Python y <code class="language-plaintext highlighter-rouge">pip</code>, y por lo tanto no es el método ideal para distribuir una aplicación independiente (hay otras herramientas para esto, buscar por ejemplo sobre pyinstaller o py2exe).</p>
<hr />
<h2 id="algunas-definiciones">Algunas definiciones</h2>
<h4 id="paquete">paquete</h4>
<p>En Python la palabra <em>paquete</em> tiene distintos significados según el contexto:</p>
<ol>
<li>m. archivo que agrupa paquetes, módulos y otros recursos para ser distribuidos como un todo. Es lo que el usuario final va a obtener e instalar (<em>distribution package</em>).</li>
<li>m. espacio de nombres que contiene otros módulos o paquetes (<em>import package</em>).</li>
</ol>
<p>A lo largo de este artículo nos concentraremos en los paquetes de la primera definición.</p>
<h4 id="pypi">PyPI</h4>
<p>Sigla, en inglés, para Python Package Index (Índice de Paquetes Python). Es el nombre del repositorio oficial. Cuenta con una interfaz web para la búsqueda y obtención de información de los paquetes registrados. Se encuentra en <a href="https://pypi.org">https://pypi.org</a>.</p>
<p>No confundir con <a href="https://www.pypy.org/">PyPy</a>!</p>
<h4 id="formatos-de-paquete">Formatos de paquete</h4>
<p><strong>Source distribution (sdist)</strong>
Se trata básicamente de un archivo <code class="language-plaintext highlighter-rouge">.tar.gz</code> que provee la metadata y los archivos fuente necesarios para generar un paquete ‘built’ (ver abajo). Permite la instalación en cualquier plataforma (si las dependencias están disponibles, claro).</p>
<p><strong>Built distribution (wheel)</strong>
Es un <code class="language-plaintext highlighter-rouge">.zip</code> (de extensión <code class="language-plaintext highlighter-rouge">.whl</code>) que contiene los archivos y metadata ya “buildeados” (en particular para aquellos paquetes que usan extensiones que requieren un paso de compilación), de tal forma que solamente se requiere moverlos a la ubicación adecuada en el sistema de archivos para su instalación.</p>
<p>Instalar un wheel es notablemente más rápido que un sdist. La contra es que puede hacer falta crear <a href="https://pypi.org/project/numpy/#files">varios wheels</a> para cubrir las distintas plataformas.</p>
<p>Al momento de instalación, si <code class="language-plaintext highlighter-rouge">pip</code> no encuentra un wheel para tu sistema, va a intentar obtener el sdist, armar el wheel particular para el caso, e instalarlo.</p>
<h2 id="herramientas">Herramientas</h2>
<p>Por lo general uno instalará estas herramientas en el entorno en el que desarrolla el proyecto. Si antes de cada release corremos este comando, nos aseguraremos de tener las versiones más recientes para la tarea:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>pip <span class="nb">install</span> <span class="nt">-U</span> pip setuptools wheel twine
</code></pre></div></div>
<p><a href="https://pip.pypa.io/en/stable/"><code class="language-plaintext highlighter-rouge">pip</code></a> nos permite instalar paquetes; <a href="https://setuptools.readthedocs.io/en/latest/"><code class="language-plaintext highlighter-rouge">setuptools</code></a>, armarlos; <a href="https://wheel.readthedocs.io/en/stable/index.html"><code class="language-plaintext highlighter-rouge">wheel</code></a>, construir el paquete homónimo; y <a href="https://twine.readthedocs.io/en/latest/"><code class="language-plaintext highlighter-rouge">twine</code></a>, subirlo y publicarlo en PyPI.</p>
<hr />
<h2 id="estructura-usual-de-un-paquete">Estructura usual de un paquete</h2>
<p>La siguiente no es la única forma de organizar los archivos de un paquete a distribuir, pero es una posible, y frecuente.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dist-package-name/
docs/
package_name/
__init__.py
module.py
tests/
__init__.py
test_module.py
CHANGELOG
CONTRIBUTING
HACKING
LICENSE
MANIFEST.in
README.md
requirements.txt
setup.py
</code></pre></div></div>
<p>Para la estructura descripta, nuestro paquete se instalaría de la siguiente manera:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>dist-package-name
</code></pre></div></div>
<p>y se importaría como:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">package_name</span>
</code></pre></div></div>
<p>Un ejemplo de un paquete simple que sigue este esquema es <a href="https://github.com/el-ega/fpt-cli">fpt-cli</a>.</p>
<h3 id="setuppy"><code class="language-plaintext highlighter-rouge">setup.py</code></h3>
<p>Es el archivo más importante a la hora de empaquetar. El principal objetivo de este módulo es llamar a la función <code class="language-plaintext highlighter-rouge">setup()</code> de <code class="language-plaintext highlighter-rouge">setuptools</code>, que toma por argumentos los detalles específicos que describen a nuestro paquete.</p>
<p>Un ejemplo mínimo de cómo podría lucir nuestro <code class="language-plaintext highlighter-rouge">setup.py</code> sería:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">setuptools</span> <span class="kn">import</span> <span class="n">setup</span><span class="p">,</span> <span class="n">find_packages</span>
<span class="n">setup</span><span class="p">(</span>
<span class="n">name</span><span class="o">=</span><span class="s">'dist-package-name'</span><span class="p">,</span>
<span class="n">version</span><span class="o">=</span><span class="s">'1.1'</span><span class="p">,</span>
<span class="n">packages</span><span class="o">=</span><span class="n">find_packages</span><span class="p">(),</span>
<span class="p">)</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">packages</code> lista los <code class="language-plaintext highlighter-rouge">import packages</code> (en nuestro caso, <code class="language-plaintext highlighter-rouge">['package_name']</code>) a incluir en el <code class="language-plaintext highlighter-rouge">distribution package</code>. En lugar de listar cada paquete a mano, usamos <code class="language-plaintext highlighter-rouge">find_packages()</code> que lo calcula automáticamente por nosotros.</p>
<p>Además, una vez que tenemos nuestro <code class="language-plaintext highlighter-rouge">setup.py</code>, se habilitan una serie de comandos relacionados al empaquetado (que para el propósito de este post casi no vamos a necesitar):</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>python setup.py <span class="nt">--help-commands</span>
</code></pre></div></div>
<p>Volviendo a los parámetros que recibe la función <code class="language-plaintext highlighter-rouge">setup()</code>, veamos cuáles son los más comunes:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">name</code> es el nombre del paquete (<em>distribution package name</em>); puede contener letras, números, <code class="language-plaintext highlighter-rouge">_</code> y <code class="language-plaintext highlighter-rouge">-</code>. Y obviamente, no tiene que estar registrado por alguien más.</li>
<li><code class="language-plaintext highlighter-rouge">version</code> es la versión del paquete (ver <strong><a href="https://packaging.python.org/guides/distributing-packages-using-setuptools/#choosing-a-versioning-scheme">más detalles aquí</a></strong>).</li>
<li><code class="language-plaintext highlighter-rouge">author</code> y <code class="language-plaintext highlighter-rouge">author_email</code> se usan para identificar al autor del paquete.</li>
<li><code class="language-plaintext highlighter-rouge">description</code> es una descripción corta del paquete, de una línea.</li>
<li><code class="language-plaintext highlighter-rouge">long_description</code> es la descripción detallada; se muestra en la página del paquete en PyPI (usualmente se popula con el contenido de <code class="language-plaintext highlighter-rouge">README.md</code>).</li>
<li><code class="language-plaintext highlighter-rouge">long_description_content_type</code> indica el tipo de markup usado en el campo anterior (en este caso, <code class="language-plaintext highlighter-rouge">Markdown</code>).</li>
<li><code class="language-plaintext highlighter-rouge">url</code> la URL a la página de nuestro proyecto (por ejemplo, un link a GitHub, GitLab, o similar).</li>
<li><code class="language-plaintext highlighter-rouge">classifiers</code> permiten definir metadata adicional de nuestro paquete; se espera que al menos se liste las versiones de Python y sistemas operativos soportados, y la licencia. La lista completa de tags está aquí: <a href="https://pypi.org/classifiers/">https://pypi.org/classifiers/</a>.</li>
</ul>
<p>Y hay varios más aún, que se pueden revisar en la <a href="https://pypi.org/classifiers/">respectiva documentación</a>.</p>
<p>Un ejemplo de cómo quedaría un <code class="language-plaintext highlighter-rouge">setup.py</code> más completo, aunque todavía simple, se puede ver <a href="https://github.com/el-ega/fpt-cli/blob/master/setup.py">aquí</a>.</p>
<h3 id="manifestin"><code class="language-plaintext highlighter-rouge">MANIFEST.in</code></h3>
<p>A veces es necesario incluir archivos en nuestro paquete que no se incluyen automáticamente (ej. README). Si además queremos que esos archivos se instalen con nuestro paquete, será necesario pasar <code class="language-plaintext highlighter-rouge">include_package_data=True</code> en nuestra llamada a <code class="language-plaintext highlighter-rouge">setup()</code> (sólo afecta paquetes sdist, habría que usar <code class="language-plaintext highlighter-rouge">package_data</code> en <code class="language-plaintext highlighter-rouge">setup()</code> for bdist).</p>
<p>Un <a href="https://github.com/pypa/sampleproject/blob/master/MANIFEST.in">ejemplo de <code class="language-plaintext highlighter-rouge">MANIFEST.in</code></a>. Para mayores precisiones sobre este tema, conviene revisar la <a href="https://setuptools.readthedocs.io/en/latest/setuptools.html#including-data-files">documentación correspondiente de setuptools</a>.</p>
<h2 id="algunas-recomendaciones">Algunas recomendaciones</h2>
<ul>
<li>Elegir un esquema de versionado (semántico, basado en fechas, secuencial, híbridos)</li>
<li>Siempre liberar bajo alguna licencia! Esto define bajo qué términos un usuario puede usar nuestro paquete. Una vez elegida la licencia, copiar el texto de la misma en el archivo LICENSE. Ante la duda, vale la pena ver <a href="https://choosealicense.com/">https://choosealicense.com/</a></li>
<li>Incluir un README (reStructuredText o markdown, encodeado en UTF-8) que describa de qué se trata nuestro proyecto, y que se muestre en la página del mismo en PyPI y/o en el sitio que hosteemos el código</li>
<li>Al listar las dependencias, idealmente especificar versiones mínimas (que permitan a los usuarios instalar actualizaciones de seguridad)</li>
<li>En lo posible y si tiene sentido, agregar información y documentación adicional (docs, CHANGELOG, CONTRIBUTING, HACKING, etc)</li>
</ul>
<h2 id="empaquetamos">Empaquetamos</h2>
<p>En este punto sólo resta correr este comando en el directorio donde tenemos nuestro <code class="language-plaintext highlighter-rouge">setup.py</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python3 setup.py sdist bdist_wheel
</code></pre></div></div>
<p>(para construir el wheel universal, pasar la opción <code class="language-plaintext highlighter-rouge">--universal</code> al final; nota importante: hoy, universal supone que nuestro paquete es compatible con Python 2 y Python 3… pero, siendo un paquete nuevo, no habría porqué soportar Python 2 :-)).</p>
<p>Después de ver pasar alguna cantidad de texto por la pantalla, al completarse la corrida, deberíamos tener un par de archivos en el directorio <code class="language-plaintext highlighter-rouge">dist</code>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dist/
dist_package_name-1.1-py3-none-any.whl
dist-package-name-1.1.tar.gz
</code></pre></div></div>
<h2 id="llegando-al-público">Llegando al público</h2>
<p>Una buena idea antes de hacer el release oficial es hacerlo en el <a href="https://test.pypi.org">servidor de testing</a>. Para ello hay que registrar un usuario, que será totalmente independiente del servidor real (de hecho cada tanto se resetea el estado del servidor de testing, así que puede ser necesario registrarse otra vez luego de un tiempo).</p>
<p>A continuación agregamos un archivo <code class="language-plaintext highlighter-rouge">~/.pypirc</code> con el siguiente contenido:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[distutils]
index-servers=
test
[test]
repository = https://test.pypi.org/legacy/
username = <nombre de usuario>
</code></pre></div></div>
<p>Ya estamos en condiciones de subir nuestro paquete, usando <code class="language-plaintext highlighter-rouge">twine</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ twine upload -r test dist/dist_package_name-1.1*
</code></pre></div></div>
<p>(con <code class="language-plaintext highlighter-rouge">-r</code> pasamos el nombre del server a utilizar, y luego los archivos correspondientes a los paquetes que creamos).</p>
<p>Si todo anduvo bien, deberíamos poder instalarlo de la siguiente manera (ojo que si tenemos dependencias esto podría fallar porque no todo paquete está subido en el servidor de testing):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ pip install -i https://testpypi.python.org/pypi dist-package-name
</code></pre></div></div>
<p>También podemos confirmar que la descripción en la página del paquete luce bien, y que la metadata del proyecto es la correcta.</p>
<h2 id="hagámoslo-oficial">Hagámoslo oficial</h2>
<p>Después de registrarnos en el PyPI de producción, actualizamos nuestro <code class="language-plaintext highlighter-rouge">~/.pypirc</code> para agregar un nuevo servidor (<code class="language-plaintext highlighter-rouge">pypi</code>):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[distutils]
index-servers=
pypi
test
[test]
repository = https://test.pypi.org/legacy/
username = <nombre de usuario>
[pypi]
username = __token__
</code></pre></div></div>
<p>En este caso vamos a <a href="https://pypi.org/help/#apitoken">configurar un token</a> para hacer los uploads (aunque también podría usarse el nombre de usuario al igual que en el caso anterior), por ser más seguro (podemos definir diferentes nivel de acceso, revocarlos, no exponemos nuestras credenciales).</p>
<p>Para usar el token creado, dejamos el <code class="language-plaintext highlighter-rouge">username</code> definido como se muestra arriba (<code class="language-plaintext highlighter-rouge">__token__</code>), y cuando nos pida el password, introducimos el token que generamos (asegurarse de guardarlo bien, porque no se puede recuperar).</p>
<p>Finalmente:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ twine upload -r pypi dist/dist_package_name-1.1*
</code></pre></div></div>
<p>Y desde este momento, nuestro paquete está a un <code class="language-plaintext highlighter-rouge">pip install</code> de distancia para cualquiera que desee instalarlo!</p>
<h2 id="referencias-et-al">Referencias et al</h2>
<p><a href="https://packaging.python.org/tutorials/distributing-packages/">Python Packaging Guide</a><br />
Documentación oficial de empaquetado en Python</p>
<p><a href="https://github.com/pypa/sampleproject">PyPA sample project</a><br />
Esqueleto de un paquete Python de PyPA (Python Package Authority)</p>
<p><a href="https://github.com/navdeep-G/setup.py">setup.py</a><br />
Ejemplo de <code class="language-plaintext highlighter-rouge">setup.py</code>, con varios patrones y convenciones más avanzados</p>
<h4 id="otras-herramientas">Otras herramientas</h4>
<p><a href="https://github.com/regebro/pyroma">pyroma</a><br />
Evalúa tus habilidades de empaquetado</p>
<p><a href="https://github.com/mgedmin/check-manifest">check-manifest</a><br />
Valida nuestro <code class="language-plaintext highlighter-rouge">MANIFEST.in</code></p>
<p><a href="https://github.com/audreyr/cookiecutter-pypackage">Cookiecutter</a><br />
Cookiecutter template para un paquete Python</p>
<p><a href="https://flit.readthedocs.io/en/latest/">Flit</a><br />
Herramienta para simplificar el subir un paquete a PyPI</p>
<p><a href="https://python-poetry.org/">Poetry</a><br />
Un modelo diferente de empaquetado y manejo de dependencias</p>Matias BordeseUna introducción al empaquetado en PythonGool2019-03-24T11:37:00-03:002019-03-24T11:37:00-03:00https://matiasb.github.io/es/Gool<p>Obviamente no podía faltar el toque ñoño, no sólo de fútbol vive el hombre. Este al menos.</p>
<p>Mientras escribía el <a href="/es/90-minutos#88min">post</a>, noté que la palabra <strong>Gool</strong> de alguna manera se parecía a <strong>Emma</strong> (vicio de computólogo?).</p>
<p>Cómo sería esto? La letra <code class="language-plaintext highlighter-rouge">G</code> en su representación <a href="https://es.wikipedia.org/wiki/ASCII">ASCII</a> corresponde al número 71, mientras que la <code class="language-plaintext highlighter-rouge">o</code> al 111, y la <code class="language-plaintext highlighter-rouge">l</code> al 108. Tanto la <code class="language-plaintext highlighter-rouge">G</code> como la <code class="language-plaintext highlighter-rouge">o</code> están, respectivamente, a distancia 2 de <code class="language-plaintext highlighter-rouge">E</code> (69) y <code class="language-plaintext highlighter-rouge">m</code> (109), aunque de la <code class="language-plaintext highlighter-rouge">l</code> a la <code class="language-plaintext highlighter-rouge">a</code> (97) hay una diferencia de 11 (las distancias también valen si consideramos el alfabeto en inglés, ese que no tiene <code class="language-plaintext highlighter-rouge">ñ</code>).</p>
<p>Hm… y entonces nada que ver? Momento!</p>
<p>Si tomamos esas diferencias (2, 2, 2, 11), claramente(?) distinguimos la <a href="https://es.wikipedia.org/wiki/Factorizaci%C3%B3n_de_enteros">factorización en primos</a> de un número: 2 x 2 x 2 x 11 = 88</p>
<p>Ajá… y con eso qué?</p>
<ul>
<li>La duración normal de un embarazo oscila entre 38-42 semanas</li>
<li>Emma nació pasada la semana 40; quien dice 40 y pico, dice 41</li>
<li>Diviendo 90 (minutos) en 42 (semanas), nos da 2.14 aproximadamente (es decir, cada semana de embarazo equivale a 2.14 minutos)</li>
<li>41 semanas serían 41 x 2.14 = 87.74 minutos, pero haciendo uso de alguna licencia poética, redondeemos a 88</li>
<li>Vimos que 88 factorizado en primos resulta en (2, 2, 2, 11)</li>
<li>Y si a la palabra Gool le “restamos” esos primos, obtenemos…</li>
</ul>
<blockquote>
<p>Emma</p>
</blockquote>
<p>El Gool a los 88 minutos no fue casualidad.</p>
<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="c1">#!/usr/bin/env python
</span><span class="n">gool</span> <span class="o">=</span> <span class="s">'Gool'</span>
<span class="n">primes</span> <span class="o">=</span> <span class="p">[</span><span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">11</span><span class="p">]</span> <span class="c1"># factorización en primos de 88
</span><span class="k">print</span><span class="p">(</span><span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">([</span><span class="nb">chr</span><span class="p">(</span><span class="nb">ord</span><span class="p">(</span><span class="n">ch</span><span class="p">)</span> <span class="o">-</span> <span class="n">i</span><span class="p">)</span> <span class="k">for</span> <span class="p">(</span><span class="n">ch</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">gool</span><span class="p">,</span> <span class="n">primes</span><span class="p">)]))</span>
<span class="c1"># Emma</span></code></pre></figure>Matias BordeseCasualidad?90 minutos2018-09-16T23:59:00-03:002018-09-16T23:59:00-03:00https://matiasb.github.io/es/90-minutos<h3 id="primer-tiempo">Primer tiempo</h3>
<p>Estamos ante un desafío único, una instancia que no fue fácil alcanzar aunque la veníamos buscando, para sorpresa de más de uno. Personalmente, en los primeros segundos de partido me cuesta darme cuenta de que lo estamos consiguiendo. Sin embargo, con el correr de los minutos se hace notar la emoción y el aliento de los primeros hinchas, y con ello se percibe más real lo que está en juego, intensificando una felicidad hasta ese momento contenida.</p>
<p>La cancha en que se desarrolla el encuentro es un escenario desconocido y se siente enorme. En pos de contrarrestar los temores que eso conlleva, el objetivo inicial ha sido mantener el control de la pelota, sin arriesgar. Mi rol, ser la primera opción de pase y encontrar la vuelta para evitar el offside. Así y todo, a veces me levantan el banderín cuando no corresponde! Paciencia. Lo creativo viene por otro lado, y yo estoy para acompañar e intentar simplificar alguna jugada.</p>
<p><img src="/images/2018/09/field.jpeg" alt="" class="align-center" style="max-height: 300px; width: auto;" /></p>
<p><a name="15min"></a></p>
<blockquote>
<p>15’</p>
</blockquote>
<p>Tras un lapso en el que no se destacan acciones trascendentes, necesitamos una señal para confirmar que seguimos en carrera. Y qué mejor que una buena sucesión coordinada de pases en velocidad. No sólo produce fascinación verlo, prestando la suficiente atención, incluso se puede oír: <em>tiki, taka, tiki, taka, tiki, taka</em>. Una sensación maravillosa, casi mágica.</p>
<p><audio controls="">
<source src="/images/2018/09/beat.ogg" type="audio/ogg" />
</audio></p>
<p>Nuestras chances continúan intactas.</p>
<p><a name="30min"></a></p>
<blockquote>
<p>30’</p>
</blockquote>
<p>Transcurrida la primera media hora, teniendo un panorama más claro y habiendo dominado hasta este punto la situación, podemos anunciar que estamos oficialmente pensando y gestando el gol. Una mayor cantidad de público se levanta para acompañar, hay una gran excitación y alegría compartida.</p>
<p>A medida que nos acercamos al final del primer tiempo también vamos aumentado la posesión, ocupando mayor superficie sobre el terreno de juego, achicando los espacios. Los nervios y miedos iniciales han ido mutando en otros nuevos, pero hasta aquí se han dado las condiciones esperadas, dejando expectativas positivas para enfrentar lo que vendrá.</p>
<p>De cualquier manera existe una fuerza interior que nos insta a mantener la calma. El camino para llegar hasta aquí ha llevado su tiempo y ha tenido sus obstáculos. Por nuestra parte, avanzamos, paso a paso, esperando el gol que confiamos va a llegar. Todavía queda mucho partido por delante.</p>
<p><a name="45min"></a></p>
<blockquote>
<p>45’</p>
</blockquote>
<p>Este último tramo tiene mucho ida y vuelta, largos recorridos, y algunos piques más cortos. Y si bien no se modifica el marcador, nos toca enfrentar un primer sobresalto que afortunadamente no pasa de eso.</p>
<p>El peso del encuentro se empieza a sentir y esperamos con cierta ansiedad el entretiempo, que servirá para obtener un pantallazo actualizado del estado del equipo, y para revisar y confirmar que estamos en forma para lo que sigue.</p>
<p><a name="entretiempo"></a></p>
<h4 id="entretiempo">Entretiempo</h4>
<p>Durante la charla técnica, ya en el descanso, analizamos el desarrollo hasta aquí, delineamos posiciones y discutimos estrategias. Las perspectivas y los números son buenos. Por otro lado, también hay algo dentro que se mueve y nos inspira para salir a jugar lo que falta.</p>
<p>Un triunfo implica sensaciones y experiencias para las cuales no hay un nombre.</p>
<p><img src="/images/2018/11/blackboard.jpeg" alt="" class="align-center" style="max-height: 400px; width: auto;" /></p>
<p><a name="segundo"></a></p>
<h3 id="segundo-tiempo">Segundo tiempo</h3>
<p>La pausa se nos esfuma rápidamente y ya estamos de vuelta en cancha para la segunda mitad.</p>
<p><strong>E</strong>ntusiasmados. (<strong>M</strong>uy) <strong>M</strong>otivados. <strong>A</strong>rriba!</p>
<p><a name="60min"></a></p>
<blockquote>
<p>60’</p>
</blockquote>
<p>El calor hace menos soportable el esfuerzo por seguir llevando la pelota hacia adelante, sin embargo no podemos dejar de acompañar a quien lidera el ataque. Y tampoco nos podemos permitir perder la línea en la defensa ante cualquier posible contraataque.</p>
<p>Atrás quedó la meseta de la última media hora, entramos en la recta final. El último tercio de partido promete acción y emoción. Como muestra debemos superar nuevas pruebas, un par de tiros en el poste que hacen tambalear nuestra fe. Sí, el hierro con lo justo! Se justifica el ingreso de un par de refuerzos que aporten un poco de oxígeno.</p>
<p><img src="/images/2019/1/fe.jpeg" alt="" class="align-center" style="max-height: 400px; width: auto;" /></p>
<p><a name="75min"></a></p>
<blockquote>
<p>75’</p>
</blockquote>
<p>Mientras mantenemos la valla invicta, nuestro gol no llega, aunque se presiente cada vez más cerca, y más fuerte. Proyectamos múltiples jugadas desde distintos ángulos, aprovechando al máximo todas las dimensiones del campo. Aún así, no se deja ver con claridad, se nos esconde. Falta definición.</p>
<p>A esta altura las interacciones e instrucciones desde el banco se tornan más frecuentes, ningún detalle puede quedar librado al azar en el cierre del partido. Se viven algunos segundos de tensión, por momentos escasea el aire. No va a ser fácil aguantar lo que resta.</p>
<p>Paralelamente nos invade cierto pánico. Este gol que estamos próximos a lograr significa un cambio de categoría que sin dudas afectará nuestro futuro. Estamos preparados para eso? El equipo? La hinchada? Nuestro estadio?</p>
<p><a name="85min"></a></p>
<blockquote>
<p>85’</p>
</blockquote>
<p>Se juegan los últimos minutos. Inhalamos, exhalamos, conscientes en cada respiración, renovando energías. Estiramos músculos, procurando flexibilidad mientras aguardamos el desenlace. Nos concentramos en el arco, que parece pequeño cuando pensamos en el gol.</p>
<p><a name="88min"></a></p>
<blockquote>
<p>88’</p>
</blockquote>
<p>La perseverancia y la paciencia dan sus frutos. Casi en tiempo cumplido, tras una serie de rebotes en el área y escapándonos del libreto, respondemos a un ataque con un contragolpe de pizarrón y…</p>
<h2 id="gool"><a href="/es/Gool">Gool!</a><sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote">1</a></sup></h2>
<p>Salió. Hay llanto y cansancio, se liberan presiones y emociones, a la vez que se respira alegría y felicidad.</p>
<p><img src="/images/2019/3/gool.jpeg" alt="" class="align-center" style="max-height: 400px; width: auto;" /></p>
<p><a name="90min"></a></p>
<blockquote>
<p>90’</p>
</blockquote>
<p>Apenas queda tiempo para recuperarse del shock. Es el final, y es difícil explicar todo lo que nos pasa por la cabeza. No sé si fueron 90 minutos o 9 meses. Sí sé que ahora empieza otro partido, más largo aún, para el que no hay táctica o estrategia que valga. Y promete ser el más importante de nuestras vidas.</p>
<p>Un partido que adivino estaremos en desventaja la mayor parte del tiempo, que sufriremos de a ratos, y que transformará nuestra forma de juego en un aprendizaje permanente. Pero es un partido que queremos disputar, en el que pelearemos cada pelota y disfrutaremos cada instante tanto como sea posible.</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p>Hay más: <a href="/es/Gool">Gool no es casualidad</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Matias BordeseUn partido que marcará un antes, un durante y un después.Uno era yo2018-04-05T19:00:00-03:002018-04-05T19:00:00-03:00https://matiasb.github.io/es/recuerdos<p><em>Tenía un compañero con el que “se entendía de memoria”, diría un comentarista. Aunque en este caso no era estrictamente cierto, porque no había un libreto preacordado o aprendido que determinara cómo jugar la pelota. No era un fútbol prefabricado, o de laboratorio. No. Era algo mucho más esotérico, casi telepático, un dialecto propio. Entre ellos existía una forma de diálogo usando la redonda en lugar de palabras.</em></p>Matias BordeseTenía un compañero con el que “se entendía de memoria”, diría un comentarista. Aunque en este caso no era estrictamente cierto, porque no había un libreto preacordado o aprendido que determinara cómo jugar la pelota. No era un fútbol prefabricado, o de laboratorio. No. Era algo mucho más esotérico, casi telepático, un dialecto propio. Entre ellos existía una forma de diálogo usando la redonda en lugar de palabras.PyCon Argentina 20172017-11-29T09:00:00-03:002017-11-29T09:00:00-03:00https://matiasb.github.io/es/pyconar<p>Pasó la PyCon en Argentina, nuevamente en Córdoba (como en el 2010). El hecho de que la conferencia tuviera lugar en la Docta fue una buena excusa para volver a participar, tras haberme perdido la del año pasado.</p>
<p>En esta ocasión presenté <a href="https://www.dropbox.com/s/j0ovxbygd30869i/201711-PyConAr-De.py.a.PyPI.pdf?dl=0">“De .py a PyPI”</a>, una charla sobre empaquetamiento y publicación de una librería en PyPI. En el proceso, además, dejamos publicado <code class="language-plaintext highlighter-rouge">fpt-cli</code><sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote">1</a></sup><sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote">2</a></sup>, una utilidad de línea de comandos para seguir las estadísticas de la primera división del fútbol argentino.</p>
<p>Aunque no pude asistir a todas las charlas que hubiera querido, aquí una breve reseña de algunas de las que vi y me parecieron interesantes:</p>
<p><strong>Magicicada, el fork open-source de Ubuntu One filesync</strong><br />
<em>Natalia Bidart</em><br />
Una charla de alto nivel sobre la historia y desafíos de Ubuntu One filesync, y la arquitectura y planes de Magicicada.</p>
<p><strong>Todavía (unit)testeamos… para qué?</strong><br />
<em>Javier Mansilla</em><br />
Análisis de las razones y motivaciones a la hora de escribir <em>unit tests</em>, repasando además buenas y malas prácticas.</p>
<p><strong>Me están espiando! Cómo saber con Python si el imperialismo te persigue o te pasaste de Focusyn</strong><br />
<em>Nicolás Demarchi</em><br />
Entretenida presentación de cómo usar herramientas escritas en Python para seguir la órbita de los satélites y estaciones espaciales (y saber cuando estamos en su rango de visión!).</p>
<p><strong>Emulando paralelismo de forma asincrónica</strong><br />
<em>Facundo Batista</em><br />
Una introducción al paradigma de programación asincrónica, describiendo cómo pensar los problemas y algunos ejemplos (comparando paralelismo/threads vs concurrencia/asyncio).</p>
<p><strong>No hay tal cosa como un almuerzo gratis en temas de software</strong><br />
<em>Lucio Torre</em><br />
La keynote de cierre, un interesante punto de vista en cómo ajustando la implementación a los requerimientos sacrificamos flexibilidad a futuro, y la importancia de ser consciente de dónde uno está parado.</p>
<p>Gracias a todos los que hicieron posible el evento!</p>
<p>Ah, en la foto de arriba soy el que tiene <a href="https://twitter.com/pyconar/status/931570985903558656">camiseta de fútbol</a>.</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p><a href="https://github.com/el-ega/fpt-cli">https://github.com/el-ega/fpt-cli</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:2" role="doc-endnote">
<p><a href="/es/fpt-cli/">Facilitame los Partidos del Torneo</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Matias BordeseConferencia Argentina de PythonFacilitame los Partidos del Torneo2017-11-29T08:00:00-03:002017-11-29T08:00:00-03:00https://matiasb.github.io/es/fpt-cli<p><code class="language-plaintext highlighter-rouge">$ pip install fpt-cli</code></p>
<script type="text/javascript" src="https://asciinema.org/a/148461.js" id="asciicast-148461" async=""></script>
<p>Pequeño <a href="https://github.com/el-ega/fpt-cli">proyecto</a> surgido con la excusa de empaquetar y subir código a <a href="https://pypi.org/project/fpt-cli/">PyPI</a> desde cero.</p>Matias BordeseFPT - Estadísticas de la primera división del fútbol argentino por línea de comandosfib(9)2017-11-16T09:00:00-03:002017-11-16T09:00:00-03:00https://matiasb.github.io/es/fib9<p>0 ganas de festejar tras cumplir 1 año más, 1 podría pensar. Pero
seguramente dentro de 2 días estaría arrepentido de no haberlo
celebrado, aunque fuera mínimamente. Además, con llegar a 3 seremos
una multitud, dicen.</p>
<p>Por tal motivo, el jueves, desde 5 minutos antes de la hora anterior a
las 8PM, quedan invitados a pasar por el depto
para tomar algo y ponernos al día. Los espero, no me vengan con un
martes 13! Y no, no es la previa de una salida. Es jueves, el viernes
hay que trabajar y ya no tenemos 21.</p>Matias Bordese34 son mejoresSon rachas2016-08-05T10:00:00-03:002016-08-05T10:00:00-03:00https://matiasb.github.io/es/son-rachas<p>Este saludo es para un groso,<br />
y espero salir airoso;<br />
partícipe él de grandes éxitos<br />
y poseedor de más de un mérito,<br />
basta citar su origen sacuyista.</p>
<p>No haremos aquí una lista,<br />
sólo un breve itinerario:<br />
otrora ganador literario,<br />
ayer blogger de me ne frega;<br />
más aún, creador de el ega<br />
y, dejen que me incline,<br />
padre del chiflido on line;<br />
imposible olvidar además,<br />
los chistes, ocurrencias y r+.</p>
<p>Y cuando los tiempos no ayudan,<br />
y se teme las respuestas no acudan,<br />
“son rachas”, brevemente analiza<br />
con la sabiduría que lo caracteriza.</p>
<p>Aunque podríamos seguir,<br />
no es trivial este discurrir<br />
y no queremos aburrir.</p>
<p>Un saludo a vuestra merced,<br />
muy feliz cumple para usted.</p>Matias BordeseEste saludo es para un groso, y espero salir airoso; partícipe él de grandes éxitos y poseedor de más de un mérito, basta citar su origen sacuyista.Not in this lifetime2016-07-18T13:45:58-03:002016-07-18T13:45:58-03:00https://matiasb.github.io/es/not-in-this-lifetime<p>Si alguna vez me hubieran preguntado si vería a los Guns N’ Roses en vivo, mi respuesta hubiera sido exactamente la que da el título a este post: no en esta vida.</p>
<p>No es que tuviera algo en contra de esta gente, pero la música en inglés nunca estuvo entre mis preferencias. Por otro lado, la banda dio su último recital con la formación original allá por 1993<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote">1</a></sup>, y según los entendidos su continuación no sería lo mismo, además de que en aquella época yo apenas contaba 10 años y mis gustos musicales eran discutibles<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote">2</a></sup>.</p>
<p>Sin embargo, el destino me juntó con Naty, quien <em>obviamente</em><sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote">3</a></sup> es fan de varias bandas representativas angloparlantes (y anglocantantes). Y en su afán de lo que ella catalogaría como “instrucción musical”, me ha hecho escuchar a varias de ellas, haciendo especial hincapié en Guns N’ Roses, grupo que de alguna manera marcó su adolescencia.</p>
<p>Y quiso el destino (también?) que los miembros emblemáticos de la banda se reunieran nuevamente este año, para la gira que lleva por nombre <strong>“Not in this lifetime”</strong><sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote">4</a></sup>.</p>
<p>Lo interesante es que esta combinación fortuita de sucesos derivó en que el último 16 de julio estuviera presente en el Rogers Centre de Toronto<sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote">5</a></sup>, con Naty y otras 49.998 personas, para el show de Axl y compañía.</p>
<p>Y más allá de los motivos que hayan llevado a la re-unión, la función fue impecable: excelente el sonido, la puesta en escena, la organización. Y la banda, considero, se mostró a la altura de las expectativas, tanto para los fans, como también para aquellos que íbamos como testigos de un hito en su historia y a contemplar el espectáculo.</p>
<p>Por mi parte debo admitir que sumó a mi satisfacción (tal como esperaba!) el hecho de ver a Naty compenetrarse y disfrutarlo como si hubiera logrado viajar al pasado, más de 20 años atrás, reviviendo una parte de su adolescencia y sueños que parecían imposibles.</p>
<p>Y es que cuando se tiene la oportunidad de volver en el tiempo para este tipo de cosas, hay que hacer lo posible por aprovecharla<sup id="fnref:6" role="doc-noteref"><a href="#fn:6" class="footnote">6</a></sup>.</p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p>Casualmente el último recital fue el 17 de julio, en la cancha de River. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:2" role="doc-endnote">
<p>Ricky Maravilla? <a href="#fnref:2" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:3" role="doc-endnote">
<p>Otra de las tantas facetas en las que nos complementamos. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:4" role="doc-endnote">
<p>Respuesta de Slash a la posibilidad de que los GnR se volvieran a juntar. Lo cual no hace más que demostrar que lo que uno piensa (y dice, o calla) es válido en un contexto y tiempo dados, y que es muy difícil mantener una posición del estilo <em>para siempre</em> o <em>nunca más</em>. Y/o que el dinero mueve montañas (y junta lo que parece injuntable). <a href="#fnref:4" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:5" role="doc-endnote">
<p>Todo esto fue planificado antes de que decidieran extender la gira a latinoamérica! Pero resultó una muy buena excusa para pasar unas inmejorables vacaciones y compartir tiempo con las primas del Canadá :-) <a href="#fnref:5" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:6" role="doc-endnote">
<p><strong><a href="http://www.azlyrics.com/lyrics/gunsnroses/itssoeasy.html">It’s so easy</a></strong> to miss the <strong><a href="http://www.azlyrics.com/lyrics/gunsnroses/nightrain.html">nightrain</a></strong>… <a href="#fnref:6" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Matias BordeseSi alguna vez me hubieran preguntado si vería a los Guns N’ Roses en vivo, mi respuesta hubiera sido exactamente la que da el título a este post: no en esta vida.