4 Formas de Hacer que tu Código Python sea Multiplataforma

Te has enfrentado alguna vez a la necesidad de que tu código Python corra en diferentes plataformas? Lo has logrado elegantemente? Te propongo en esta entrada examinar algunas respuestas para estas interrogantes.

Una de las cuestiones más recurrentes y que a menudo preocupa y ocupa a los desarrolladores en la industria del software es, sin duda, lograr que su código llegue a ser multiplataforma, es decir, que funcionen sin problemas en todas o en la mayoría de las plataformas disponibles en la actualidad (Windows, Linux, Mac OS, etc.).

Existen infinidad de acercamientos al tema y no hay recetas definitivas. Incluso algunos programadores llegan a desarrollar código independiente para cada plataforma y luego construyen los paquetes correspondientes por separado. Claro está, este proceder resulta un tanto trabajoso y es muy propenso a errores y olvidos.

Ahora bien, ¿cómo puedes lograr que una única base de código funcione correctamente independientemente de la plataforma en la que se ejecute? Continúa leyendo para conocer nuestras respuestas.

Primero lo Primero

Es preciso dejar claro que los requisitos esenciales que debes tener en cuenta si deseas desarrollar aplicaciones multiplataforma, son:

  • Selección del lenguaje de programación a emplear, que en este caso es Python, por supuesto. Existe infinidad de lenguajes de programación allá afuera, ahora bien, si necesitas desarrollar una aplicación que sea multiplataforma, debes asegurarte de seleccionar un lenguaje que esté disponible en todas las plataformas a las que quieres llegar. Python es bien conocido por ser un lenguaje multiplataforma, así que este ya lo tienes cumplido

  • En caso de que tu aplicación requiera de una interfaz gráfica de usuario (GUI por sus siglas en Inglés), debes hacer una adecuada selección de las librerías gráficas y/o frameworks a emplear y de esta forma garantizar la disponibilidad de estos en las plataformas objetivo

  • Finalmente, tu código base (el modelo) debe diseñarse adecuadamente, de modo tal que funcione correctamente en cada plataforma.

Pueden existir muchas otras cuestiones que influyan o definan si tu aplicación será multiplataforma o no, pero los tres puntos anteriores son realmente importantes y definitorios.

Una vez seleccionado el lenguaje y las librerías/frameworks a emplear, puedes centrarte en el diseño y desarrollo del código base.

Empleando el Patrón de Diseño Strategy

El Patrón de Diseño Strategy (Estrategia) permite implementar diferentes soluciones para un mismo problema, cada una de ellas en un objeto diferente. Luego, el código cliente puede elegir qué objeto emplear de forma dinámica en tiempo de ejecución. Esto suena muy adecuado para el lograr desarrollar una aplicación multiplataforma. Cómo hacerlo con Python?

file: strategy.py

from sys import platform

# Strategy Pattern

class Win32Strategy:

    """Strategy class for Windows Platform."""

    def platform_dependent_action(self):

         # Some Windows specific algorithm here

         print('I am running on Windows')

class LinuxStrategy:

    """Strategy class for Linux Platform."""

    def platform_dependent_action(self):

        # Some Linux specific algorithm here

        print('I am running on Linux')

# Client code

platform_map = {'win32': Win32Strategy, 'linux': LinuxStrategy}

obj = platform_map[platform]()

obj.platform_dependent_action()

Listo, si ejecutas este código en Linux obtendrás la salida:

$ python3 strategy.py

I am running on Linux

y si lo ejecutas en Windows:

C:\>python strategy.py

I am running on Windows

Con esto ya lograste que tu código sea multiplataforma, pero existen otras variantes de solución.

Empleando la Combinación Strategy + Factory

Otra variante que puede servir, es el empleo del Patrón de Diseño Strategy combinado con el Factory (Fábrica). El Patrón Factory se emplea cuando necesitas crear un objeto determinado en respuesta a condiciones que no se pueden predecir en el momento en que estas escribiendo el código. Esto viene como anillo al dedo, pues en este caso no se sabe si el usuario ejecutará tu aplicación en Windows, en Linux o en Mac. En la práctica lo puedes hacer de la forma siguiente:

file: strategy_factory.py

from sys import platform

class BaseStrategy:

    """Base Strategy class."""

    pass

class Win32Strategy(BaseStrategy):

    """Strategy class for Windows Platform."""

    def platform_dependent_action(self):

        # Some Windows specific algorithm here

        print('I am running on Windows')

class LinuxStrategy(BaseStrategy):

    """Strategy class for Linux Platform."""

    def platform_dependent_action(self):

        # Some Linux specific algorithm here

        print('I am running on Linux')

# Factory

def generic_factory():

    for subclass in BaseStrategy.__subclasses__():

        if subclass.__name__.lower().startswith(platform):

            return subclass()

# Client code

obj = generic_factory()

obj.platform_dependent_action()

En este caso, primeramente se define una clase base que te servirá de punto de partida para procesar las diferentes subclases o estrategias, aprovechando el método __subclasses__(). Este método retorna una lista con todas las subclases de una determinada clase. Luego se define la función generic_factory(), que itera sobre las subclases en busca de la clase correcta para la plataforma en cuestión y finalmente retornamos el objeto adecuado.

Esta variante de implementación, aunque requiere unas líneas más de código, te aporta mejoras significativas en cuando a la mantenibilidad del código. Por ejemplo, en la implementación anterior para agregar nuevas plataformas, debes agregar la clase correspondiente al Strategy y luego actualizar el diccionario platform_map con esta nueva clase. Por el contrario, en esta última implementación, para añadir nuevas plataformas, solo debes añadir la clase correspondiente al Strategy asegurándote de derivarla de BaseStrategy, y listo, todo funcionará correctamente, sin necesidad de actualizar ninguna otra parte de tu código.

Empleando Despacho Dinámico

El Despacho Dinámico es el mecanismo mediante el cual se escoge, en tiempo de ejecución, el método que responderá a un determinado llamado. Es útil cuando este método no puede ser determinado cuando estas escribiendo el código. Empleando el Despacho Dinámico puedes llegar a la solución siguiente:

file: dynamic_dispatch.py

from sys import platform

class Multiplatform:

    """Multiplatform class."""

    def platform_dependent_action(self):

        getattr(self, platform + '_action')()

    def linux_action(self):

        # Some Linux specific algorithm here

        print('I am running on Linux')

    def win32_action(self):

        # Some Windows specific algorithm here

        print('I am running on Windows')

# Client code

obj = Multiplatform()

obj.platform_dependent_action()

Esta es una solución bien elegante y muy simple, que además tiene una mantenibilidad envidiable. Para agregar nuevas plataformas solo hay que añadir el método correspondiente, asegurándote de incluir el identificador de la plataforma en el nombre del método.

Empleando Bloques if...elif...else

Esta propuesta de solución, aunque no se puede considerar muy pythónica y a pesar de los problemas de mantenibilidad que tiene, se puede encontrar muy a menudo en distintas aplicaciones y librerías, y muchas veces es la primera solución que se te ocurre. Para implementarla puedes escribir:

file: if_elif_else.py

from sys import platform

def linux_action():

     # Some Linux specific algorithm here

     print('I am running on Linux')

def win32_action():

     # Some Windows specific algorithm here

     print('I am running on Windows')

# Client code

if platform == 'win32':

     win32_action()

elif platform == 'linux':

     linux_action()

Como podrás apreciar, es una implementación bien sencilla y cualquiera con conocimientos mínimos de Python y de programación, puede llegar a ella. Sin embargo, tiene la desventaja de que cada vez que necesites agregar una nueva plataforma, debes actualizar tu bloque if...elif...else. En definitiva, no se trata de la solución más elegante, ni más mantenible de todas, pero funciona y casi todos la entienden a la primera.

Bien, esto es todo por ahora, si este artículo te resultó interesante y/o útil, compártelo para que otros también puedan acceder a él. Deja tus comentarios y podremos mejorar nuestros contenidos.

Nos vemos,

lpozo

9 comentarios

Ir al formulario de comentarios

    • jajajaja en 23 diciembre, 2018 a las 9:24 pm

    leodanis….de los libros aquellos de la pagina allitebooks.com…te los has leidos todos ????

    1. Ok, me alegro que te sirviera el link. Hay buenos libros ahí.
      saludos,
      lpozo

    • lorenzoag en 24 diciembre, 2018 a las 6:14 pm

    Hola, me pareció muy interesante este tema y me decidí a tirar unas lineas de código para mostrarles mi manera.

    class PlatformException(Exception):
    pass

    cached_funtion = {}
    def do_platform(platform = None):
    from platform import system
    cached_platform = platform or system()

    def decorator(funtion):
    cached_name = ‘{}_{}’.format(funtion.__name__, cached_platform.lower())
    cached_funtion[cached_name] = funtion

    def wrapper(*args, **kwords):
    real_name = ‘{}_{}’.format(funtion.__name__, system().lower())
    if real_name in cached_funtion:
    return cached_funtion[real_name](*args, **kwords)
    raise PlatformException(‘The platform {} not soported’.format(system().lower()))
    return wrapper
    return decorator

    Como pueden ver me decidí a utilizar un decorador ya que es totalmente reusable tanto para pequeños proyectos como para grandes.
    Pero como se utiliza?

    @do_platform() # Se ejecuta siempre, útil para métodos genéricos o mensajes de error personalizado
    def some_funtion():
    print ‘Hello unknow’

    @do_platform(‘windows’) # Método de Windows
    def some_funtion():
    print ‘Hello Windows’

    @do_platform(‘linux’) # Método de Linux
    def some_funtion():
    print ‘Hello Linux’

    some_funtion() # Se llama a la función y el @decorador escoge la que corresponde a tu sistema.

    Como último quisiera decir que NO SE DEBE CONFIAR en la librería estándar de Python para esto ya que cuando estamos en un sistema sin soporte oficial como (Android, IOS) esta no lo reconoce y cree que estamos en otro aun cuando todos sabemos que cada uno tiene sus peculiaridades.

    1. Ok, genial. Gracias por tu aporte. Tienes razón con lo de la Librería estándar que apuntas al final de tu comentario.
      saludos,
      lpozo

  1. saludos mano estoy montando el server del Battlefield 4 y para hacer q el cliente se comunique con el server hay q crear base de datos en mysql y trabajar con el python, so hay un problemita en el python que es a la hora de insertar estos codigos:

    pip install twisted
    pip install pyopenssl
    pip install pypiwin32
    pip install service_identity

    aqui te dejo el video tutorial para q veas por donde anda la cosa (a partir del minuto 4:00 es q empieza lo de python)

    https://tecn.cubava.cu/files/2018/10/BF4-LAN-setup_1.part1_.rar
    https://tecn.cubava.cu/files/2018/10/BF4-LAN-setup_1.part2_.rar
    https://tecn.cubava.cu/files/2018/10/BF4-LAN-setup_1.part3_.rar

    me gustaria saber porque es que me da error ahi. saludos

    1. mano se me olvido ponerte el error que me da , es algo de conection protocol algo d eso

    2. Ok, revisaré a ver si te puedo ayudar en esto.
      saludos,
      lpozo

  2. Hey leodanis, queria decirte que ya he vuelto a actualizar el blog con cositas básicas de python para noobs, etceteara y ya sabes estamos para lo que se necesite … UN SALUDO
    blog: mundogeek.cubava.cu

    1. ok, gracias hermano.
      saludos,
      lpozo

Los comentarios han sido desactivados.