[Python-es] Traits en Python

Chema Cortes pych3m4 en gmail.com
Mie Ago 28 14:44:50 CEST 2013


El día 28 de agosto de 2013 01:44, Olemis Lang <olemis en gmail.com> escribió:
> Busco una librería q implemente un mecanismo de extensión similar a
> los traits (self, scala, smalltalk, ...) . Lo q necesito es extender
> el comportamiento de clases sin herencia múltiple i.e. mixins . Si el
> mecanismo funciona a nivel d objetos en vez d clases puede q m sirva
> también .
>
> En mi corta investigación he encontrado :
>
>   - https://pypi.python.org/pypi/strait : en la línea d lo q necesito hacer
>     pero no permite redefinición (overrides) ni encadenamiento.
>   - http://pypi.python.org/pypi/Traits :
>   - https://pypi.python.org/pypi/simpletraits : lejanamente
>     parecido a lo q quiero hacer , creo q ni siquiera tiene q ver ...
>
> ¿Alguien conoce otra librería o alguna otra variante q pueda recomendar?

Viendo estas referencias, hay quién confunde "traits" con la
programación por contrato. Un trait define un comportamiento
determinado mediante:

- Métodos "concretos" que implementan un comportamiento determinado
- Métodos "abstractos" que parametrizan un comportamiento determinado

En scala, los traits son similares a las clases abstractas de java,
con la excepción de que también pueden tener métodos concretos.

Del mismo modo, en python puedes usar las clases abstractas base
(ABC). Mediante el uso de la metaclase abc.ABCMeta se pueden crear
clases abstractas virtuales que funcionen como traits.

Mejor lo vemos con un ejemplo (para python3):

from abc import ABCMeta, abstractmethod

class Trait(ABCMeta):
    pass

class MyTrait(metaclass=Trait):

    @abstractmethod
    def __str__(self):
        return ""

    def print(self):
        print(">>"+self.__str__()+"<<")

Hemos definido un trait 'MyTrait' con un métodos abstracto y otro
concreto. Para usarlo, empleamos la herencia múltiple:

class MyClass(MyTrait):
    pass

c=MyClass()  ##ERROR: Clase abstracta no definida

class MyClass(str, MyTrait):
    pass

c=MyClass("HOLA")  ##OK
c.print()  ## >>>HOLA<<<

Supongo que ésto ya lo habías probado. Según pidés, no quieres
"mixins", que equivaldría a que no quede rastro en el __mro__ . Se me
ocurre complicar algo más Trait para que sea capaz de "inyectar"
métodos:

class Trait(ABCMeta):

    def __init__(self, name, bases, dic):
        self.__dic=dic

    def register(self, cls):
        super().register(cls)
        for k,v in self.__dic.items():
            if not hasattr(cls,k):
                setattr(cls,k,v)
        return cls

El modo de empleo de MyTrait sería como decorador:

@MyTrait.register
class MyClass(object):
    def __str__(self):
        return "MyTrait"

assert(issubclass(MyClass, MyTrait))
assert(MyTrait not in MyClass.__mro__)

c=MyClass()  ##OK
c.print()  ## >>>MyTrait<<<

El problema de este último sistema es que no tiene en cuenta los
métodos abstractos ya que no está soportado añadir métodos abstractos
dinámicamente. Como solución, se podría suplantar los métodos
abstractos por una implementación que lanze una excepción
"NotImplementedError" o similar.


Supongo que se puede hacer mejor. Espero que te sirva.



-- 
Hyperreals *R  "Quarks, bits y otras criaturas infinitesimales":
http://ch3m4.org/blog
Buscador Python Hispano: http://ch3m4.org/python-es


Más información sobre la lista de distribución Python-es