Python, para empezar...

Lo que hay que saber cuando empiezas con Python
No soy un especialista en este lenguaje.
Este es un breve esquema de los puntos que me parecen importantes para empezar con Python.

¿Porqué Python?

Mis razones...
  • Tiene una enorme comunidad
  • Es muy popular
  • Extraordinariamente bien documentado
  • Fácil encontrar respuesta a cualquier cuestión en internet
  • Viene instalado en todas las distribuciones linux
  • Curva de aprendizaje suave
  • Prefiero hacer scripts en Python que en bash
  • Fantástica y amplia librería (para casi todo)
  • Llamadas a código C fácilmente

Tabla características

Tabla breve características
CaracterísticaObservaciones
OOP
Herencia múltipleMixins
Prog. estructurada
Módulos
Parámetros por nombre
Parámetros por defecto
Número parámetros variable
Parámetros por referencianaNo aplica (ver variables|asignación)
Parámetros por valornaNo aplica (ver variables|asignación)
FuncionalpParcial
Recursión de colanoAl menos en CPython
ClosuresTodas las funciones son closures
Eventosno
Tener un modelo de eventos tipo delegados o signal/slot no debería ser un problema gracias a losclosures
Lambdaslimitadas a una expresión
CorrutinasLo llaman generadores
Multimétodosno
Declaración explícita de variablesno
Declaración de constantesno
Variables montículonaNo aplica
Variables pilanaNo aplica
Tipado dinámicoduck typing
Tipado fuerte
Literal string
String multilínea
Tuplas
Listas
Mapasdiccionarios
Regular expresionslibrería estándar
Enteros gigantes
REPL
Metaprogramación
Reflexión
PopularidadENORME
LibreríasEnormes
Generación código nativonoCython sí
RendimientopBajo
JITppypy sí. CPython no
Gestión memoriavContador referencias y recolector de basura para ciclos
RAIIwidth
Propertiesver más abajo
ConcurrenciapCorrutinas y threads. Ver paralelismo
ParalelismopBloqueos GIL
SingletonpFácil de crear en librería. Discutible necesidad por soporte prog. estructurada.
Curva aprendizajesuave
Decoradores
Integración con librerías CMuy sencillo
Control de errorestry... except
Control división por cerolanza una excepción

General

  • Paradigma:
    • OOP y estructurado
    • Parcialmente funcional y metaprogramación
  • Tipado:
    • Dinámico
    • Estricto
  • Gestión memoria
    • Combinación de contador de referencias y recolector de basura para ciclos
  • Filosofía Python
    “there should be one—and preferably only one—obvious way to do it”
    vs Perl...
    “here is more than one way to do it”
  • Estructuración de código
    • No punto y coma
    • No llaves
    • Identación
  • Comentarios
    • Sólo # para comentario hasta final de la línea
  • Estructuras de control
    • import
    • if... elif... else...
    • for ... in ...
    • while...
    • try... finally
    • class...
    • def...
    • with... (RAII)
    • pass
    • assert
    • yield coroutines, llamadas generadores en Python
  • Operadores y expresiones
    • Comparación concatenados a<=b<=c
    • // división entera
    • == Compara por valor
    • and or not
    • condicional expresions x if c else y
    • list comprehesion
    • lambda expresions
  • Tipos
    • Tipado dinámico
    • tipado fuerte
    • duck typing
    • Inmutables
      • str
      • bytes
      • tuple
      • frozenset
      • int
      • float
      • complex
      • bool
    • Mutables
      • bytearray
      • list (slice e índice)
      • set
      • dict (the keys has to be inmutables)
  • Strings
    • ‘hola’
    • “hola”
    • r’hola’
    • “”“En un lugar de la mancha,
      de cuyo nombre, no quiero acordarme,
      vivía un hidalgo caballero...”“”
  • OOP
    • instance.method(argument) es una simplificación (azúcar sintáctico) de Class.method(instance, argument)
    • Métodos requieren el parámetro self
    • Herencia múltiple
    • Mixins

Herramientas imprescindibles

  • Sphinx para la documentación
  • PEP8 para revisión de estilo
  • Pylint para revisión de estilo y posibles errores

Atención y precauciones

Estilo

http://www.python.org/dev/peps/pep-0008/
  • joined_lower para variables y nombre de métodos
  • JOINED_UPPER para constantes
  • CamelCase para nombres de clases
  • Todos los módules, clases, funciones y métodos deben tener cadenas de documentación
  • Utilizar comentarios de mantenimiento
    • # !!! BUG: ...
    • # !!! FIX: ...
    • # TODO: ...
    • # ???
  • if x == True: NO, en su lugar... if x:
  • Nunca print a, siempre print(a) compatible con Python 2 y 3

Parámetros por defecto

Muy buena funcionalidad, pero observa el siguiente error:
# -*- coding: utf-8 -*-
"""
Example error with default parameters
"""


def spam(a, b=[]):
    b.append(8)
    print(a, b)


if(__name__ == "__main__"):
    spam(1)
    spam(2)
    spam(2)

La salida no es la esperada
>>>
(1, [8])
(2, [8, 8])
(2, [8, 8, 8])
>>>
Pylint nos avisará de estos problemas. Cuando el valor por defecto es un mutable, no puede hacerse así

Variables

Se asocia un nombre a un valor.

Sin declaración explícita de variables

Tipado estricto sí
>>> a=8
>>> print(a+' hola')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
>>>
pero...
>>> a=8
>>> a='hola'
>>> print(a)
hola
>>>
En otros lenguajes:
auto i=5;
std::string s("lkjlkj");
s=99;    <<--  ERROR

Asignación variables

Dos nombres apuntan al mismo valor.
>>> a=8
>>> b=a
>>> print(a, b)
(8, 8)
>>>
Ahora dos nombres apuntan al mismo valor, y luego uno de los nombres apunta a un valor diferente
>>> a=8
>>> b=8
>>> b=10
>>> print(a,b)
(8, 10)
>>>
Pero...
>>> a=[1,2]
>>> b=a
>>> print(a, b)
([1, 2], [1, 2])
>>> b.append(3)
>>> print(a, b)
([1, 2, 3], [1, 2, 3])
>>>
Aquí se modifica el valor de a al cambiar el de b. En este caso, las dos etiquetas siguen apuntando al mismo valor.
La diferencia en ambos casos, es que una lista es mutable mientras que un entero es inmutable

Copia variables

Como hemos comentado, el operador ‘=’ no significa copiar el valor. Pero si el valor es de un tipo inmutable, el resultado es equivalente.
¿Qué hacer con el frecuente caso de que el valor no sea inmutable?
En los tipos diclistset... tenemos un constructor para copiar.
>>> a = {"a": 1, "b": 2}
>>> b = a.copy()
>>> print(a,b)
({'a': 1, 'b': 2}, {'a': 1, 'b': 2})
>>> b["b"] = 22
>>> print(a,b)
({'a': 1, 'b': 2}, {'a': 1, 'b': 22})
También podemos hacerlo así:
>>> a = {"a": 1, "b": 2}
>>> b = dict(a)
>>> print(a, b)
({'a': 1, 'b': 2}, {'a': 1, 'b': 2})
>>> b["b"] = 22
>>> print(a, b)
({'a': 1, 'b': 2}, {'a': 1, 'b': 2})
>>> b["b"] = 22
>>> print(a,b)
({'a': 1, 'b': 2}, {'a': 1, 'b': 22})
Perfecto, b = dict(a) nos crea una copia.
Pero atención:
>>> d={"a": 1, "sd": { "b": 2} }
>>> d2=dict(d)
>>> print(d, d2)
({'a': 1, 'sd': {'b': 2}}, {'a': 1, 'sd': {'b': 2}})
>>> d2["sd"]["b"] = 33
>>> print(d, d2)
({'a': 1, 'sd': {'b': 33}}, {'a': 1, 'sd': {'b': 33}})
Sorpresa, hemos realizado una copia, pero... han cambiado d["sd"]["b"] y d2["sd"]["b"]
Para evitar esto...
>>> d={"a": 1, "sd": { "b": 2} }
>>> from copy import deepcopy
>>> d2=deepcopy(d)
>>> print(d, d2)
({'a': 1, 'sd': {'b': 2}}, {'a': 1, 'sd': {'b': 2}})
>>> d2["sd"]["b"] = 33
>>> print(d, d2)
({'a': 1, 'sd': {'b': 2}}, {'a': 1, 'sd': {'b': 33}})
Ahora sí.
Soy consciente de ignorar una de las reglas de estilo Python al escribir from copy import deepcopy, pero lo considero justificado en este caso.

¿Variable global o local?

En este programa no hay sorpresas
# -*- coding: utf-8 -*-
"""
Example
"""


vg = 8


def spam(a):
    print(vg)


if(__name__ == "__main__"):
    spam(1)
    spam(2)
Pero con esta pequeña variación...
# -*- coding: utf-8 -*-
"""
Example
"""


vg = 8


def spam(a):
    vg += a
    print(vg)


if(__name__ == "__main__"):
    spam(1)
    spam(2)
Tenemos un error porque dice que vg es utilizada antes de ser asignada. Para evitar este error...
# -*- coding: utf-8 -*-
"""
Example
"""


vg = 8


def spam(a):
    global vg
    vg += a
    print(vg)


if(__name__ == "__main__"):
    spam(1)
    spam(2)

import y path

Organizar módulos en directorios.
Podemos hacer esto en Python al estilo de otros lenguajes.
Pero no olvides crear el fichero __init__.py aunque esté vacío en cada directorio que quieras que sea un módulo

import, path y pruebas

Supongamos que organizamos nuestro proyecto en directorios (muy lógico). El path para la búsqueda de módulos estará en el directorio actual y podríamos tener algo como...
from misc.Mixin import mixin
Pero este módulo podría ser utilizado en unas pruebas unitarias desde otro módulo y otro directorio.
lib_path = os.path.abspath('../..')
sys.path.append(lib_path)
from misc.Mixin import mixin

Eventos

Utilizando PyQt4 es directo con signal/slot de Qt, pero, no es Pythonic
Es fácil crear un sistema tipo signal/slot o delegates
# -*- coding: utf-8 -*-
"""
Created on Sun Sep  1 14:26:12 2013

"""


class Event:
    """Event support class"""
    def __init__(self):
        self.handlers = set()

    def handle(self, handler):
        self.handlers.add(handler)
        return self

    def unhandle(self, handler):
        try:
            self.handlers.remove(handler)
        except:
            raise ValueError("Handler is not handling this event, "
                                    "so cannot unhandle it.")
        return self

    def fire(self, *args, **kargs):
        for handler in self.handlers:
            handler(*args, **kargs)

    def getHandlerCount(self):
        return len(self.handlers)

    __iadd__ = handle
    __isub__ = unhandle
    __call__ = fire
    __len__ = getHandlerCount


if(__name__ == '__main__'):
    def receiver_func(message):
        print("receiver_func " + str(message))

    class EventExampleSender:
        def __init__(self):
            self.event = Event()

        def raise_event(self, message):
            self.event(message)

    class EventExampleReceiver:
        def __init__(self, id):
            self.event = Event()
            self.id = id

        def event_received(self, message):
            print("Received... " + self.id + " " + str(message))

    sender = EventExampleSender()
    receiver1 = EventExampleReceiver("1")
    receiver2 = EventExampleReceiver("2")

    sender.event += receiver1.event_received
    sender.event += receiver2.event_received
    sender.event += receiver_func

    sender.raise_event("Hi there")
    sender.raise_event(88)

Pasar por referencia

Como ya hemos visto, no se puede pasar por referencia. Todo son valores de etiquetas.
Si el objeto pasado es o no inmutable...
No suele ser neceario pasar por referencia. Lógicamente hay que utilizar las tuplas.
Pero en el caso de la clase Event anterior, sí podemos necesitar pasar por referencia para recoger un valor de todos los llamados.
Para evitar problemas con los mutable e inmutable, basta con pasar una lista, que es mutable
def _on_line_event(self, line):
    self.command_result.appendPlainText(">>> " + line)
    result = []
    self.on_command(line, result)
    for line in result:
        self.command_result.appendPlainText(line)

Ocultar

privateprotected, ocultación.
No existe en Python. Guido dice que Python es para adultos, y no necesitan un filtro de protección.
No obstante, se utiliza como convenio _ al principio de los métodos que queremos indicar que no son de “libre acceso”, y que se debería acceder a ellos siendo mayores de edad y con madured acreditada.

Múltiples líneas

Sí, se puede utilizar \ para dividir una línea, pero es más claro cómodo y sencillo, utilizar paréntesis
from  PyQt4.QtGui import(QWidget,
                        QSplitter,
                        QVBoxLayout,
                        QTabWidget)

Gestión de errores

Un ejemplo muy sencillo
def on_command(command, result):
    try:
        result.append(eval(command))
    except Exception as error:
        result.append(str(error))

Propiedades

class Contact(object):

    ...

    def set_email(self, value):
        if '@' not in value:
            raise Exception("This doesn't look like an email address.")
        self._email = value

    def get_email(self):
        return self._email

    email = property(get_email, set_email)

O utilizando decoradores...
class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

Ejemplo completo

Típico problema de los misioneros y caníbales:
# -*- coding: utf-8 -*-
"""\
Small example missioners problem
Simple recursive solution
"""

from copy import deepcopy


STATUS_INIT = {
        "side_a": {
                "missioners": 3,
                "cannibals": 3,
            },
        "side_b": {
                "missioners": 0,
                "cannibals": 0,
            },
        "boat_side": "a"
    }

STATUS_END = {
        "side_a": {
                "missioners": 0,
                "cannibals": 0,
            },
        "side_b": {
                "missioners": 3,
                "cannibals": 3,
            },
        "boat_side": "b"
    }

MAX_DEEP = 15


def print_status(status):
    """Just print the status"""
    import json
    print("---------------------------")
    print(json.dumps(status, indent=4, sort_keys=True))


def check_if_movement_is_possible(current_side, move_info):
    """Checking if the movement is legal"""
    missioners = move_info["missioners"]
    cannibals = move_info["cannibals"]
    if((missioners == 0 and cannibals == 0)
            or (missioners + cannibals > 2)
            or (current_side["missioners"] < missioners)
            or (current_side["cannibals"] < cannibals)):
        return False
    if(missioners > 0 and current_side["missioners"] == 0 or
                        current_side["missioners"] < missioners):
        return False
    if(cannibals > 0 and current_side["cannibals"] == 0 or
                        current_side["cannibals"] < cannibals):
        return False


def check_nam_nam(side):
    """Check if cannibals can eat missioners"""
    if(side["missioners"] > 0 and (side["missioners"] < side["cannibals"])):
        return True
    else:
        return False


def move(status, move_info, acc_movement):
    """It executes the movement"""
    status = deepcopy(status)
    acc_movement = deepcopy(acc_movement)
    missioners = move_info["missioners"]
    cannibals = move_info["cannibals"]

    if(status["boat_side"] == "a"):
        current_side = status["side_a"]
        other_side = status["side_b"]
    else:
        current_side = status["side_b"]
        other_side = status["side_a"]

    if(check_if_movement_is_possible(current_side, move_info) == False):
        return False, status, acc_movement

    # execute movement
    current_side["missioners"] -= missioners
    current_side["cannibals"] -= cannibals
    other_side["missioners"] += missioners
    other_side["cannibals"] += cannibals
    acc_movement.append(move_info)
    if(status["boat_side"] == "a"):
        status["boat_side"] = 'b'
    else:
        status["boat_side"] = 'a'
    if(check_nam_nam(current_side) or check_nam_nam(other_side)):
        return False, status, acc_movement
    return True, status, acc_movement


def print_solution(solution):
    """Just prints a solution"""
    print(str(len(solution)) + " ------------")
    for step in solution:
        print(str(step))


def recursive_check_branchs(status, acc_movement):
    """Find recursivily the solutions"""
    global MAX_DEEP
    if(len(acc_movement) >= MAX_DEEP):
        return
    for missioners in range(0, 3):
        for cannibals in range(0, 3):
            move_ok, new_status, new_acc_movement = \
                            move(status,
                                    {"missioners": missioners,
                                    "cannibals": cannibals},
                                acc_movement)
            if(move_ok):
                if(new_status == STATUS_END):
                    print('-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*')
                    print('            EUREKA')
                    print('-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*')
                    print_solution(new_acc_movement)
                    MAX_DEEP = len(new_acc_movement)
                    return
                else:
                    recursive_check_branchs(new_status, new_acc_movement)


def main():
    """The starting point"""
    print_status(STATUS_INIT)
    recursive_check_branchs(STATUS_INIT, [])


if(__name__ == "__main__"):
    main()

Comentarios

Entradas populares de este blog

Software libre

Servicios, servicios, servicios... (y Amazon)

Tecnologías divertidas