3ENGINE

Programación y otros cachivaches

Etiqueta: Python

Página 4/6

Tecnologia

Clases abstractas en Python


Python de forma nativa no soporta clases abstractas, pero eso no significa que no existan. A partir de la versión 2.6, Python trae el módulo abc (abstract base classes) de acuerdo a PEP 3119, que consigue emular la abstracción de clases mediante metaclases y decoradores Python.

python abstract class

Metaclases

Un buen artículo muy recomendable que consigue explicar el mundillo de las metaclases sin pegarse un tiro en la cabeza, es este de crysol Ahí va la virgen! Metaclases! (con Python). Pero para hacerse una idea, diremos que una metaclase es una clase cuyas instancias son clases en lugar de objetos.

Es decir, si para construir un objeto usas una clase, para construir una clase usas una metaclase. Es importante entender que en Python todo es un objeto (incluso las clases, los modulos, los tipos de datos, etc..) y por lo tanto las clases, al ser objetos, son instancias de una metaclase.

En el caso que nos ocupa, es interesante porque permite modificar el comportamiento o características de una clase en el momento de su creación.

Decoradores

Respecto a los decoradores pues prefiero recomendaros un artículo de la casa 😉 Decoradores Python. A modo de resumen diremos que un decorador Python permite añadir funcionalidad extra a una función mediante un wrapper (envoltorio).

Clases abstractas en Python

Para empezar. Definiremos un método abstracto mediante el uso del decorador @abstractmethod. Al instanciar la clase… ¡Sorpresa! ha funcionado. Python no se queja.

>>> import abc
... 
... class Vehicle(object):
...     @abc.abstractmethod
...     def who_are_you(self):
...         print "I'm a vehicle and also an abstract method"
... 
... vehicle = Vehicle()
... vehicle.who_are_you()
I'm a vehicle and also an abstract method
>>>

Para que el invento funcione, la clase Vehicle tiene que heredar de la metaclase ACBMeta. Con un poco de ‘azucar sintáctico’ (¿what?) mediante __metaclass__ es fácil. Ahora Python hace lo esperado, se queja porque no es posible instanciar una clase con métodos abstractos.

>>> import abc
... from abc import ABCMeta
... 
... class Vehicle(object):
...     __metaclass__ = ABCMeta
...     
...     @abc.abstractmethod
...     def who_are_you(self):
...         print "I'm a vehicle and also an abstract method"
... 
... vehicle = Vehicle()
... vehicle.who_are_you()
Traceback (most recent call last):
  File "<pyshell#6>", line 11, in <module>
    vehicle = Vehicle()
TypeError: Can't instantiate abstract class Vehicle with abstract methods who_are_you
>>>

Para curiosos. En el repositorio oficial está la implementación de la metaclase ABCMeta. Ahora vamos a crear una clase Car que herede de Vehicle y que sobreescriba el método abstracto.

>>> import abc
... from abc import ABCMeta
... 
... class Vehicle(object):
...     __metaclass__ = ABCMeta
...     
...     @abc.abstractmethod
...     def who_are_you(self):
...         print "I'm a vehicle and also an abstract method"
... 
... class Car(Vehicle):
... 
...     def __init__(self):
...         Vehicle.__init__(self)
...         
...     def who_are_you(self):
...         print "I'm a car"
... 
... car = Car()
... car.who_are_you()
I'm a car
>>>

Como curiosidad, podemos hacer ‘trampas’ e invocar al método abstracto mediante el uso de super().

>>> import abc
... from abc import ABCMeta
... 
... class Vehicle(object):
...     __metaclass__ = ABCMeta
...     
...     @abc.abstractmethod
...     def who_are_you(self):
...         print "I'm a vehicle and also an abstract method"
... 
... class Car(Vehicle):
... 
...     def __init__(self):
...         Vehicle.__init__(self)
...         
...     def who_are_you(self):
...         super(Car, self).who_are_you()
...         print "I'm a car"
... 
... car = Car()
... car.who_are_you()
I'm a vehicle and also an abstract method
I'm a car
>>>

Con el decorador @abstractproperty podemos crear propiedades abstractas. Aquí un ejemplo.

>>> import abc
... from abc import ABCMeta
... 
... class Vehicle(object):
...     __metaclass__ = ABCMeta
...     
...     @abc.abstractmethod
...     def who_are_you(self):
...         print "I'm a vehicle and also an abstract method"
... 
...     @abc.abstractproperty
...     def color(self):
...         pass
...         
... class Car(Vehicle):
... 
...     def __init__(self):
...         Vehicle.__init__(self)
...         
...     def who_are_you(self):
...         print "I'm a car"
... 
...     @property
...     def color(self):
...         return "red"
... 
... car = Car()
... car.who_are_you()
... print "my color is", car.color
I'm a car
my color is red
>>> 

El anterior ejemplo definimos una propiedad de lectura. También es posible crear propiedades abstractas de lectura y escritura (getter & setter) pero esto solo es posible hacerlo sin ‘azúcar sintáctico’

>>> import abc
... from abc import ABCMeta
... 
... class Vehicle(object):
...     __metaclass__ = ABCMeta
...     
...     @abc.abstractmethod
...     def who_are_you(self):
...         print "I'm a vehicle and also an abstract method"
... 
...     def get_color(self):
...         pass
...     
...     def set_color(self, value):
...         pass
... 
...     color = abc.abstractproperty(get_color, set_color)
...         
... class Car(Vehicle):
... 
...     def __init__(self):
...         Vehicle.__init__(self)
...         self._color = "red"
...         
...     def who_are_you(self):
...         print "I'm a car"
... 
...     @property
...     def color(self):
...         return self._color
... 
...     @color.setter
...     def color(self, value):
...         self._color = value
... 
... car = Car()
... car.color = "blue"
... car.who_are_you()
... print "my color is", car.color
I'm a car
my color is blue
>>> 




Tecnologia

Decoradores Python


Aunque los decoradores Python coinciden en el nombre con el Patron Decorador y guardan cierta similitud, no hay que confundirlos.

Decoradores Python

Mientras que el Patron Decorador permite añadir dinámicamente funcionalidad a un objeto, el objetivo principal de un decorador Python es añadir funcionalidad extra a una funcion. Para añadir funcionalidad hacemos uso de un wrapper. Un envoltorio, es decir, envolvemos la funcion. Para hacer esto, podemos usar clases u objetos.

Usando clases como decoradores Python

Para que esto funcione, el objeto devuelto por el decorador tiene que estar preparado para ser utilizado como una función. Esto significa que debe ser invocable. Para conseguir esto la clase debe implementar la función especial __call__ que permite emular un objeto como si fuera una función.

Ejemplo 1

class Decorador(object):

    def __init__(self, f):
        print "__init__()"
        f() 

    def __call__(self):
        print "__call__()"

def Funcion():
    print "Funcion()"

print "Inicio" 
Decorado = Decorador(Funcion)
Decorado()
print "Fin"
Inicio
__init__()
Funcion()
__call__()
Fin

Para hacerlo menos engorroso, Python proporciona soporte para envolver una función en un decorador mediante el símbolo @.

Ejemplo 2. La función se invoca en __init__

class Decorador(object):

    def __init__(self, f):
        print "__init__()"
        f() 

    def __call__(self):
        print "__call__()"

@Decorador
def Funcion():
    print "Funcion()"

print "Inicio" 
Funcion()
print "Fin"
__init__()
Funcion()
Inicio
__call__()
Fin

Ejemplo 3. Guarda la referencia en __init__ y se invoca en __call__

class Decorador(object):

    def __init__(self, f):
        print "__init__()"
        self._f = f 

    def __call__(self):
        print "__call__()"
        self._f()

@Decorador
def Funcion():
    print "Funcion()"

print "Inicio" 
Funcion()
print "Fin"
__init__()
Inicio
__call__()
Funcion()
Fin

Ejemplo 4. Invocar y guarda el resultado de la función __init__ y hacer uso en __call__

class Decorador(object):

    def __init__(self, f):
        print "__init__()"
        self._resultado = f()

    def __call__(self):
        print "__call__()"
        print "la funcion dijo", self._resultado

@Decorador
def Funcion():
    print "Funcion()"
    return "Hola!"

print "Inicio" 
Funcion()
print "Fin"
print
__init__()
Funcion()
Inicio
__call__()
la funcion dijo Hola!
Fin

Ejemplo 5. Invoca en __call__ y habilitar el paso de parámetros

class Decorador(object):

    def __init__(self, f):
        print "__init__()"
        self._f = f

    def __call__(self, *args, **kwargs):
        print "__call__()"
        self._f(*args, **kwargs)

@Decorador
def Funcion(saludo):
    print saludo, "desde la Funcion()"

print "Inicio" 
Funcion("Hola!")
print "Fin"
__init__()
Inicio
__call__()
Hola! desde la Funcion()
Fin

Usando funciones como decoradores Python

Hemos visto que la única condición para que funcione un decorador es que éste sea invocable. En los ejemplos anteriores, hemos reemplazado la función original con un objeto de una clase que sobreescribe el método __call__. Podemos reescribir los ejemplos anteriores utilizando una función en lugar de una clase, aunque no da tanto juego.

Ejemplo 6. Generar un mensaje de bienvenida en formato HTML

def html(func):
    contador = 0
    def box(*args, **kwargs):
        return "%s" % (func(*args, **kwargs))
    return box

def saludo(nombre, visitas):
    return "Hola %s eres el visitante numero %d!" % (nombre, visitas)

documento = html(saludo)
print documento("David", 500)
Hola David eres el visitante numero 500!

Como antes hemos visto, para hacerlo menos engorroso hacemos uso del símbolo @.

def html(func):
    contador = 0
    def box(*args, **kwargs):
        return "%s" % (func(*args, **kwargs))
    return box

@html
def saludo(nombre, visitas):
    return "Hola %s eres el visitante numero %d!" % (nombre, visitas)

print saludo("David", 500)

Ejemplo 7. Un logger para registrar los parámetros pasados a la función

def logger(func):
    def box(*args, **kwargs):
        print "los argumentos son: %s, %s" % (args, kwargs)
        result = func(*args, **kwargs)
        return result
    return box

@logger
def suma(a, b):
    return a+b

@logger
def resta(a, b):
    return a-b

print suma(3,2)
print resta(3,2)
los argumentos son: (3, 2), {}
5
los argumentos son: (7, 3), {}
4

Ejemplo 8. Comprobar con ‘assert’ que el argumento es un entero

def require_int (func):
    def wrapper (arg):
        assert isinstance(arg, int)
        return func(arg)

    return wrapper

@require_int
def p1 (arg):
    print arg

@require_int
def p2(arg):
    print arg*2

p1(2)
p2(4)
p2("dos")
p1("cuatro")
2
8
Traceback (most recent call last):
  File "decorator.py", line 18, in 
    p2("dos")
  File "decorator.py", line 3, in wrapper
    assert isinstance(arg, int)
AssertionError

Nota: por defecto los asserts están activados. Para desactivarlos hay que pasar el parámetro -O al interprete de comandos.

> python -O decorator.py
2
8
dosdos
cuatro

Built-in Functions

Python incorpora algunas funciones útiles que pueden usarse como decoradores, por ejemplo classmethod, property y staticmethod

Ejemplo 9. uso de @staticmethod y @property para decorar funciones de una clase

class Saludador:

    def __init__(self, nombre):
        self._nombre = nombre

    @staticmethod
    def saludo_generico():
        print "Hola a todos!"

    def saludo_individual(self):
        print "Hola", self._nombre

    @property
    def nombre(self):
        return self._nombre

test = Saludador("David")
test.saludo_generico()
Saludador.saludo_generico()
test.saludo_individual()
print "nombre", test.nombre
Hola a todos!
Hola a todos!
Hola David
nombre David

Os dejo un enlace con una presentación sobre decoradores avanzados en Python.




Tecnologia

argparse: módulo de análisis de línea de comandos para Python


El módulo argparse (PEP389) módulo de análisis de línea de comandos que ofrece más funcionalidad que los módulos de análisis de línea de comandos existentes en la biblioteca estándar.

A partir de la versión Python 2.7 y 3.2 el módulo argparse sustituye al módulo optparse. Existe un tutorial que puedes seguir para saber cómo funciona.

módulo de análisis de línea de comandos

Para un aprendizaje rápido de este módulo he creado una ejemplo con los comandos básicos.

Código

# -*- coding: cp1252 -*-
from argparse import ArgumentParser

# ArgumentParser con una descripción de la aplicación 
# (https://docs.python.org/2/library/argparse.html#argumentparser-objects)
parser = ArgumentParser(description='%(prog)s is an ArgumentParser demo')

# Argumento posicional. Los argumentos posicionales son obligatorios.
parser.add_argument('arg1')

# Un argumento posicional con una descripción 
parser.add_argument('arg2', help='help for arg2')

# Un argumento posicional con un tipo definido de tipo int 
# (https://docs.python.org/2/library/argparse.html#type)
parser.add_argument('arg3', help='help for arg3', type=int)

# Argumento posicional con tres opciones posibles 
# (https://docs.python.org/2/library/argparse.html#choices)
parser.add_argument('arg4', choices=['rock', 'paper', 'scissors'])

# Argumento opcional. Si se pametriza, requiere acompañarlo de un valor
parser.add_argument('-opt1')

# Un argumento opcional puede tener varios nombres
parser.add_argument('-opt2', '--option2')

# Argumento opcional con una descripción. Si se pametriza, requiere 
# acompañarlo de un valor de tipo int
parser.add_argument('-opt3', help='help for opt3', type=int)

# Argumento opcional con una descripción. Si se pametriza, requiere 
# acompañarlo de un valor de tipo int. Por defecto el valor es 10
parser.add_argument('-opt4', help='help for opt4', type=int, default=10)

# Argumento opcional. Con 'action' damos valor si el argumento se parametriza 
# (https://docs.python.org/2/library/argparse.html#action)
parser.add_argument('-opt5', '--option5', help='help for opt5', 
action='store_true', default=False)

# Argumento opcional requerido
parser.add_argument('-opt6', required=True)

# Argumento opcional con tres opciones posibles
# (https://docs.python.org/2/library/argparse.html#choices)
parser.add_argument('-opt7', choices=['rock', 'paper', 'scissors'])

# Argumento opcional que requiere dos argumentos
parser.add_argument('-opt8', nargs=2)

# Argumento opcional que requiere de 1 a N argumentos
parser.add_argument('-opt9', nargs='+')

# Argumento opcional que requiere de 0 a N argumentos
parser.add_argument('-opt10', nargs='*')

# Por último parsear los argumentos
args = parser.parse_args()

# Imprimir los parametros
print 'args.arg1:', args.arg1
print 'args.arg2:', args.arg2
print 'args.arg3:', args.arg3
print 'args.arg2:', args.arg4
print 'args.opt1:', args.opt1
print 'args.opt2:', args.option2
print 'args.opt3:', args.opt3
print 'args.opt4:', args.opt4
print 'args.opt5:', args.option5
print 'args.opt6:', args.opt6
print 'args.opt7:', args.opt7
print 'args.opt8:', args.opt8
print 'args.opt9:', args.opt9
print 'args.opt10:', args.opt10

Ayuda básica

>python argcons.py
usage: argcons.py [-h] [-opt1 OPT1] [-opt2 OPTION2] [-opt3 OPT3] [-opt4 OPT4]
                  [-opt5] -opt6 OPT6 [-opt7 {rock,paper,scissors}]
                  [-opt8 OPT8 OPT8] [-opt9 OPT9 [OPT9 ...]]
                  [-opt10 [OPT10 [OPT10 ...]]]
                  arg1 arg2 arg3 {rock,paper,scissors}
argcons.py: error: too few arguments

Mostrar ayuda extendida

>python argcons.py -h
usage: argcons.py [-h] [-opt1 OPT1] [-opt2 OPTION2] [-opt3 OPT3] [-opt4 OPT4]
                  [-opt5] -opt6 OPT6 [-opt7 {rock,paper,scissors}]
                  [-opt8 OPT8 OPT8] [-opt9 OPT9 [OPT9 ...]]
                  [-opt10 [OPT10 [OPT10 ...]]]
                  arg1 arg2 arg3 {rock,paper,scissors}

argcons.py is an ArgumentParser demo

positional arguments:
  arg1
  arg2                  help for arg2
  arg3                  help for arg3
  {rock,paper,scissors}

optional arguments:
  -h, --help            show this help message and exit
  -opt1 OPT1
  -opt2 OPTION2, --option2 OPTION2
  -opt3 OPT3            help for opt3
  -opt4 OPT4            help for opt4
  -opt5, --option5      help for opt5
  -opt6 OPT6
  -opt7 {rock,paper,scissors}
  -opt8 OPT8 OPT8
  -opt9 OPT9 [OPT9 ...]
  -opt10 [OPT10 [OPT10 ...]]

Ejemplo de uso

>python argcons.py  a b 15 rock -opt6 c -opt7 rock -opt8 1 2 -opt9 1 2 3 d e f -opt10
args.arg1: a
args.arg2: b
args.arg3: 15
args.arg2: rock
args.opt1: None
args.opt2: None
args.opt3: None
args.opt4: 10
args.opt5: False
args.opt6: c
args.opt7: rock
args.opt8: ['1', '2']
args.opt9: ['1', '2', '3', 'd', 'e', 'f']
args.opt10: []



Tecnologia

Stemming con Python


Stemming

Según la wikipedia Stemming es un método para reducir una palabra a su raíz o (en inglés) a un stem o lema. Hay algunos algoritmos de stemming que ayudan en sistemas de recuperación de información. Stemming aumenta el recall que es una medida sobre el número de documentos que se pueden encontrar con una consulta. Por ejemplo una consulta sobre bibliotecas también encuentra documentos en los que solo aparezca bibliotecario porque el stem de las dos palabras es bibliotec. Mas información en la Wikipedia.

Lematización

Existe otro método llamado Lematización que se diferencia del Stemming en que dada una forma flexionada (es decir, en plural, en femenino, conjugada, etc), halla el lema que por convenio se acepta como representante de todas las formas flexionadas de una misma palabra. Es decir, el lema de una palabra es la palabra que nos encontraríamos como entrada en un diccionario tradicional: singular para sustantivos, masculino singular para adjetivos, infinitivo para verbos. Por ejemplo, decir es el lema de dije, pero también de diré o dijéramos; guapo es el lema de guapas; mesa es el lema de mesas. Mas información en la Wikipedia.

Implementación de Stemming en Python

Es posible realizar Stemming mediante un algoritmo que use reglas gramaticales de derivación morfológica para el idioma en cuestión, o bien usando un diccionario informatizado que asocie a cada forma su lema (palabra) representante. Para el primera solución Snowball (Github) es un pequeño lenguaje de procesamiento implementado en ANSI C para la creación y uso de algoritmos de stemming. Después disponemos de PyStemmer (Github) que es un wrapper de Snowball para python.

Instala PyStemmer con Pip

pip install pystemmer

Nota: Si durante la instalación de PyStemmer se produce un error de este estilo es porque PyStemmer necesita un compilador de C++ para Python. Sigue las instrucciones :

error: Microsoft Visual C++ 9.0 is required (Unable to find vcvarsall.bat). 
Get it from http://aka.ms/vcpython27

Test:

# idiomas disponibles
>>> import Stemmer
>>> print(Stemmer.algorithms())
[u'danish', u'dutch', u'english', u'finnish', u'french', u'german', u'hungarian',
 u'italian', u'norwegian', u'porter', u'portuguese', u'romanian', u'russian',
 u'spanish', u'swedish', u'turkish']

# stem de la palabra en inglés 'cycling'
>>> import Stemmer
>>> stemmer = Stemmer.Stemmer('english')
>>> print(stemmer.stemWord('cycling'))
cycl

# stem de varias palabras en inglés 
>>> import Stemmer
>>> stemmer = Stemmer.Stemmer('english')
>>> print(stemmer.stemWords(['cycling', 'cyclist']))
['cycl', 'cyclist']

# Permite codificación UTF-8
>>> import Stemmer
>>> stemmer = Stemmer.Stemmer('english')
>>> print(stemmer.stemWords(['cycling', u'cyclist']))
['cycl', u'cyclist']

# Por defecto la caché de palabras es de 10000, pero
# se puede modificar
>>> import Stemmer
>>> print(stemmer.maxCacheSize)
10000
>>> stemmer.maxCacheSize = 1000
>>> print(stemmer.maxCacheSize)
1000

# Otro ejemplo en español
>>> import Stemmer
>>> stemmer = Stemmer.Stemmer('spanish')
>>> print stemmer.stemWords(['camionero','camiones','camion','camionera'])
>>> print stemmer.stemWords(['frutal','frutas','frutivoro','abcde'])
['camioner', 'camion', 'camion', 'camioner']
['frutal', 'frut', 'frutivor', 'abcde']

Ejemplo busqueda por palabra clave

import Stemmer

pelis = {u"La reina de Montana": u"Sierra Nevada Jones llega a Montana con su padre para tomar posesión de unas tierras que han pertenecido a sus familias durante generaciones",
         u"La carpa invisible": u"Historia de una circo familiar Los Magote Pablo el director y maestro de ceremonias su esposa la payasa y acróbata Margarita y sus hijos",
         u"Familia": u"Santiago después de levantarse baja a la cocina donde lo espera su familia para felicitarlo es su cumpleaños"}
keywrd = "familiar"

stemmer = Stemmer.Stemmer('spanish')
lemkw = stemmer.stemWord(keywrd)
print "keyword:", keywrd
print "keyword lem:", lemkw
print
for peli in pelis:
    lems = stemmer.stemWords(pelis[peli].split())
    print "title:",
    print "argument", pelis[peli]
    print "argument lem:", lems
    if lemkw in lems:
        print "keyword lem", lemkw, "encontrado en argument lem"
    print

Resultado:

keyword: familiar
keyword lem: famili

title: argument Historia de una circo familiar Los Magote Pablo el director y maestro de ceremonias su esposa la payasa y acróbata Margarita y sus hijos
argument lem: [u'Histori', u'de', u'una', u'circ', u'famili', u'Los', u'Magot', u'Pabl', u'el', u'director', u'y', u'maestr', u'de', u'ceremoni', u'su', u'espos', u'la', u'payas', u'y', u'acrobat', u'Margarit', u'y', u'sus', u'hij']
keyword lem famili encontrado en argument lem

title: argument Sierra Nevada Jones llega a Montana con su padre para tomar posesión de unas tierras que han pertenecido a sus familias durante generaciones
argument lem: [u'Sierr', u'Nev', u'Jon', u'lleg', u'a', u'Montan', u'con', u'su', u'padr', u'par', u'tom', u'posesion', u'de', u'unas', u'tierr', u'que', u'han', u'pertenec', u'a', u'sus', u'famili', u'durant', u'gener']
keyword lem famili encontrado en argument lem

title: argument Santiago después de levantarse baja a la cocina donde lo espera su familia para felicitarlo es su cumpleaños
argument lem: [u'Santiag', u'despues', u'de', u'levant', u'baj', u'a', u'la', u'cocin', u'dond', u'lo', u'esper', u'su', u'famili', u'par', u'felicit', u'es', u'su', u'cumplea\xf1']
keyword lem famili encontrado en argument lem

Como se puede apreciar en el ejemplo al buscar por la palabra clave familiar encuentra coincidencias en el argumento de las tres peículas: familiar, familias y familia.

Este es un ejemplo muy simple. Se podria mejorar eliminando previamente las palabras vacias (stop words), ordenar el resultado según número de coincidencias mediante un algoritmo Tf-idf, o un sistema de retroalimentación aportada por el usuario para indicar la relevancia de los resultados como Rocchio y así mejorar las busquedas.

overstemming y understemming

El stemning presenta dos problemas básicos. El overstemming cuando se reduce demasiado se representa con un mismo stem formas que deberían ser representadas con varios stems y el understemming cuando se reduce poco y se obtienen distintas formas para representar unicamente a una.

Un ejemplo

import Stemmer

stemmer = Stemmer.Stemmer('spanish')
lems = stemmer.stemWords(["familiar", "familias", "familia", "familiares",
                          "familiaridad", "familiaridades"])
print lems
lems = stemmer.stemWords(lems)
print lems

Resultado

['famili', 'famili', 'famili', 'familiar', 'familiar', 'familiar']
['famili', 'famili', 'famili', 'famili', 'famili', 'famili']

Del ejemplo vemos que si aplico dos veces stemming, coinciden las seis formas. Si se aplica sólo una vez se produce un efecto de understemming.