Calculando factoriales de números grandes

El día de ayer por medio del perfil de línea de tiempo del Doctor Juan González observamos el interesante artículo tutorial sobre Python publicado por Juan J. Merelo Guervós el cual incluye retos de programación. Ni tardo ni perezoso, a pesar de llegar cansado a mi hogar, y tras una vigorizante taza de café negro con leche de avena sin azúcar nos dimos a la tarea de practicar algunas cositas para mantener el cerebro en forma y al día con las novedades en programación.

Calculando el factorial de un número.

Definición.

No vamos a hacer esta entrada muy profunda, ya que debemos ir a trabajar para ganarnos el pan de cada día, directo y rápido: el factorial de un número viene expresado por la multiplicación sucesiva de sus antecesores, uno a uno, hasta llegar a uno y se denota por ” n! ” ; así, por ejemplo 5!= 5 x 4 x 3 x 2 x 1 = 120 (las equis indican multiplicación).

El reto.

Pues arroja el guante el sr. Merelo con una función recursiva escrita, por supuesto, en lenguaje Python para calcular un factorial y tras ensayo y error llegamos a una satisfactoria y muy parecida a la ya famosa y escrita por todos lados en internet (para ser honestos en algún momento vimos esta función pero no recordabamos… hasta que el buscador DuckDuckGo acudió en nuestra ayuda, un asistente tan capaz -en memoria- como todas las asistentes que tuvo Albert Einstein -que en paz descansen, Valentine Bargmann y el Doctor Einstein-).

Para ilustraros cómo funciona, aprovechando nuestro mundo tecnológico –y añorando el gis y el pizarrón- os colocamos un gif animado tomado de penjee.com, un sitio web dedicado también a la enseñanza de Python:

Función factorial animada tomada de penjee.com
Función factorial animada tomada de penjee.com
def factorial(num):
  if num == 1:
    return 1
  else:
    return num * factorial(num - 1)

Calculando el factorial de un número grande.

Factorial, fórmula y definición
Factorial, fórmula y definición

Hasta acá todo muy bien, pero el reto no finalizaba allí, una segunda pregunta era ¿qué tal calcula los números grandes? Como bien podéis imaginar, el multiplicar y multiplicar en ordenador pues en algún momento se nos agota la memoria suponíamos nosotros… pues que aquí está la pega del asunto, tras calcular el factorial 120! de manera fácil (en Wikipedia en inglés denotan que normalmente el máximo factorial que se puede calcular con un procesador de 64 bits en una variable de tipo entero es 20! –ya nosotros hemos comentado el poder de cómputo de los procesadores y sistemas operativos de 64 bits-) pues nos envalentonamos a ir “al infinito y más allá”.

python factorial.py con n = 120 (ciento veinte)
python factorial.py con n = 120 (ciento veinte)

Siendo así la cosa le establecemos a 1200 (un mil doscientos) a la función de marras y nos arrojó este lindísimo mensaje de error que os mostramos:

python factorial.py con n = 1200 (mil doscientos)
python factorial.py con n = 1200 (mil doscientos)

Pero leyendo nos percatamos que el problema no es el desbordamiento de memoria, no,  el desbordamiento es un error en la profundidad de recursión:

RecursionError: maximum recursion depth exceeded in comparison

El contrareto.

Ya esto se salía del reto impuesto, es un contrareto que nos presenta Python pues claro, en un entorno de programación uno debe siempre imponer límites considerando que aún no ha llegado la computación cuántica (y aún así ésta tiene límites en este universo). Indagando, y hay bastante material sobre el tema, llegamos a la librería sys encargada de manejar tales asuntos y de hecho el manual en línea de Pyton nos advierte:

Set the maximum depth of the Python interpreter stack to limit. This limit prevents infinite recursion from causing an overflow of the C stack and crashing Python. The highest possible limit is platform-dependent. A user may need to set the limit higher when she has a program that requires deep recursion and a platform that supports a higher limit. This should be done with care, because a too-high limit can lead to a crash.

Que traducimos al castellano:

Establece el límite de la profundidad máxima de la pila del interprete Python. Este límite previene una recursión infinita ocasionando un desbordamiento en la pila (del lenguaje) C y la consecuente falla de Python (Dios nos ampare que alguien crea que Python es falible†). El límite más alto posible es dependiente de la plataforma empleada. Un usuario puede necesitar ajustar el límite superior cuando tenga un programa que requiera una mayor recursión y la plataforma que lo soporta lo permita. Esto debería hacerse con cuidado porque un límite muy alto puede conducir a un fallo (Dios nos ampare que alguien crea que Python es falible†).

†(Nota del traductor en tono de sarcasmo).

Python recursion limit get and set
Python recursion limit get and set

O inventamos o erramos.

Don Simón Rodríguez: "Inventamos o erramos".
Don Simón Rodríguez: “Inventamos o erramos”.

Como nuestro insigne Don Simón Rordríguez nos legó, debemos inventar y a lo sumo fallar (y reintentar y así sucesivamente hasta lograr inventar) nosotros pensamos ¿Y si establecemos el límite superior de la pila de procedimientos (límite de recursión) precisamente de manera recursiva? 🤔

Pues acá vamos entonces:

import sys
def factorial(num):
  '''Return large factorial by Jimmy Olano Sayago
               (GNU General Public License v3.0) '''
  if num<=1:
    return 1
  else:
    sys.setrecursionlimit(sys.getrecursionlimit()+1)
    return num * factorial(num - 1)

file = open('large_factorial_result.txt', 'w')
cad = factorial.__doc__
file.write( cad )
file.write( ' factorial(21700): ' )
file.write( str( factorial( 21700) ) )
file.close

Al tener nosotros establecida la función, por tanteo y utilizando un procesador AMD A8-6600K APU (64 bits, 4 núcleos y 8 gigabytes en RAM) llegamos un valor de 21700! el cual ocupa 86400 digitos. Esencialmente el guion que hicimos basados en la clásica función recursiva de cálculo factorial es aumentar el límite de pila de Python de manera recursiva para luego abrir un archivo de texto y guardar allí el resultado.

El delicado tema de los typos de variables y la memoria disponible.

Consultamos muy diversos reportajes sobre el tema del manejo de la memoria y las variables pero el que fue revelador para nosotros es uno escrito hace más de once años pero que goza de plena vigencia: una comparación sobre cómo trabajan las variables los lenguajes C y Python.

Tiene su teoría y abunda su práctica y probamos los diferentes ejemplos y nos resultó en valore comprobatorios contra lo que se afirma en dicha entrada pero la conclusión es tan excelente que se las traemos en idioma inglés y la traducimos al castellano:

Python removes this confusion, there is only the integer object. Does it have any limits? Very early versions of Python had a limit that was later removed. The limits now are set by the amount of memory you have in your computer. If you want to create an astronomical integer 5,000 digits long, go ahead. Typing it or reading it will be the only problem! How does Python do all of this? It automatically manages the integer object, which is initially set to 32 bits for speed. If it exceeds 32 bits, then Python increases its size as needed up to the RAM limit.

La traducción hecha por nosotros:

Python elimina esta confusión (de variables), hay solo el objeto entero. ¿Tiene algún límite? Muy tempranas versiones de Python tuevieron un límite que luego más tarde fue removido. Los límites ahora son establecidos por el monto de memoria que usted tenga en su computadora. Si quiere crear un entero astronómico de 5 mil dígitos, ¡adelante! ¡Escribirlo o leerlo será el único problema! ¿Cómo Python hace todo esto posible? Pues automáticamente dirige el objeto entero, el cual inicialmente estaba configurada a 32 bits por motivos de velocidad. Si excede los 32 bits, entonces Python incrementa su tamaño tanto como se necesite hasta llegar al límite de la memoria RAM.

Así como lo podéis leer, en el año 2006 se consideraba “astronómico” una cifra de 5000 dígitos, ¡PUES HE AQUÍ QUE CALCULAMOS UN FACTORIAL CON 86400 DÍGITOS! Para nosotros es todo un logro digno de dedicarle una entrada y una pequeña nota en las Wikipedia(s).

Sometido a consideración por la comunidad de programación y comunidad matemática.

De inmediato subimos el guion y su resultado (utilizando el idioma inglés) a Gist mantenido en línea por GitHub.com (¡gracias!) y “tuiteado” con la etiqueta #Python y #1linepy tal como recomienda el sr. Merelo:

Experimento social.

Como experimeto social hemos cometido una torpeza en la fórmula cuando establecemos:

if num<=1:
  return 1

El objetivo es detectar cuántas personas realmente analizan nuestra propuesta y nos hacen llegar sus observaciones (se especula que el mismísimo Leonardo DaVinci codificaba mal sus planos a propósito por si caían en manos enemigas) ya que el cálculo de factoriales está supeditado al campo de los números positivos, ya sean reales o imaginarios (esto es así porque el cálculo factorial es clave en la determinación de combinaciones y permutas, todas ellas en el campo de los números positivos).

También hemos publicado en Wikipedia en inglés nuestra experiencia (y próximamente en castellano, francés, italiano, portugués, alemán y ruso en la medida de lo posible y de nuestro tiempo libre) para determinar cuales sociedades unidas por un idioma común aceptan o rechazan nuestra propuesta (cada idioma Wiki tiene sus wikipedistas que algunas veces nos han “echado para atrás” nuestras ediciones por diversos motivos variopintos).

 

Conclusión: no hemos llegado aún al infinito, menos más allá.

Una inquietud que tenemos es la gran cantidad de ceros al final del resultado y una cosa que nos tranquiliza es la serie de resultados hasta 20! que va aumentado la cantidad de ceros a la derecha. Lo lógico es que si estamos multiplicando pues “corremos la coma hacia la derecha” y se debe, desde luego, rellenar con ceros para ello.

Aún falta por determinar hasta qué punto alcanza nuestro hardware su límite, podremos hacer un ciclo “for” que arranque con este último valor de 21700! e irlo incrementando y guardando en archivos cuyos nombres tengan el valor calculado apara identificarlos hasta que simplemente falle Python, ¡PERO ESPEREN, HAY MÁS!

Pensamos que arrancando nuestro sistema operativo Ubuntu 16.04 LTS en modo terminal, sin cargar interfaz gráfica y “tumbando” los servicios diversos -Apache server, MySQL, etc.- y los más que se puedan de systemd ganaremos más memoria y compararemos resultados a ver si influye y si es así, cuánto influye, ¡SOMOS TODO OÍDOS A VUESTRAS IDEAS AL RESPECTO POR MEDIO DE NUESTRA CUENTA EN TWITTER @KS7000!

Fuentes consultadas.

En idioma castellano.

En idioma italiano.

En idioma inglés.

Python logo sin texto

Python http.server

La verdad es que revisando los temas publicados por nuestros colegas de GNULinuxBlog (por ahora quien publica el tema, el sr. Elías Rodríguez Martín y el sr. José Miguel, creador del blog) encontramos uno en particular muy útil y que nosotros habíamos enfocado de una manera un tanto complicada en comparación con la facilidad de lo que allí proponen. Cuando conseguimos que alguien es más listo que nosotros inmediatamente lo reconocemos y aprendemos de dichas personas, no tenemos rubor en admitirlo, por eso os pedimos que nos acompañéis en nuestro artículo de hoy: compartir ficheros de una manera rápida y sencilla con Python.

Python logo original
Python logo original

Introducción.

Pasar ficheros de una máquina a otra en algún momento nos tocó o tocará hacerlo y en nuestro caso fue de una máquina con Ubuntu hacia otra con software privativo ubicada fuera de nuestra red de área local. Para ello habilitamos un servidor FTP, en este caso ProFTPd, que conecta con las credenciales de otro usuario creado de manera exprofeso y con derechos de lectura sobre los archivos en cuestión. Eso a nosotros nos pareció fácil, pero ¿qué tal hacer algo que se pueda ejecutar en varios sistemas operativos?

Para ello nada mejor que Python: es legendario y ya en 1996 corría en sistemas tan diverso como Linux, IRIX, Compaq Tru64, OS X, Solaris, and Windows (ver caso de éxito “Industrial Light & Magic”, “Lucas Films LTD”). Al momento de escribir estas líneas Python (que ya va por la versión 3.6.1) está disponible para ser descargado y usado en Linux/Unix, Windows, AS/400 (OS/400), Beos, MorphOS, MS-DOS (sí, así como lo leen, no ha muerto, aún), OS/2, OS/390 and z/OS, RIC-OS, Symbian OS series 60, VMS, ¡¡¡ Windows CE or Pocket PC !!!, HP-UX y hasta podemos comprar una versión privativa a la cual no podremos acceder a su código fuente, y cuya distribución es llamada ActivePython (sabrá Dios en cuales plataformas de hardware ejecutará este último “sabor”) pero como véis, de que tenemos opciones y libertad de escoger es indudable, y si nos proponemos hasta podemos nosotros mismos hacer nuestra propia distribución de Python por nosotros elaborada (palabras mayores). Esto último es conocido como “implementaciones” y las podéis conocer más en este enlace -en idioma inglés-; ya por último debemos recalcar que Python es aceptado como lenguaje dentro del manejador de base de datos PostgreSQL ¡¿maravilloso, cierto?! 😎 .

Versión Python 2.x

En dicha versión 2.x se utiliza “SimpleHTTPServer” y lo probamos y comprobamos desde nuestra red de área local y desde una máquina externa, ya en la entrada que nos inspiró bien lo explican: solo debemos lanzar el siguiente comando (las librerías vienen integradas en Python):

python -m SimpleHTTPServer 8000

¿Fácil cierto? Nos vamos en una ventana terminal hasta la carpeta que contiene los archivos “a compartir”, lanzamos el guión en Python indicandole que abrimos el puerto 8000 y en el otro ordenador lanzamos nuestro navegador web con la dirección IP destino (si tenemos un enrutador pues configurad ALGO parecido a lo nuestro):

TP-LINK Forwarding Virtual Servers
TP-LINK Forwarding Virtual Servers

Reconocimiento a la labor del sr. Elías.

En su repositorio de aplicaciones -gentilmente alojadas por GitHub.com- el sr. Elías nos enseña como agregar valor al código existente por medio de una bonita interfaz gráfica para escoger la carpeta a compartir y cambiar el puerto de escucha -vamos que antes igual teníamos esa opción por la línea de comandos- pero con otro botón adicional: el botón de detener el servicio ya que por línea de comandos solo con CTRL+C se detenía el proceso. En la siguiente imagen comparto una carpeta de respaldo de un antiguo aparatito reproductos de MP3 y disfruto de música que tenía tiempo que no escuchaba:

PyShare (guion escrito por Elías Rodríguez Martín)
PyShare (guion escrito por Elías Rodríguez Martín)

Lo bonito del asunto del asunto (el guion, no la utilería gráfica PyShare) es que podemos navegar en nuestras carpetas, iniciar el servicio, detenerlo, ir a otra carpeta y repetir el proceso; y por si queda alguna duda sobre su funcionamiento se abre de una vez una pestaña en nuestro navegador web predeterminado para visualizar la carpeta compartida, todo muy práctico:

python_easy_server.py
python_easy_server.py

Lo que véis en la última imagen es el guión desarrollado por el Sr. Elías y el último error es simplemente un mal manejo de los caracteres de nuestro bello idioma castellano (compartí la carpeta llamada “Vídeos” que tiene un acento). Alegremente lo primero que hisimo fue lo que hacen los niñatos y niñatas de los parvularios, COPIAR Y PEGAR en un archivo llamado “python_easy_server.py” pero obviando la regla de oro: UTILIZAR GIT PARA CONTROLAR (y colaborar) EN EL CONTROL DE VERSIONES (mirad nuestro humilde tutorial al respecto).

No os vamos a “aburrir” con el tema de GitHub pero si os indicamos que propusimos unos cambios (“fork” y “pull request”) que podréis analizar con detalle en nuestros repositorios alojados -de nuevo, la publicidad- en GitHub.

Versión Python 3.X

Como bien especifica el sr. Elías en Python versión 3.x el SimpleHTTPServer ha sido sustituido por http.server y en esencia tiene la misma línea de comandos pero si analizamos el código pues bueno, todo de acuerdo a la nueva versión (3.6 en este caso). En el repositorio correspondiente en el módulo server.py leemos 1211 líneas y está muy bien explicado, un trabajo muy compacto con las deficiones básicas del protocolo HTTP y sobre cómo funciona por defecto esta librería escrita en Python.

Nos agrada mucho que vayan directo y al grano explicando cómo funciona un servidor web, eso es poesía informática, para muestra un botón: el inicio de la clase BaseHTTPRequestHandler:

The following explanation of HTTP serves to guide you through the
code as well as to expose any misunderstandings I may have about
HTTP (so you don’t need to read the code to figure out I’m wrong
:-).

Que traducido lo exponemos de la siguiente manera:

La siguiente explicación de HTTP sirve para guiarlo a través del código, así como para exponer cualquier malentendido que yo pueda tener sobre HTTP (por lo que no necesita leer el código para averiguar que estoy equivocado).

Allí se fajan a explicar cómo un servidor web recibe una petición y como se contesta, ya sea con el protocolo “HTTP/0.9” (por defecto en este módulo, línea N° 261), “HTTP/1.0” o “HTTP/1.1”. Esencialmente funciona de la siguiente manera (vamos a ser redundantes con propósitos didácticos):

  1. Un servidor web está funcionando con un “socket” abierto que se interpreta (y nos abstraemos) como un puerto abierto en un número conocido (por defecto es el puerto 80 pero como estamos usando algo “privado” usamos el puerto 8000).
  2. Un programa cliente, ya sea en nuestra propia red de área local o desde el internet, envía una petición a ese puerto conociendo de antemano la dirección IP del servidor web (no ahondaremos en el tema de los DNS, para los lectores más avezados).
  3. Esa petición tiene un protocolo específico: ejemplo “GET / HTTP/1.1″ ¿Pero que significa esto? Veamos el siguiente punto no sin antes mencionar que una petición puede contener dos líneas adicionales que son opcionales (por ahora no complicaremos la labor).
  4. Ante una petición tipo GET (en este caso nos exigen usar un protocolo específico HTTP/1.1) el servidor web procede a revisar el directorio por defecto (estamos hablando del http.server de Python, un servidor Apache es mucho más avanzado) y si encuentra un archivo index.html lo enviará, de lo contrario, si no lo halla entonces procederá a mostrar un listado de los archivos presentes en la carpeta desde donde ejecutamos el comando “python -m http.server 8000 (de hecho así también se comporta un servidor web Apache). Este último comportamiento lo podemos detallar en la función send_head() de la clase SimpleHTTPRequestHandler. Incluso podríamos aquí empoderarnos de nuestro idioma e incluir comandos para buscar también un archivo llamado “índice.html“, es decir, que ‘busque’ en inglés y castellano ¿Por qué no? (amén de traducir al castellano todos los demás mensajes). Así mismo, con acentos y todo el hecho de nosotros hablar dos o más idiomas ejercita nuestro cerebro, ampliamos nuestra memoria y nos ayuda a crear sinapsis nuevas en nuestras neuronas.
  5. La manera como envía esta respuesta debe estar ajustada al protocolo en el cual nos lo solicitaron. Para este ejemplo debemos contestar según el protocolo que nos pidieron [más abajo veremos que esto no es necesariamente cierto;-) ] y el cual consta de tres partes.
  6. La primera parte es una línea que está compuesta a su vez en tres segmentos:
    • Versión en la que estamos respondiendo (este ejemplo “HTTP/1.0“) más un espacio como separador al final.
    • Un código numérico de tres dígitos que está bien normalizado ( ¡A que alguna vez habrán visto una respuesta “404“! ). Para este ejercicio contestaremos con un “200” (más el consabido espacio en blanco como separador).
    • Un tercer segmento que explica ¡cómo no, en inglés! un mensaje descriptivo para nosotros los seres humanos (el código anterior, numérico, es muy agradable a nuestros amigos los ordenadores). El código 200 tiene la descripción simple y llana: OK.
  7. Para separar la primera parte de la segunda se recomienda utilizar la combinación de caracteres especiales de retorno de carro y avance de línea, pero debemos estar prestos a utilizar solo el avance de línea para una compatibilidad más amplia (de nuevo, ver BaseHTTPRequestHandler).
  8. La segunda parte son varias líneas llamadas encabezados y ajustados a la norma RFC-822 (la norma data del año 1982 pero pensamos está plenamente vigente).
  9. Un punto muy especial es que para separar la segunda parte de la tercera, además de utilizar el CRLF -ver punto 7- debemos insertar una línea en blanco, ¿cómo se hace esto? Pro favor ver la función end_headers() {línea 516} y os sorprenderéis.
  10. La tercera parte son los preciados datos en sí mismos cuyas características están especificadas de antemano en la segunda parte (recordad RFC-822).

Todo esto QUE PARECE COMPLICADO EN TEORÍA os lo podemos demostrar en la práctica: abrimos una ventana terminal y ejecutamos el servidor web Python, abrimos una segunda ventana terminal y ejecutamos la siguiente orden con el parámetro -i (o la manera larga –include) el cual muestra los encabezados HTTP:

curl -i http://192.168.1.47:8000/

Tras lo cual recibiremos la siguiente respuesta:

HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.5.2
Date: Sun, 16 Apr 2017 01:24:24 GMT
Content-type: text/html; charset=utf-8
Content-Length: 1370

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
(...

Acá la “magia” la realiza nuestro navegador web: según el Content-type se prestará a interpretar y mostrar de manera correcta los datos recibidos. Si es “text/html” pues vemos que es el lenguaje de marcado muy famoso y que además debemos mostrarlo con codificación utf-8. ¿Estáis confundido o perdido? Consultad nuestro tutorial sobre HTML.

Notad la respuesta del servidor identificandose a sí mismo “SimpleHTTP/0.6 Python/3.5.2“, maravilloso para nosotros. Mirad en la siguiente figura a lo que nos referimos:

Orden «GET HTTP 1.1»
Orden «GET HTTP 1.1»

Solicitando descargar un archivo.

Sigamos entonces con nuestra práctica: ya sabemos que no vamos a mostrar un archivo index.html -ni índice.html– sino que mostramos un listado de archivos, ahora nos iremos a la carpeta donde clonamos el “fork” del proyecto py-qt-share y lanzamos allí nuestro servidor web Python.

«python3 -m http.server 8000» carpeta proyecto py-qt-share
«python3 -m http.server 8000» carpeta proyecto py-qt-share

Allí tenéis de una vez la tres líneas completas, un abreboca, pero veamos la primera y segunda líneas -olvidemos la tercera, por el momento- y abramos de nuevo otra ventana terminal donde lanzamos el comando curl para retribuir el listado de archivos disponibles:

orden GET HTTP 1 sobre carpeta py-qt-share
orden GET HTTP 1 sobre carpeta py-qt-share

Una vez recibimos e interpretamos el HTML solicitamos de nuevo con otro comando GET el fichero README.md, observemos lo que responde:

curl -i README.md
curl -i README.md

Diseccionemos la respuesta:

Server: SimpleHTTP/0.6 Python/3.5.2
Date: Sun, 16 Apr 2017 02:55:23 GMT
Content-type: application/octet-stream
Content-Length: 575
Last-Modified: Sat, 15 Apr 2017 23:33:16 GMT

¿Cómo es posible que en el Content-type nos indique que es una aplicación? 😐😐😐

Sabemos plenamente que un fichero con extensión .md generalmente contiene un lenguaje de marcado tipo MarkDown (de nuevo, mirad nuestro otro tutorial relacionado), si acaso se asemeja más a un text/html que a una aplicación ¿Qué piensan ustedes?

Los lectores más experimentados nos responderán “¿de dónde saca que .md es un MIME type?” Pues veamos los tipos principales definidos en RFC-2046:

The “text” media type is intended for sending material which is
principally textual in form. A “charset” parameter may be used to
indicate the character set of the body text for “text” subtypes,
notably including the subtype “text/plain”, which is a generic
subtype for plain text.

De manera independiente a la opinión que tengamos si MarkDown es un fichero de texto o una aplicación debemos primero analizar en qué se basan para programar nuestro servidor web en Python para clasificar los archivos que listamos para compartir con otras computadoras. Analizemos la función dedicada a esto:

  def guess_type(self, path):
     """Guess the type of a file.
     Argument is a PATH (a filename).
     Return value is a string of the form type/subtype,
     usable for a MIME Content-type header.
     The default implementation looks the file's extension
     up in the table self.extensions_map, using application/octet-stream
     as a default; however it would be permissible (if
     slow) to look inside the data to make a better guess.
     """

Como bien podemos leer PRIMERO se trata de identificar el archivo por la extensión del archivo, lo cual es una solución rápida -y sucia- de identificar con qué tipo de datos estamos lidiando, y por defecto asumimos que es una aplicación “application/octet-stream” ya que ponerse a la tarea de abrir cada archivo y ver su contenido es una tarea muy tediosa (y que puede bloquear nuestro ordenador si tenemos demasiados ficheros almacenados).

“import mimetypes”

Es por ello necesario que pasemos a revisar la librería que importamos llamada mimetypes. Si revisamos la documentación de Python al respecto pues… la metodología es la misma, ¡”adivinar” por la extensión del fichero!

No obstante se apoyan en un método más “científico”: revisar las aplicaciones que tenemos en nuestro sistema operativo para poder ofrecer así una mayor variedad de opciones:

knownfiles = [
 "/etc/mime.types",
 "/etc/httpd/mime.types", # Mac OS X
 "/etc/httpd/conf/mime.types", # Apache
 "/etc/apache/mime.types", # Apache 1
 "/etc/apache2/mime.types", # Apache 2
 "/usr/local/etc/httpd/conf/mime.types",
 "/usr/local/lib/netscape/mime.types",
 "/usr/local/etc/httpd/conf/mime.types", # Apache 1.2
 "/usr/local/etc/mime.types", # Apache 1.3
]

Actualizado el jueves 20 de julio de 2017.

En este enlace de la “Web del Programador” podreis descargar un fichero en formato PDF con la mayoría de las extensiones de fichero conocidas, muy completa lista, os lo recomendamos.


De acá notamos que rapidamente podemos agregar nuestras propias extensiones, empezemos por la primera línea /etc/mime.types: en una ventana terminal metemos el comando “sudo nano /etc/mime.types” y podremos ver un encabezado parecido al siguiente:

###############################################################################
#
#  MIME media types and the extensions that represent them.
#
#  The format of this file is a media type on the left and zero or more
#  filename extensions on the right.  Programs using this file will map
#  files ending with those extensions to the associated type.
#
#  This file is part of the "mime-support" package.  Please report a bug using
#  the "reportbug" command of the "reportbug" package if you would like new
#  types or extensions to be added.
#
#  The reason that all types are managed by the mime-support package instead
#  allowing individual packages to install types in much the same way as they
#  add entries in to the mailcap file is so these types can be referenced by
#  other programs (such as a web server) even if the specific support package
#  for that type is not installed.
#
#  Users can add their own types if they wish by creating a ".mime.types"
#  file in their home directory.  Definitions included there will take
#  precedence over those listed here.
#
###############################################################################

En el último párrafo especifican que podremos crear nuestros propios tipos de ficheros por medio de un archivo en nuestro directorio personal de datos (“home”) y que tendrá preferencia aquella lista sobre esta. Pero si modificamos la lista principal los efectos tomarán para todos los usuarios en el ordenador donde estemos compartiendo archivos.

Es así que agregamos la siguiente línea, ya sea al final o buscáis el último grupo de líneas:

text/markdown                                   md
mime types text markdown md
mime types text markdown md

Previamente, por medio de nuestro editor de texto favoritos, nano, buscamos que la palabra “markdown” no esté registrado ni tampoco la extensión de archivo “md“. Luego le damos guardar y salir y relanzamos de nuevo nuestro servidor web escrito en Python para compartir archivos y solicitamos descargar de nuevo el archivo markdown del guion que le hicimos “fork” antes de resubirlo a GitHub. Nuestro navegador ahora nos mostrará el diálogo de descarga de una manera diferente y hasta nos propondrá editarlo con nuestro editor de texto con entrono gráfico favorito gedit, mirad:

Abriendo README.md
Abriendo README.md

Y claro, solicita abrirlo con gedit porque le estamos especificando que es un archivo de texto pero con una nueva clasificación “por nosotros propuesta”, tipo markdown.

Presentando ficheros markdown como HTML.

Para ensanchar nuestros conocimientos haremos un ejercicio interesante en cuanto a agregar funcionalidades a nuestro servidor web escrito en Python. La idea consiste en agregar una librería a nuestro Python y el cual permite convertir código markdown en HTML. Para ello debemos primero descargar el software necesario, tanto para Python 2.X como Python 3.X, mirad la imagen:

pip install markdown
pip install markdown

Una vez tengamos instalada la librería, corremos una terminal Python y hacemos unas cuantas pruebas y confirmar como convierte en código HTML lo que le pasemos en código markdown:

import markdown
import markdown
Python 3.5.2 (default, Nov 17 2016, 17:05:23) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import markdown
>>> print(markdown.markdown("* Lista "));
<ul>
<li>Lista </li>
</ul>
>>> print(markdown.markdown("# Título 1 "));
<h1>Título 1</h1>
>>>

Ya tenemos nuestro conversor; la idea que tenemos en mente es mostrar todo archivo con extensión “.md” en la carpeta que compartimos en ambos formatos pero con una diferencia: el enlace mostrado en el navegador en realidad NO es un fichero real en disco sino una “traducción” del fichero tipo markdown que sí existe en disco. Por supuesto nuestro navegador web al hacer click en uno de estos enlaces y recibir la cabecera de datos que recibe del servidor web Python se comportará de diferentes maneras: ante un fichero “.html” no ofrecerá guardar en disco sino directamente mostrarlo por pantalla. Esto que queremos hacer pareciera no tener sentido alguno pero si alguna vez llegáis a estudiar el lenguaje PHP entenderéis como un guión escrito en un lenguaje es convertido a un lenguaje de marcación como HTML. Cuesta un poco acostumbrarse a esta manera de trabajar en PHP pero una vez que uno le toma el gusto todo va sobre ruedas.

Ahora presentamos un código de prueba para exhibir un fichero markdown por pantalla en formato HTML, para ello también echaremos mano de la librería codecs que viene por defecto en las instalaciones de Python, veamos (recordad que tenemos abierta una ventana terminal en la carpeta que estamos compartiendo):

>>> import markdown
>>> import codecs
>>> archivo_md = codecs.open("README.md", mode="r", encoding="utf-8")
>>> texto = archivo_md.read()
>>> print(markdown.markdown(text);
<p><strong>Utilidad escrita por el sr. Elías Rodríguez Martín</strong></p>
<p><a href="https://linuxgnublog.org/es/compartir-archivos-facil-y-rapidamente-gracias-a-python/">Entrada hecha en Linux GNU Blog para compartir archivos con Python</a></p>
<p><em>Adición hecha por Jimmy Olano para soportar caracteres no ascii (utf-8) en os.chdir()</em></p>
<p><a href="http://www.ks7000.net.ve/2017/04/15/python-http-server/">Entrada hecha en KS7000 + WP para ampliar el alcance de dicho guion</a></p>
<p><a href="https://www.gnu.org/licenses/license-recommendations.html">Propongo tenga una licencia Apache que es adecuada para pequeños proyectos 8-)</a></p>

Por supuesto que hay algunos detalles que corregir: primero se debe colocar el encabezado del archivo HTML, luego el “cuerpo” que es el código anterior y al final las etiquetas de cierre del archivo HTML. Segundo se debe comprobar que el código sea “limpiado” y presentado correctamente ya que proviene de una fuente “no confiable”. Para ello recomiendan una herramienta llamada Bleach (Blanqueador) el cual se debe instalar via pip y no explicaremos más para no poner pesada esta entrada (más pesada, queremos decir).

Ya tenemos la idea de lo que vamos a hacer, ahora analizamos el código que vamos a modificar el cual podemos inspeccionar y descargar desde este enlace. En realidad son dos funciones que modificaremos: la función que muestra los ficheros en un archivo virtual en formato HTMl con un listado de los archivos a compartir y luego la otra función que entrega el ficero en sí mismo.

La primera función es llamada list_directory y la segunda es llamada do_GET y ambas pertenecen a la clase SimpleHTTPRequestHandler.

Fuentes consultadas:

En idioma castellano:

En idioma inglés:

GNU software libre.

¿Qué es el Software Libre?

¿Qué es el Software Libre?

El software libre es un tipo de programas de ordenador que respeta nuestra libertad. Utilizar software libre es una decisión política y ética que nos permite ejercer nuestro derecho a aprender y a compartir lo que aprendemos con otras personas.

Es habitual que el software que compramos nos niegue dichos derechos. Eso es porque realmente no estamos adquiriendo la propiedad del software que compramos, sino una licencia sobre el uso del software. Y dichas licencias nos atan mediante numerosas y sutiles reglas acerca de lo que podemos y no podemos hacer con el programa.

Software libre (" free software" )
Software libre (” free software” )

Si hacemos una copia del programa y se la damos a un amigo, si tratamos de averiguar cómo funciona el programa, si tenemos más de una copia del programa en nuestro computador en casa… y nos sorprenden haciéndolo, podemos ser multados o incluso procesados. Eso es lo que pone en la letra pequeña de las licencias.

¿Y si hubiera un grupo a nivel mundial de programadores éticos con gran talento, dedicados a la idea de escribir y compartir software entre ellos, y con cualquiera que acepte compartirlo de igual forma? ¿Y si cualquiera pudiera formar parte de dicha comunidad, y beneficiarse de ello, sin necesidad de saber nada sobre programación? En ese caso no tendríamos que preocuparnos más acerca de ser sorprendidos copiando un programa para nuestros amigos… porque no estaríamos haciendo nada ilegal.

¿Qué es el software libre? Video.

El movimiento del software libre.

De hecho, tal movimiento existe, y puedes llegar a ser parte de él. El movimiento del software libre fué creado en 1984 por Richard M. Stallman, mediante la creaci ́on de un proyecto llamado GNU (que significa “GNU’s Not Unix”) para proporcionar un reemplazo del sistema operativo UNIX; un reemplazo que respete la libertad de quienes lo usen. Más tarde, en 1985, Stallman creó la Free Software Foundation, una organización sin ánimo de lucro con la misión de promover y educar al público de todo el mundo acerca del software libre.

Hoy, el número de gente que no es usuaria de computadores disminuye contínuamente, a medida que la tenología se asienta en todo el globo. Para que esta tecnología funcione son necesarios ciertos conocimientos. La gente que acapara dicho conocimiento, castigando y amenazando a los que tratan de obtenerlo y compartirlo, no lo hace para preservarlo, pese a lo que puedan decir. Muy al contrario, su objetivo es preservar su poder a expensas de la libertad de los demás.

Habiéndose dado cuenta de esto, millones de personas de todo el mundo (incluyendo gobiernos al completo) han decidido utilizar sólo software libre en sus computadoras. El hecho de que tanta gente esté dispuesta a tomar esa decisión (y ponerla en práctica) frente a las ofertas más y más baratas de Microsoft, Apple, y otras empresas que hacen softare privativo, prueba que esas empresas no están en lo cierto: no las necesitamos para hacer software.

GNU software libre.
GNU software libre.

Podemos hacerlo. Lo estamos haciendo.

¿Cómo funciona? ¡Copyleft!

En vista a que las leyes de copyright sobre el software suelen utilizar se para privarnos de nuestra libertad, Stallman y la FSF desarrollaron un documento legal llamado la “GNU General Public License” (GPL) para proteger dicha libertad. En lugar de restringir qué puede hacerse con el software, la GPL nos alienta a estudiar y compartir. Por eso es lo que denominamos una licencia “copyleft”. Miles de personas y negocios, desde amateurs a grandes compañías como IBM y Novell, desarrollan y distribuyen software bajo los términos de la GPL.

Pero qué software utilizar es una elección del usuario, no de la gente que lo programa y lo vende. Es fácil perder nuestra libertad al pulsar el botón “Aceptar” después de pasar rápidamente treinta páginas de restricciones. O podemos pulsar “Cancelar”, y buscar algún programa libre que haga lo que necesitamos.

Debemos pulsar “Cancelar” siempre que podamos, porque es la opción más ética. Hacerlo significa que tendremos que aprender a utilizar un nuevo programa, y algunas veces la alternativa libre quizás no funcione tan bien; la alternativa ética no siempre es la alternativa fácil.

¡Participa!

Puedes comenzar por prometerse a tí misma que buscará alternativas libres a los programas que utiliza. El Free Software Directory (http://directory.fsf.org) contiene una gran lista de programas, más de 5000.

Hay muchas otras formas en las que la gente (con o sin conocimientos de programación) puede ayudar a que el movimiento del software libre continue siendo un éxito. Vea las páginas web de la Free Software Foundation (http://www.fsf.org/es) y el proyecto GNU (http://www.gnu.org) para averiguar cómo.

Y por supuesto, ¡ haga copias de este documento y compártalo con otros!

Reconocimientos de autoría:

Tomado de la página web de la “Free Software Foundation” en asociación estratégica con el proyecto GNU.

 Copyright c© 2000, 2001, 2005, 2006 Free Software Foundation, Inc., 51 Franklin Street, 5th Floor, Boston, MA 02110, USA.

Verbatim copying and distribution of this entire article is permitted in any medium, provided this notice is preserved.

Humor: si no buscas al software libre…

Enlaces consultados:

Enlaces web relacionados (en inglés):