PythonNET

Crear aplicaciones para Windows
con Python y .NET

Python.NET Logo


INTRODUCCIÓN

Python.NET (pythonnet) es un paquete que permite una integración casi perfecta con .NET Framework 4.0 o superior en Windows (también puede utilizarse en Linux con Mono). Con PythonNET puedes crear scripts de aplicaciones .NET o crear aplicaciones completas en Python.

PythonNET integra el motor CPython con el tiempo de ejecución .NET o Mono.

Con PythonNET puedes, por tanto, utilizar tipos de datos .NET en Python. Para instalar pythonnet ejecuta:

python -m pip install pythonnet

Observa el siguiente código:

>>> import clr
>>> from System import String
>>> text1 = "Hola Mundo"
>>> text2 = String("Hola Mundo")
>>> type(text1)
< class 'str' >
>>> type(text2)
< class 'System.String' >

La sentencia import clr importa la interfaz para interactuar con el CLR (Common Language Runtime) de .NET. A partir de aquí podemos utilizar los tipos de datos de .NET en Python.

La sentencia from System import String es equivalente a using String en C#.

En el ejemplo he creado dos variables: text1 y text2. La primera es un str normal de Python: class 'str'. Pero la segunda es un String de .NET: class 'System.String'

Aparentemente da igual usar una variable u otra, pero los datos almacenados en estas variables son totalmente diferentes:

>>> len(text1)
10
>>> len(text2)
Traceback (most recent call last):
  File "", line 1, in 
TypeError: object of type 'String' has no len()

Como text2 no es un tipo de dato de Python, len(text2) produce un error en tiempo de ejecución. Esto se debe a que los tipos de datos de .NET no tienen un atributo especial __len__. Para obtener la longitud de text2 debes utilizar su atributo Length:

>>> text2.Length
10

No obstante, PythonNET implementa el método __dir__ para todos los objetos de .NET, lo que nos facilita el desarrollo de aplicaciones cuando no sabemos exactamente que atributos de un objeto tenemos que utilizar:

>>> text2.__dir__()
['__repr__', '__hash__', '__call__', '__str__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__iter__', '__init__', '__getitem__', '__setitem__', '__delitem__',
'__new__', '__doc__', '__module__', 'GetHashCode', 'get_Chars', 'Replace', 'Compare', 'Intern', 'Copy', 'ToLowerInvariant', 'LastIndexOf', 'Normalize', 'Format', 'Equals',
'ToLower', 'Concat', 'GetTypeCode', 'Trim', 'Split', 'ToCharArray', 'TrimEnd', 'Substring', 'op_Inequality', 'ToUpperInvariant', 'ToUpper', 'ToString', 'EndsWith', 'Length',
'IsNullOrWhiteSpace', 'LastIndexOfAny', 'CopyTo', 'IsNormalized', 'CompareTo', 'Clone', 'IsNullOrEmpty', 'PadLeft', 'IndexOfAny', 'op_Equality', 'Empty', 'GetEnumerator',
'get_Length', 'PadRight', 'Join', 'Insert', 'StartsWith', 'TrimStart', 'IndexOf', 'Contains', 'Remove', 'IsInterned', 'CompareOrdinal', '__overloads__', 'Overloads', 'GetType',
'Finalize', 'MemberwiseClone', 'ReferenceEquals', '__getattribute__', '__setattr__', '__delattr__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__',
'__format__', '__sizeof__', '__dir__', '__class__']

Como puedes observar, uno de los elementos de la lista devuelta por text2.__dir__() es Length.

Si desarrollas aplicaciones con pythonnet realmente no vas a crear un string .NET a no ser que necesites pasar un argumento de ese tipo a otro objeto .NET.

Puede parecer que esto carece de utilidad pero gracias a PythonNET puedes crear aplicaciones Python cuya interfaz gráfica está hecha en .NET.

CON PYTHON.NET PUEDES CREAR INTERFACES GRÁFICAS EN VISUAL ESTUDIO MEDIANTE DRAG AND DROP PARA UTILIZARLAS EN TUS PROGRAMAS PYTHON.

Has leido bien, puedes crear una interfaz gráfica con Visual Studio arrastrando y soltando, compilarla como librería .dll y scriptearla con Python.

Por ejemplo, en visual studio puedes crear un formulario con un botón, y con python programar lo que sucede al hacer click en dicho botón. Suena bien, ¿verdad? Si es así continúa leyendo


CREAR UNA INTERFAZ GRÁFICA

En PythonNET es posible crear una aplicación completa con Python sin necesidad de utilizar Visual Studio. Podrías crear un formulario y darle toda su funcionalidad desde python. Sin embargo, lo realmente interesante es no tener que preocuparte de programar una interfaz gráfica, aprovechar para ello visual studio y unicamente programar la funcionalidad desde python. Una vez hecho esto puedes distribuir tu aplicación con PyInstaller.

Vamos a crear un pequeño ejemplo para que veas lo que es posible hacer:

Abre Visual Studio y haz click en Crear un proyecto:

Visual Studio - Crear un proyecto

Selecciona Aplicación de Windows Froms (.NET Framework):

Visual Studio - Aplicación de Windows Forms

En Nombre del proyecto escribe "ejemplo". Asegurate de que en el campo Framework está seleccionado .NET Framework 4 o una versión posterior:

Visual Studio - Configuración del Proyecto

Al finalizar se abrirá el IDE de Visual Studio:

Visual Studio - IDE

Fíjate en el IDE: Lo primero que vemos es un formulario vacío cuyo título es Form1. A su izquierda y en vertical puede leerse el texto Cuadro de herramientas (si haces click en él se abrirá un menú que permite añadir widgets/controles al formulario).

A la derecha del IDE, en la parte superior está el Explorador de solucines (donde se ve la estructura del proyecto), y en la parte inferior está el menú de propiedades (muestra las propiedades del widget* seleccionado).

Haz click en el formulario para seleccionarlo. Ahora las propiedades mostradas en el menú de propiedades de la derecha son las de Form1.

Cambia la propiedades Text de Form1 para que el título del formulario sea "Ejemplo":

VS - Cambiar título del Formulario

A continuación, a la izquierda, haz click en Cuadro de herramientas y busca Button:

VS - Cuadro de herramientas - Button

Haz doble click en Button o arrastralo hacia el formulario Form1 para añadirlo al formulario:

VS - Formulario con un botón

Puedes mover el botón de posición y puedes cambiar sus dimensiones de la misma forma que redimensionas una ventana en Windows. También puedes redimensionar el tamaño del formulario. Yo lo he dejado finalmente así:

VS - Formulario seleccionado

Observa que en la imagen anterior está seleccionado el formulario, de modo que las propiedades mostradas en el menú de propiedades son las del formulario Form1 (con título "Ejemplo").

Selecciona el botón haciendo click en él:

VS - Botón seleccionado

Ahora que el botón está seleccionado, las propiedades mostradas en el menú de propiedades son las del botón Button1. Cambia el texto de Button1:

VS - Propiedades Botón - Text

Al cambiar Text y presionar enter se habrá actualizado el texto del botón Button1:

VS - Formulario - Botón Saludar

Aunque hemos cambiado el texto del botón a "Saludar" yo sigo refiriendome a él como Button1. Esto es porque el texto del botón no es su identificador, es decir, Button1 sigue siendo el nombre de la variable que apunta al botón en el código fuente.

El identificador se define a través de la propiedad (Name) del menú de propiedades. Cambia Button1 por boton_saludar:

VS - Propiedad Button Name (identificador)

En nuestro código Python nos refiriremos a este botón como boton_saludar, pero este botón todavía no es accesible desde Python: para hacer que un objeto .NET sea accesible por PythonNET hay que hacerlo público. Esto se hace estableciendo el valor del campo Modifiers del menú de propiedades como public:

VS - Propiedades Botón - Modifiers: public

Si el formulario tuviese mas widgets habría que hacer que todos sean públicos si queremos manipularlos en el script python.

Ya hemos terminado de editar la interfaz gráfica del programa, pero antes de trastear desde python hay que configurar algunas cosas mas.

En el menú superior del IDE haz click en COMPILAR y ve a Administrador de configuración...:

VS - COMPILAR - Administrador de configuración

En el campo Configuración de soluciones activas por defecto está seleccionado Debug, cambialo a Release. A continuación, en el campo Plataforma de soluciones activas crea una nueva:

VS - COMPILAR - Adeministrador de configuración - Nueva Plataforma

Antes de continuar revisa tu instalación de Python. En mi caso, si abro Python aparece lo siguiente:

CMD - python

Mi instalación de Python es la versión de 64bits (la misma arquitectura que la del sistema). Si tu sistema es de 64bits y tu instalación de Python fuese de 32bits deberías instalar la versión de 64bits, ya que este es causa frecuente de que aplicaciones distribuidas con PyInstaller sean detectadas como maliciosas.

Dicho esto, continuamos creando la nueva plataforma de soluciones activas. Se habrá abierto el cuadro de diálogo de Nueva plataforma:

VS - Nueva plataforma de solución

En el campo Escriba o seleccione la nueva plataforma selecciona x64 (o x86 si tu equipo e instalación de python son de 32bits). Haz click en el botón Acepar para aplicar los cambios.

Finalmente, en el Administrador de configuración, el proyecto "ejemplo" está configurado como "release" para la plataforma "x64":

VS - Administrador de configuración - Release, x64

Si ahora compilamos el proyecto obtendremos un ejecutable .exe, pero lo que queremos es una librería .dll. En el menú superior ve a PROYECTO - Propiedades:

VS - PROYECTO - Propiedades

En el campo Tipo de aplicación selecciona Biblioteca de clases:

VS - PROYECTO - Propiedades - Tipo de Aplicación (Biblioteca de clases)

Ya puedes compilar el proyecto como librería DLL: presiona Ctrl+B o en el menú superior ve a COMPILAR - Compilar ejemplo:

VS - COMPILAR - Compilar proyecto

El progreso de la compilación se puede observar en la parte inferior del IDE. Una vez finalizada la compilación el IDE lo notifica:

VS - Compilación correcta

Ahora ve al Explorador de solucines (en la parte superior de la derecha del IDE), haz click derecho en "ejemplo" y a continuación en Abrir carpeta en el Explorador de archivos:

VS - Explorador de soluciones - Abrir carpeta

Se abrirá la carpeta que contine el proyecto. Dentro del proyecto "ejemplo" navega al subdirectorio bin\x64\Release el archivo ejemplo.dll es la librería que contiene el formulario con un botón que hemos creado anteriormente.

Archivo ejemplo.dll

NOTA: El archivo ejemplo.dll es multiplataforma, es decir, aunque lo has compilado en windows con visual studio puedes utilizarlo en linux/mono (no tienes que recompilar para utilizarlo en linux).


DAR FUNCIONALIDAD AL PROGRAMA CON PYTHON.NET

Crea un directorio, por ejemplo: C:\miprograma.

En su interior copia el archivo ejemplo.dll y crea un archivo program.py.

Escribe el siguiente código para program.py:

import clr

clr.AddReference('ejemplo')

from System.Windows.Forms import *
from ejemplo import Form1

app = Form1()
Application.Run(app)

Ejecuta el script program.py y observa lo que sucede:

Aplicación Ejemplo (Screenshot)

¡¡ ACABAS DE ABRIR EL FORMULARIO DESDE PYTHON !! Esto ya es bastante sorprendente de por si, pero el programa todavía no hace nada. Si haces click en el botón Saludar no sucede absolutamente nada, pero enseguida le daremos funcionalidad.

Antes vamos a analizar el código de program.py:

arquitectura de la instalación de Python (x86 o x64).

accederse a librerías .NET como si de módulos python se trataran (equivale a using System.Windows.Forms en C#).

Ahora que comprendemos el código vamos a darle funcionalidad al botón. Define una función saludar:

def saludar(sender, e):
    MessageBox.Show('Hola, soy un mensaje Python! :D')

Esta función toma dos argumentos: sender y e, que son necesarios para utilizar la función como manejador de eventos.

La función saludar va a ser un manejador del evento Click del botón.

En visual studio habíamos establecido que el identificador del botón es boton_saludar. Podemos acceder al botón mediante app.boton_saludar.

Para convertir la función saludar en un manejador del evento click del botón:

app.boton_saludar.Click += saludar

Se utiliza el operador += porque un evento puede tener varios manejadores asociados.

El script program.py queda así:

import clr

clr.AddReference('ejemplo')

from System.Windows.Forms import *
from ejemplo import Form1

def saludar(sender, e):
    MessageBox.Show('Hola, soy un mensaje Python! :D')

app = Form1()
app.boton_saludar.Click += saludar
Application.Run(app)

Ejecuta program.py y haz click en el botón Saludar:

Aplicación Ejemplo - MessageBox

No tengo palabras, simplemente...

Hemos creado una interfaz gráfica en visual studio y nuestro programa python es capaz de utilizarla y darle funcionalidad!!

Al hacer click en el botón Saludar aparece un MessageBox, que pertenece a System.Windows.Forms.


OOP CON PYTHON.NET

Podemos crear aplicaciones realmente complejas y a la vez escalables con PythonNET, ya que permite realizar programación orientada a objetos sobre tipos de datos .NET. Esto de pythonnet no es ningún juguete...

Por ejemplo, el programa anterior podríamos reescribirlo así y funcionaría exactamente igual:

import clr

clr.AddReference('ejemplo')

from System.Windows.Forms import *
from ejemplo import Form1


class App(Form1):

    def __init__(self):
    	super().__init__(self)
    	self.boton_saludar.Click += self.saludar

    def saludar(self, sender, e):
    	MessageBox.Show('Hola, soy un mensaje Python! :D')


app = App()
Application.Run(app)

DISTRIBUCIÓN DE APLICACIONES

Ahora que has creado tu primera aplicación con Python.NET te preguntarás si es posible convertir tu aplicación en un ejecutable (.exe). Puedes hacerlo como de costumbre con PyInstaller.

Si quires crear un standalone, es decir, que tu programa sea un solo archivo, y además quieres ocultar la consola de comandos tienes que ejecutar:

pyinstaller --onefile --windowed --add-data "ejemplo.dll;." program.py

El argumento --onefile hace que se genere un standalone, y el argumento --windowed significa que la aplicación tiene interfaz gráfica y queremos ocultar la consola de comandos al ejecutar el programa.

Con el argumento --add-data "ejemplo.dll;." añadimos la librería que contiene el formulario dentro del .exe.

Pero antes de compilar de esta forma tienes que hacer una pequeña modificación en el programa:

Se creará un directorio dist que contiene program.exe. Si lo ejecutas comprobarás que funciona perfectamente y la consola de comandos permanece oculta.

¿que hemos hecho?

Al compilar con --onefile y ejecutar program.exe, el bootloader extrae todos los archivos dentro de program.exe en un directorio temporal antes de ejecutar el programa. Cada vez que ejecutes el programa se utilizará un directorio temporal diferente. Podemos conocer cual es este directorio temporal mediante sys._MEIPASS.

En este directorio temporal se extrae, entre otros, el archivo ejemplo.dll. Si no ejecutas os.chdir(sys._MEIPASS) al inicio del script se producirá un error, ya que el directorio de trabajo y el directorio temporal no son el mismo, es decir, os.getcwd() != sys._MEIPASS.

Realizamos, por tanto, un cambio de directorio de trabajo cuando el programa se ejecuta como program.exe (y no realizamos este cambio cuando lo ejecutamos como program.py, es decir, cuando no existe sys._MEIPASS):

if hasattr(sys, '_MEIPASS'):
    os.chdir(sys._MEIPASS)

Si compilases la aplicación sin --onefile no tendrías que hacer nada de esto. Quería aclararlo porque en su día me costó mucho encontrar esta información.


ESPERO QUE HAYAS DISFRUTADO LEYENDO ESTE ARTÍCULO. Tan pronto como pueda seguiré escribiendo sobre PythonNET.

A continuación te dejo unos enlaces a dos de los programas que hice con pythonnet, por si quieres echarles un vistazo:

Para Easy PDF Two Sided la interfaz fue creada en Visual Studio y la funcionalidad fue programada en python gracias a pythonnet.

Las primeras versiones de LoL Absent utilizaban PythonNet pero posteriormente migré la aplicación a IronPython, ya que el código de una aplicación pythonnet es código válido para una aplicación ironpython (teniendo en cuenta que ironpython es python2). La razón por la que cambié a IronPython es que PyInstaller frecuentemente crea ejecutables que son detectados como virus.

Si quieres continuar puedes crear una calculadora básica para Windows con PythonNET o un cronómetro con interfaz WinForms.


¡¡ HASTA LA PRÓXIMA !!