Introducción a Python 3#
¿Qué es Python?#
Es un lenguaje de programación interpretado, desarrollado por Guido Van Rossum. Se trata de un lenguaje que soporta varios paradigmas de programación: programación orientada a objetos, programación imperativa, programación funcional y, recientemente, programación asíncrona.
Versiones de Python#
Como muchos otros lenguajes y software en general, la versión de Python viene determinada por la versión major y la minor. Se distinguen de forma muy marcada Python 2 y Python 3. La versión 2 dejó de tener soporte de seguridad desde hace unos años, por lo que nos centraremos en la versión 3.
Dentro de cada versión, la versión minor nos indica si están soportadas nuevas características del lenguaje. A la escritura de este manual, la versión más reciente es Python 3.10.7.
Especificación del estándar#
El desarrollo de Python está fuertemente dirigido por la comunidad a través de documentos llamados Python Enhancement Proposals o PEP. En ellos se documentan los acuerdos llegados para definir diferentes aspectos del lenguaje, como el PEP 100 que especifica la integración de Unicode en PYthon, y algunos en clave de humor, como el PEP 20, The Zen of Python.
El intérprete de Python#
Llamamos intérprete a cualquier programa que sea capaz de, tomando como entrada un programa escrito en Python, reproducir su ejecución. El intérprete por defecto y más habitual de encontrar es CPython, el cuál se distribuye bajo una licencia libre.
CPython es la implementación que es distribuída desde la web oficial de Python y la que suele ser distribuida por las diferentes distribuciones de GNU/Linux en sus sistemas de empaquetado.
Al ser software libre, CPython soporta multitud de plataformas, como Linux, Windows, BSD, Darwin, MacOS e incluso algunas plataformas como PS3, XBMC o Nintendo DS. Las plataformas soportadas están documentadas en el PEP 11.
También existen intérpretes alternativos que pueden ser instalados y utilizados:
Iron Python: implementado en C# e integrado en la plataforma .NET.
Jython: implementado en Java, permite ejecutar programas Python en la JVM.
PyPy: intérprete implementado en… ¡Python!
Nuitka: compilador (no intérprete), escrito en Python y que permite generar ficheros ejecutables binarios para la plataforma deseada.
MicroPython: desarrollado específicamente para ser utilizado en microcontroladores.
Introducción al lenguaje#
En este apartado no vamos a hablar de cómo desarrollar en Python desde 0, ya que es un lenguaje que ya se ha utilizado con anterioridad en la carrera. Sin embargo, si que haremos hincapié en algunas características del lenguaje que nos serán imprescindibles para su uso en la asignatura de Sistemas Distribuidos.
Definición de clases#
Como mencionamos anteriormente, Python es un lenguaje que permite utilizar la programación orientada a objetos.
Para definir una clase:
class MyClass:
def method1(self):
pass
def method2(self, argument):
pass
Con el código anterior se definirá una clase llamada MyClass
, con 2
métodos diferentes. El primero no aceptará ningún argumento, y el segundo,
aceptando un argumento. Como puede verse, ninguno de los 2 métodos define
ni el tipo de retorno del mismo ni, en el caso del segundo, el tipo que
debe tener el parámetro argument
. Ésto se debe a que en Python no se
comprueban en ningún momento (ni en tiempo de compilación ni de ejecución)
los valores devueltos por una función o método, ni los tipos de los
argumentos que se van a pasar. Hay que ser cuidadoso con ello, ya que de
utilizar un parámetro de un tipo diferente al que el método espera recibir
puede provocar errores en tiempo de ejecución.
Creando una instancia#
Para crear una instancia de la clase definida en el ejemplo anterior, tenemos que realizar lo siguiente:
instance = MyClass()
A partir de ese momento, la variable instance
contendrá una referencia a
un objeto de tipo MyClass
, por lo que podemos invocar a cualquiera de sus
métodos o utilizar sus atributos.
Siempre que instanciamos un objeto, se llevan a cabo 2 procesos diferentes:
Creación de la nueva instancia, a través del método especial
__new__
.Inicialización de la nueva instancia, a través del método especial
__init__
.
Mientras que el primero nos devuelve una instancia vacía, el segundo se encarga de añadir los atributos necesarios a la instancia, siendo éste el que normalmente utilizaremos en nuestras clases para definir sus atributos.
El argumento self
#
Si nos fijamos bien en el ejemplo anterior, ambos métodos tienen definido
un primer argumento denominado self
. De forma muy general, podemos
considerar a self
el equivalente al this
de Java: es la referencia a la
instancia de la que vamos a ejecutar el código.
Atributos de una clase#
Las clases, además de métodos, pueden tener atributos. Éstos atributos son los que harán que 2 diferentes instancias sean, en efecto, diferentes. Los atributos de una clase pueden ser estáticos (creados en la definición de la clase) o dinámicos (creados en el inicializador de la clase).
En cuanto a la visibilidad, en Python no existen los atributos privados como tal, aunque si que existen algunos mecanismos y convenciones que permiten, hasta cierto punto, hacer privados los atributos:
Si un atributo comienza por un
_
(guión bajo o underscore), se considera que el atributo no debe ser utilizado directamente por los usuarios de la instancia. Se trata de una simple convención, por lo que el intérprete de Python no realiza ninguna acción.Si un atributo comienza por
__
(doble underscore), Python realiza una modificación denominada “name mangling”, en la cual se modifica el nombre del atributo de manera que los usuarios de las instancias no tengan ningún tipo de acceso sobre él de manera directa. Si se conoce bien el mecanismo, es posible acceder a los atributos declarados de esta manera, pero al igual que en el caso anterior, no se debe.
A continuación se muestra una modificación de nuestra MyClass
para añadir
diferentes atributos:
class MyClass:
def __init__(self):
self.public = "this is a normal attribute"
self._dont_use = "this is a normal attribute, but please don't use it"
self.__hidden = "this attribute will have name mangling"
def show_attributes(self):
print(f"Internal access to attributes in {self.__class__.__name__}")
print(f"normal = {self.normal}")
print(f"don't use = {self._dont_use}")
print(f"hidden = {self.__hidden}")
a = MyClass()
a.show_attributes()
print(f"Direct access to attributes in {a.__class__.__name__}")
print(f"public={a.public}")
print(f"don't use={a._dont_use}")
print(f"internal={a._MyClass__hidden}")
Cómo se puede ver en el ejemplo, en el método show_attributes
la
instancia es capaz de acceder a los atributos con el nombre con el que han
sido definidos, mientras que fuera de ese ámbito necesitamos hacer uso
del nombre “privado” del atributo __hidden
para poder imprimir su valor.
Herencia y polimorfismo#
Como lenguaje orientado a objetos, la herencia es un concepto muy utilizado en Python. De hecho, una clase Python puede heredar de varias clases padres simultáneamente, aunque es una funcionalidad que no se utiliza demasiado.
Para definir que una clase hereda de nuestra MyClass
haremos lo
siguiente:
class ChildrenClass(MyClass):
pass
El concepto de polimorfismo en Python es algo diferente al mismo en lenguajes de tipado estático como Java. Al no haber ningún tipo de verificación de tipos en tiempo de compilación, se basará simplemente en lo que denominamos duck typing.
El concepto de duck typing se puede resumir en
Note
Si anda como un pato y grazna como un pato, entonces debe ser un pato
Esta frase resume muy bien el concepto de polimorfismo en Python: si un objeto es capaz de responder a una serie de invocaciones a métodos pasándole una serie de parámetros válidos, independientemente de que esté definido formalmente como una herencia de clase o no, podremos utilizar ese objeto de esa manera.
Estructura típica de un programa Python#
Un programa Python está definido en uno o varios ficheros de texto plano que cumplen con la sintaxis de Python. Cuando ejecutamos el intérprete de Python pasándole un fichero como entrada, el intérprete cargará dicho fichero y tratará de ejecutarlo.
Entrypoints#
En otros lenguajes estamos habituados a que se introduce un método o
función con un nombre determinado que actuará como punto de entrada de la
ejecución del programa. Por ejemplo, la función main
en lenguajes como C,
C++ o Golang, un método público y estático llamado main
en una clase
Java…
En Python sin embargo no existe ninguna convención al respecto: el código que se ejecutará será el que se encuentre en el fichero que se pase al intérprete sin ninguna identación.
print("Mi primer programa Python")
Si escribiéramos en un fichero el código anterior y pasáramos el fichero al intérprete de Python:
$ python3 miprograma
Mi primer programa Python
Sin embargo, este tipo de programas, aunque nos puedan servir para empezar a trabajar en Python, no son muy mantenibles a largo plazo, ya que el código sin identar se ejecutará también cuando otro fichero Python quisiera importar alguna de las funciones que tuviéramos definidas. Por ello es habitual añadir el código que consideramos parte del entrypoint a una función y ejecutarlo únicamente cuando el fichero se está ejecutando desde un intérprete de forma directa:
def entrypoint():
print("Mi primer programa Python")
if __name__ == "__main__":
entrypoint()
El código anterior cargará la función entrypoint
, sin ejecutarla, y
ejecutará el bloque if
. La variable __name__
es una variable especial
que el intérprete de Python inyecta a nuestro programa. Si el módulo donde
estamos escribiendo esto ha sido ejecutado directamente por el intérprete,
y no como parte de una secuencia de importación, el valor será la cadena
"__main__"
, por lo que de ser así, ejecutaremos la función entrypoint
.
Shebangs#
Un shebang es una catacterística típica de los terminales que cumplen
con el estándar POSIX que permite indicar, desde el propio fichero, el
intérprete que debe ser utilizado para ejecutar el fichero. Debe ser
especificado en la primera línea del fichero y comenzar con un símbolo de
#
seguido de una admiración !
y la ruta al intérprete que debe
ejecutarlo.
#!/usr/bin/python3
def entrypoint():
print("Mi primer programa Python")
if __name__ == "__main__":
entrypoint()
Al hacerlo de esta manera, si damos permisos de ejecución al fichero, podríamos lanzarlo sin necesidad de invocar al intérprete.
Note
Es bastante habitual que cuando queremos lanzar nuestro programa Python,
tras haberle dado permisos de ejecución, que el sistema se quede
“congelado” y el cursor se convierta en una cruceta. Si ésto te ocurre, se
debe a que has olvidado definir el shebang y el sistema está intentando
ejecutar tu programa como si se tratara de un script en Bash. El cambio de
cursor se debe a que casi siempre las primeras líneas de nuestros programas
Python incluyen import
, que además de ser la palabra reservada en Python
para importar otros módulos, es un comando de línea de comandos que permite
hacer capturas de pantalla.
Modularización en Python#
Como se ha mencionado de pasada anteriormente, Python permite importar
funcionalidad disponible en otros ficheros Python a través de dos elementos
de su sintaxis: import
y from ... import ...
.
import module
permite añadir al espacio de nombres de nuestro módulo el módulomodule
y utilizar cualquier función definida en él a través demodule.something
from module import function
permite añadir al espacio de nombres de nuestro módulo únicamente un símbolo de los definidos enmodule
, en este caso, el símbolofunction
.
La modularización en Python es muy importante ya que nos permite utilizar tanto funcionalidades de la librería estándar de Python como de librerías de terceros. También nos permite organizar nuestro código mejor, manteniendo una estructura en nuestro programa que permita mejorar la legibilidad y el mantenimiento de nuestro software.