Tabla de Contenidos

Python avanzado

Pequeños conceptos

Comprehesion lists

Crean una lista a partir de otras.
Siguen el formato [variable_devuelta bucle(s) condicion(es)]:

>>> lst = [4, 6, 1, 8, 3, 0, 5, 9, 10, 7, 2]		# Creación de la lista inicial
>>> big = [n for n in lst if n > 5]			# Selección de números mayores que 5
>>> big
[6, 8, 9, 10, 7]
>>> sbig = sorted([n for n in lst if n > 5])		# Y ordenados
>>> sbig
[6, 7, 8, 9, 10]
>>> sbig = sorted(n for n in lst if n > 5)		# Aunque entre paréntesis no es necesario poner los corchetes
>>> sbig
[6, 7, 8, 9, 10]
>>> lst2 = [3, 5, 9]
>>> [(n, n2) for n in lst for n2 in lst2 if n == n2]
[(3, 3), (5, 5), (9, 9)]
>>> [n for n in lst for n2 in lst2 if n == n2]
[3, 5, 9]
>>> listOfWords = ["this","is","a","list","of","words"]
>>> [ word[0] for word in listOfWords ]
['t', 'i', 'a', 'l', 'o', 'w']
>>> ['object ' + arg.__class__.__name__ if isinstance(arg, types.ObjectType) else arg for arg in args]

El siguiente crea pares de género (primero para news y luego para romance) y palabra en mayúsculas:

genre_word = [(genre, word.upper())
    for genre in ['news', 'romance']
    for word in words(categories=genre)]

Pueden ser combinadas con funciones del sistema como any o all:

myList = (2, 3, 5, 6)
print any(x == 1 for x in myList)	# False
print any(x == 5 for x in myList)	# True

También los diccionarios pueden ser creados así:

user_email = {user.name: user.email for user in users_list if user.email}

Esto crearía un objeto del estilo:

user_email = {'alfredgg': 'alfredgg@yahoo.es', 'diabolika': 'diabolikalau@gmail.com'}

O los sets:

users_first_names = {user.first_name for user in users}
other = {x+x for x in 'patata'} # -> {'pp', 'aa', 'tt'}

También puedes anidarlas:

matrix = [[1, 2], [3, 4]]
[1, 2, 3, 4]

Generators

Los generators son valores que se van generando on the fly, es decir, sólo puedes recoger un valor una vez ya que la lista no se guarda en memoria.
La sintaxis es la misma pero con paréntesis:

mygenerator = (x*x for x in range(3))
for i in mygenerator :
  print(i)

Los generadores no soportan el indexado (gen[3] daría error) ni el slicing (gen[:3] también daría error). Tampoco pueden concatenarse a las listas ni podremos realizar las funciones que hacemos con estas (consultar su tamaño, ordenarlo…).

Podemos realizar las siguientes acciones sobre ellos:

filtered_gen = (item for item in my_list if item > 3)
filtered_gen.next()
filtered_gen.next()
filtered_gen.next()

Otro ejemplo:

def route(*args):
    return ':'.join(str(arg) for arg in args)
route('user', 32, 'friends') # -> "user:32:friends" 

yield

Es una especie de return dentro de un bucle pero que conserva su valor para la próxima vez que se le invoque. Es decir, retorna un generator.

def func(l):
  while True:				# Genera valores infinitos
    for v in l: yield v
it = func([1, 9, 5, 6, 3])
it.next()				# 1
it.next()				# 9
it.next()				# 5

Context manager (with)

# Este código no libera file_handle si ocurre una excepción (MAL!):
file_handle = open(path_to_file, 'r')
for line in file_handle.readlines():
  if raise_exception(line):
    print('No! An Exception!')
############
# Código CORRECTO:
with open(path_to_file, 'r') as file_handle:
  for line in file_handle:
    if raise_exception(line):
      print('No! An Exception!')

Podemos definir clases que trabajen como context manager definiendo los métodos __enter__ y __exit__, o funciones a partir del módulo contextlib.

Reflection

# sin reflection:
Foo().hello()
 
# con reflection:
getattr(globals()['Foo'](), 'hello')()

El siguiente ejemplo recoge las propiedades\métodos de un objeto y mira su herencia, si vienen de DataType realizará una acción con ellas.

def load (self, config):
   import inspect
   attrs = [getattr(self, prop) for prop in dir(self)]
   for attr in attrs:
      types = inspect.getmro(attr.__class__)
      if types[len(types)-1] == DataType:
         attr.load(config[attr.configname])

Expresiones lambda

Una expresión lambda es una función sencilla de una sola línea. Son virtuales y no tienen un nombre con el que llamarlas. Se crean, se ejecutan y desaparecen, pero puedes guardarlas en una variable y llamarlas desde ella.

Sintaxis

lambda <parametro1, parametro2…> : <línea de código devuelta>

a = lambda x,y : x*y
a(3,4)						# devolvería 12

Llamada a una expresión lambda sin almacenar

(lambda x: x*2)(3)				# devolvería 6

Map (high-order function)

Realiza una llamada a una función para cada elemento del array:

lst = [1, 5, 8, 6, 4, 2, 3]
map ((lambda x : x*3), lst)			# retornaría: [3, 15, 24, 18, 12, 6, 9]

Filter (high-order function)

Retorna los elementos de una lista que al evaluarla con una función devuelven True:

lst = [1, 5, 8, 6, 4, 2, 3]
filter((lambda x : x < 5), lst)			# retornaría: [1, 4, 2, 3]

Reduce (high-order function)

Ejecuta una función para cada elemento de un array acumulando su valor:

lst = [1, 5, 8, 6, 4, 2, 3]
reduce((lambda x,y : x+y), lst)			# retornaría: 29, la suma de todos los elementos de la colección

Ejemplos de uso

Función de comparación por longitud:

my_cmp = (lambda x,y: cmp(len(x), len(y)))
my_cmp('abc', 'de')

Relación con las comprehesion lists

Pueden ser usadas en vez de las comprehesion lists (y son preferibles):

map(lambda x: x**2, range(5)) # -> [0, 1, 4, 9, 16]
filter(lambda x: x % 2, range(10)) # -> [1, 3, 5, 7, 9]

Tipos de datos

Comprobar el tipo de una variable:

type(o) is str
isinstance(o, str)

Saber si es una subclase:

issubclass(type(o), str)

Saber si…

isinstance(obj, types.FunctionType)  # es función

Para comprobar si es un string (del que sea) estas dos son válidas:

isinstance(o, basestring)
isinstance(o, (str, unicode))

Decorators

Las funciones son objetos del lenguaje, como tal pueden ser pasadas a otras funciones como parámetros.
Un decorador no es más que una función que recibe otra, le añade funcionalidad y la devuelve:

def identity_decorator(func):
    def wrapper():
        func()
    return wrapper
 
def a_function():
    print "I'm a normal function."
 
decorated_function = identity_decorator(a_function)
decorated_function()
# >> I'm a normal function

El scope de wrapper es identity_decorator. En el siguiente ejemplo se utiliza un mutable object (diccionarios, listas, instancias…). Un simple integer sería inmutable dentro de wrapper y de solo lectura.

def logging_decorator(func):
    def wrapper():
        wrapper.count += 1
        print "The function I modify has been called {0} times(s).".format(
              wrapper.count)
        func()
    wrapper.count = 0
    return wrapper
 
def a_function():
    print "I'm a normal function."
 
modified_function = logging_decorator(a_function)
modified_function()
# >> The function I modify has been called 1 time(s).
# >> I'm a normal function.
modified_function()
# >> The function I modify has been called 2 time(s).
# >> I'm a normal function.

*args y **kwargs

Para acceder a los parámetros de una llamada aprovecharemos *args (que contiene los parámetros por defecto de la función) y **kwargs (los especificados no por defecto en forma de diccionario). Puedes mirar el apartado de funciones.

Wraps en functools

Es un decorador que mantiene las propiedades el objeto función (como __name__, __class__...), que si no se utilizase quedaría con las propiedas de la función que lo decora.

from functools import wraps
def memoize(func):
    cache = {}
    @wraps(func)
    def wrapper(*args):
    ...

Sintaxis

Podemos emular el código anterior utilizando la sintaxis @nombre_func:

def some_function():
    print "I'm happiest when decorated."
# Here we will make the assigned variable the same name as the wrapped function
some_function = logging_decorator(some_function)
# We can achieve the exact same thing with this syntax:
@logging_decorator
def some_function():
    print "I'm happiest when decorated."

Decoradores de clases

También pueden ser añadidos a clases para, por ejemplo, agregar nuevos parámetros:

foo = ['important', 'foo', 'stuff']
def add_foo(klass):
    klass.foo = foo
    return klass
@add_foo
class Person(object):
    pass

O clases de decoradores

class IdentityDecorator(object):
    def __init__(self, func):
        self.func = func
    def __call__(self):
        self.func()
@IdentityDecorator
def a_function():
    print "I'm a normal function."

Argumentos a decoradores

from functools import wraps
def argumentative_decorator(gift):
    def func_wrapper(func):
        @wraps(func)
        def returned_wrapper(*args, **kwargs):
            print "I don't like this " + gift + " you gave me!"
            return func(gift, *args, **kwargs)
        return returned_wrapper
    return func_wrapper
@argumentative_decorator("sweater")
def grateful_function(gift):
    print "I love the " + gift + "! Thank you!"
grateful_function()
# >> I don't like this sweater you gave me!
# >> I love the sweater! Thank you!
from functools import wraps
GLOBAL_NAME = "Brian"
def print_name(function=None, name=GLOBAL_NAME):
    def actual_decorator(function):
        @wraps(function)
        def returned_func(*args, **kwargs):
            print "My name is " + name
            return function(*args, **kwargs)
        return returned_func
    if not function:  # User passed in a name argument
        def waiting_for_func(function):
            return actual_decorator(function)
        return waiting_for_func
    else:
        return actual_decorator(function)
@print_name
def a_function():
    print "I like that name!"
@print_name(name='Matt')
def another_function():
    print "Hey, that's new!"
a_function()
# >> My name is Brian
# >> I like that name!
another_function()
# >> My name is Matt
# >> Hey, that's new!

Ejemplo memoization

Este decorator hace que, cuando la función es llamada con los mismos parámetros devuelva el mismo resultado (memoriza el cálculo):

from functools import wraps
def memoize(func):
    cache = {}
    @wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrapper
@memoize
def an_expensive_function(arg1, arg2, arg3):

Notas

Unicode

Para poder escribir por consola unicode:

import sys
reload(sys)
sys.setdefaultencoding('utf-8')

El lenguaje

Python trabaja internamente con objetos. Pocos son los datos que se guardan como valores y lo que comunmente denominamos “variables” serían mejor llamadas “nombres”. Y es que una asignación es un enlace de un nombre a un objeto.

>>> foo = Foo()
>>> foo = 10
>>> print(foo.__add__)
<method-wrapper '__add__' of int object at 0x8502c0>

Podríamos comprobar el string que devuelve una referencia con otra para saber si pertenecen al mismo objeto (ya que ese string representa la dirección de memoria en la que está almacenado). Aún así la forma de hacerlo correctamente es usando is.

is también es el comando indicado para comparar con None. Esto es porque None es un objeto singleton dentro del lenguaje.