Apuntes de Python 3 (1ª Parte)

28 junio 2016 at 19:41 by Adrián Pérez

python3
Bueno, este artículo contiene una serie de apuntes que me han parecido interesantes, referentes a un curso sobre Python 3. Así pues, no es ni mucho menos, un curso de Python 3, pero con suerte le será de utilidad a alguien (además de a mí mismo). De momento, aquí el primer post que he creado mientras he ido avanzando en el curso.

1. Introducción

1.1. ¿Por qué Python?

  • Escritura eficiente: requiere escribir menos código que otros lenguajes
  • Lenguaje de alto nivel y propósito general
  • Funciona bajo cualquier sistema operativo
  • Popular y gratuito
  • Cercano al pseudo-código, enfatiza la legibilidad

1.2. ¿Qué es Python?

  • Soporta múltiple paradigmas de programación (orientación a objetos, funcional, etc.)
  • Type checking done at runtime
  • Gestión automática de la memoria / garbage collector
  • Modular (core pequeño, extensible con módulos)
  • El código python puede ser paquetizado en un único ejecutable listo para distribuir (ej. dropbox)
  • Open-Source
  • Uno de los objetivos más importantes de los "pythoneros" es que sea un lenguaje divertido de usar
  • Creado por Guido van Rossum (aka BDFL)

Implementaciones más importantes

  • CPython: escrito en C, compila programas python en bytecode intermedio (.pyc) listo para ser ejecutado por una máquina virtual. Es la implementación que se suele usar por defecto.
  • PyPy: python escrito en python. Objetivo: velocidad (en el interprete de python)
  • Jython: compila en Java bytecode, para ejecutarse en cualquier JVM. Puedes usar librerias de Java desde el código Python.

Por ejemplo, si tenemos código Java ya hecho, y queremos extender este código, pero no sabemos Java (o estamos más cómodos con Python) podemos usar Jython para importar las clases existentes Java y extender sus funcionalidades con nuevo código Jython.

Otro concepto importante, es el código Python debe seguir la filosofía "pythonica", es decir, que siga las guías para expresar lo que quiere de la mejor forma posible: "debería haber una (y preferiblmente sólo una) forma obvia de hacerlo".

1.3. Python 2 vs Python 3

En py3readiness.org se encargan de listar cuales de los 360 módulos más populares de Python están listos para funcionar con Python 3. A día de hoy, 307/360.

Podemos migrar código Python 2 a 3 con la librería 2to3, y al revés con 3to2.

1.4. Gestor de paquetes de Python

El gestor de paquetes de Python recomendado es "pip" (Python Package Index), el cual suele incluirse con la propia instalación de Python. Tenemos un listado de los paquetes disponibles por aquí.

Un comando interesante es "pip freeze" el cual lista todos los paquetes actualmente instalados, con sus versiones.

pip freeze > requirements.txt

2. Python

2.1. Ejecutando un script python

a) Directamente, pasándole como parámetro el script al intérprete

python3.4 myscript.py

b) Haciendo ejecutable nuestro script tras añadirle el shebang correspondiente.

[adri@localhost ~] head -1 myscript.py
#!/usr/bin/env python3.4
[adri@localhost ~] chmod u+x myscript
[adri@localhost ~] ./myscript.py

NOTA: Python checkea el código en tiempo de ejecución, no en tiempo de compilación, excepto para errores de sintaxis (por ejemplo si usas la función "print" sin acordarte de los paréntesis). Ésto es importante tenerlo en cuenta, pues si tienes por ejemplo un "if", sólo se checkeará el código de la "rama" del if a la que entre, pero no el resto.

2.2. Módulos

Puedes incluir otros scripts python ubicados en tu mismo directorio (que serán módulos para nosotros) con "import".

import myotherfile

Estos otros módulos, no tienen porqué importarse siempre desde otro script, si no que podrían poderse ejecutar directamente como un script standalone (sin que le llame ningún otro script). Para ello, podemos usar el siguiente if desde el módulo a importar para definir un comportamiento diferente si se está llamando con un import desde otro script o si se está ejecutando directamente.

# Ejecutamos este código en cualquier caso
print('Esto se ejecutará siempre')
 
# Definimos la función main
def main():
   print('Estoy en standalone')
 
# Sólo si estoy en standalone, llamo al main
if __name__ == "__main__":
   main()

NOTAS:

  • Este módulo, una vez importado, nos permitirá en cualquier caso llamar a la función main desde el script que lo ha importado, pues no deja de ser una función más del módulo. La función se llamará via el namespace del módulo, tal que así: myotherfile.main().
  • Tras la primera ejecución de nuestro script, el cual importa un módulo, el módulo importado quedará cacheado y almacenado (ya compilado, y por tanto se tratará de un fichero bytecode .pyc, pero aun así ejecutable) dentro del directorio __pychache__. Sólo se cachearán los módulos importados.

3. Aprendiendo python

3.1. Variables

Una característica muy chula de python es que puedes asignar el valor de varias variables en una sóla línea, tal que así:

a, b = 0, 1
a, b = b, a+b

Hay que tener en cuenta, pero, que el código anterior da como resultado a=1, b=1, pues en el momento de ejecutar la segunda línea, a=0 y b=1.

Variables inmutables

Al cambiar el valor de una variable de este tipo, en realidad se está destruyendo el objeto y creando uno nuevo. La variable tendrá pues, un nuevo "id" al cambiarle el valor.

>>> a=3
>>> id(a)
139803787806208
>>> a=a+1
>>> id(a)
139803787806240

Son inmutables las variables de tipo primitivo, como ints, floats o strings.

Variables mutables

Las variables mutables, en cambio, pueden cambiar de valor manteniendo el objeto. Por defecto, este tipo de variables deben entenderse como "etiquetas" o "alias" sobre un valor. Así pues, asignar la variable x a la y, significa que y será un alias de x, y por tanto, ambas apuntarán al mismo valor.

>>> x = [a,b,c]
>>> y = x
>>> x.append[d]
>>> y
[a,b,c,d]

Si se quiere hacer una copia de una variable mutable en otra, se puede hacer una "shadow copy" o una "deep copy" (explicado al final del post).

Son mutables, por tanto, los tipos de dato más complejos, como los arrays.

Más sobre in/mutabilidad

3.2. Tipos básicos

Números

Podemos usar las expresiones típicas para realizar operaciones, como suma (+), resta (-), multiplicación (*), división (/), potencias (**), resto -o "mod"- (%) y división entera (//).

Por defecto, una división no entera devolverá un float.

Strings

Un string se define por comillas simples, dobles comillas, o comillas triples (para múltiples líneas sin necesidad de \n). Escapamos, como es habitual, con "\".

Podemos usar la función print() para printar los carácteres especiales, como:

print("esto es una linea\ny esta es otra línea")

También podemos no tratar los carácteres especiales y printar el string tal cual:

print(r"Esto se printará e\n una única línea")

a) Concatenación

Concatenamos strings con "+".

b) Indexación

Podemos "indexar" strings, es decir, referirnos a un carácter concreto en función de su posición en el string.

>>> a='Hola'
>>> a[0]
'H'

También contando hacía atrás.

>>> a[-1]
'a'

c) Substrings (+ rangos)

Con un rango en la indexación conseguimos un substring. Atención, el inicio del rango se incluye, pero el fin se excluye. Si no se especifica uno de los 2 valores del rango, se cojerá lo que pertoque (inicio o fin).

>>> a='Cadena'
>>> a[0:2]
'Ca'
>>> a[:3]
'Cad'

Tenemos la opción adicional del "slice", como tercer parámetro del "rango", que nos permite seleccionar cada "n" posiciones del string. El siguiente ejemplo devolvería las posiciones pares de todo el string:

>>> a='Cadena'
>>>print(a[1:0:2])
'Cdn'

d) Inmutabilidad
Los strings son inmutables, así que no puedes modificarlos directamente:

>>> a='Cadena'
>>> a[0] = 'K'
TypeError

Listas

Podemos tener listas que incluyan diferentes tipos de datos, e incluso listas anidadas:

>>> lista = [1, 2, 3, 4]
>>> lista = [1, 2, "tres", 4]
>>> lista = [1, 2, [2.25, 2.5, 2.75], 3, 4]

Igual que con los strings, podemos referirnos a un elemento de la lista por su posición, o a una sublista (lo cual, en realidad devuelve una nueva lista). Para referirnos a un elemento de una lista anidada, lo haremos:

>>> lista = [1, 2, [2.25, 2.5, 2.75], 3, 4]
>>> lista[2][0]
2.25

Las listas son mutables, con lo que podemos modificarlas refiriéndonos a una posición o rango concreto, variando también su tamaño.

3.3. Estructuras

If
Así iría un if con sus diversas posibilidades:

if x < 0:
    print("x menor que cero")
elif x == 0:
    print("x igual a cero")
else:
    print("x mayor que cero")

No hay switch/case en Python. Usaremos "elif" como sustituto.

While
Tampoco tiene historia el bucle while:

i = 1
while i < 10:
    print(i)
    i = i + 1

For
En un bucle for, iteraremos por los diferentes elementos de un objeto.

words = ['uno','dos','tres']
for word in words:
    print(word)

Si se quiere iterar sobre un objeto, que hemos de modificar dentro del bucle, quizá nos interese iterar sobre el objeto original (sin modificar) pese a que se modifique dentro del bucle. Para ello, podemos hacer una copia del objeto con "[:]", lo cual nos permitirá que pese a que durante el bucle, modifiquemos el objeto (el cual quedará modificado tras salir del bucle) las iteraciones se realizarán sobre el objeto original.

i = 0
words = ['uno','dos','tres']
for word in words[:]:
    print(word)
    if word == 'tres':
        words.append('cuatro')
 
print(words,len(words))

Ésto dará como resultado:

uno                         
dos                                   
tres                                   
['uno', 'dos', 'tres', 'cuatro'] 4

Especialmente interesante es saber que un "for" puede tener su propio "else", que se ejecutará cuando la lista por la que itera el for se termine, pero no se ejecutará si el for acaba por un "break". Dentro del "for" también podemos tener "continue" el cual salta directamente a la siguiente iteración del bucle sin acabar lo que restara de la iteración actual.

NOTA: Otra declaración interesante es "pass" la cual en realidad no hace nada, pero nos puede venir bien si por ejemplo necesitamos declarar una función pero no vamos a implementarla ahora, o una clase vacía (una clase no puede estar vacía, de ahí que usemos "pass").

3.4. Funciones

Definiremos una función con la clave "def", seguido del nombre de la función y los parámetros entre paréntesis, como viene siendo habitual. Deberemos definir una función antes de poder usarla (en python el código se ejecutará de forma secuencial, así que obtendrás un error si realizas la llamada a la función antes de la definición de la misma).

def myfunction(var_1,var_2):
   """My docstring"""
   #Something
   return var_3, var_4

NOTA: Podemos devolver más de una variable a la vez.

Opcionalmente (pero recomendable, si queremos hacer las cosas bien en python) las primera línea de código dentro de la función conformarán el docstring que no es más que una descripción del propósito de la función.

Esta información que proporciona el docstring se podrá recuperar con "help(myfunction)" o "print(myfunction.___doc___)". Hay herramientas que generan documentación a partir de los docstrings. Tenemos las siguientes convenciones:

  • La primera línea del docstring será un resumen del propósito de la función, seguido de una línea en blanco
  • A partir de ahí, pasaremos a describir en detalle la función

Respecto a las variables dentro de una función, podemos usar la clave "global" al referirnos a una variable global definida anteriormente (y fuera de la función), con tal de poder asignarle un nuevo valor dentro de la función. No hace falta usar la clave "global" para definir una variable global si únicamente queremos consultar su valor.

a="A es global"
b="B es global"
 
def myfunction():
   """"Testing purposes"""
   print(a)   # Printará "A es global"
   global b
   b="B es global y he modificado su valor"
   print(b)   # Printará "B es global y he modificado su valor"
 
print(a)   # Printará "A es global"
print(b)   # Printará "B es global y he modificado su valor"

Si en el ejemplo anterior no usaráramos la clave "global", la función estaría símplemente usando una variable local llamada, casualmente, igual que la variable global b. Si has programado antes, ésto no tiene misterio.

Argumentos de la función

Respecto a los argumentos en las funciones, si pasamos argumentos que son variables immutables y los modificamos dentro de la función, al ser immutables, se estará creando un nuevo objeto dentro de la función que tendrá ámbito local, por lo tanto, la modificación sólo afectará localmente a la variable dentro de la función. Sin embargo, si pasamos como argumentos variables mutables, estaremos pasando la referencia al objeto, y si modificamos la variable dentro de la función, esta modificación se realizará sobre el objeto original con lo cual la modificación persistirá aun cuando salgamos de la función.

s = [0, 1]
 
def myfunction(s):
   s[1]=2
 
print(str(s))   # Printará "[0, 2]"!!!

NOTA: Todo en python es un objeto, incluso las funciones. Puedes asignar una función a otra (newfunc = myfunc) y lo que estarás haciendo en realidad es crear un alias, igual que con las variables immutables.

Si tienes ganas de más puedes iré publicando aquí el resto de posts del grupo de apuntes:

  1. Apuntes de Python 3 (2ª Parte)

NOTAS FINALES:
A. Python permite hacer asignación de múltiples variables a la vez, tal que así:

a, b = 1, 2

Cualquier entero diferente de cero es "True". También cualquier tipo de datos de tamaño mayor que cero. Por tanto, cero es "False", así como cualquier tipo de dato de tamaño 0 (como un array vacío).

B. PEP8 es el estándar de formato de Python.
Komodo Edit (la versión gratuita de Komodo) como IDE + Addon "Perfect Python" para el PEP8.
O usando Komodo Edit 9 > http://forum.komodoide.com/t/enable-pep8-in-komodo-edit-9/1740

Más:
https://docs.python.org/3/reference/
http://forum.komodoide.com/t/enable-pep8-in-komodo-edit-9/1740

C. Shadow copy y deep copy: Por defecto usaremos "shadow copy", lo cual será más que suficiente para la mayoría de ocasiones. La shadow copy copiará los objetos de primer nivel, pero no los anidados, que quedarán por referencia (se hará referencia al objeto original, el cual no se habrá copiado!!). Así por ejemplo, de la siguiente lista, [a, b] sería una lista anidada (o de segundo nivel) la cual no se incluiría en una "shadow copy" si no que se le haría referencia, con lo que si se cambiara un valor, también se cambiaría en la lista original.

>>> [1, 2, 3, [a, b]]

La "deep copy", como su nombre indica, sí que copiaría todos los niveles, así que hay que ir con cuidado si se usa con un objeto con múltiples objetos anidados.