[Python-es] ¿ Injección de código con decoradores o herencia ?

Yeiniel Suárez Sosa yeiniel en uclv.cu
Mie Oct 23 14:49:43 CEST 2013


Ander

solo quiero decir que es muy mala idea que un decorador devuelva un 
objeto que no es el que decora, como en el ejemplo que presentas. Si 
realizas introspección no vas a ser capaz de saber quien es quien, 
porque el nombre de la clase de los objetos decorados queda oculta 
dentro de la implementación del decorador. Es preferible inyecta 
my_method y new_method empleando settatr.

def decor(arg):
   def _decor(cls):
     def my_method(self, value):
       print "New my method {0}, conn string: {1}".format(value, arg)

     def new_method(self, value):
       print "New new method {0}".format(value)

     setattr(cls, 'my_method', my_method)
     setattr(cls, 'new_method', new_method)

     return cls

   return _decor

Si deseas sacar los métodos my_method() hacia el modulo para que 
aparezcan en la documentación con help o sphynx entonces los puedes 
encapsular en una clase que reciba en la inicialización como argumento 
arg y en el decorador creas una instancia de la clase y empleando 
settatr transfieres los métodos de la instancia a la clase decorada. 
Pero bueno, me gustaría ver un ejemplo real de código donde piensas que 
esto se puede usar.

Como estabas hablando de bases de datos te envío algo que uso. Yo tengo 
mucho código que emplea la DBAPI 2.0. Así que empleo el siguiente 
proveedor de conexión empleando sqlalchemy

========================== modulo engine_provider 
=============================
import sqlalchemy

__all__ = ['EngineProvider']


class EngineProvider:
     """ `SQLAlchemy`_ support provider.

     This component provide support for use the ``SQLAlchemy`` library 
to
     connect to one database. The `get_engine` method is the only 
exposed
     service.

     The source database is configured using the `dsn` component 
attribute.

     If you need different database connections in the same application 
you
     can create multiple instances of this component and distribute them 
as
     needed.

     .. _SQLAlchemy: http://www.sqlalchemy.org/
     """

     dsn = 'sqlite:///application.db'

     def __init__(self, dsn=None):
         self._engine = sqlalchemy.create_engine(dsn or self.dsn)

     def get_engine(self) -> sqlalchemy.engine.Engine:
         """ Return an :class:`sqlalchemy.engine.Engine` object.
         :return: a ready to use :class:`sqlalchemy.engine.Engine` 
object.
         """
         return self._engine
============================== fin del modulo engine_provider 
===========================

un ejemplo de codigo donde empleo esto es el siguiente

============================== modulo blog 
=======================================
# Copyright (c) 2011, Yeiniel Suarez Sosa.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions 
are met:
#
#    * Redistributions of source code must retain the above copyright 
notice,
#      this list of conditions and the following disclaimer.
#
#    * Redistributions in binary form must reproduce the above 
copyright
#      notice, this list of conditions and the following disclaimer in 
the
#      documentation and/or other materials provided with the 
distribution.
#
#    * Neither the name of Yeiniel Suarez Sosa. nor the names of its
#      contributors may be used to endorse or promote products derived 
from
#      this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
"AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
SOFTWARE,
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import datetime
import os

import sqlalchemy
from sqlalchemy import engine, orm

from aurora.webapp import foundation, infrastructure, mapping
from aurora.webcomponents import layout, views

from components import engine_provider

from . import models

__all__ = ['Blog']


class Blog:
     """ Blogging service provider.

     This component provide the three common services all blogging 
platforms
     provide (listing posts, showing posts and composing posts). It has 
three
     dependencies:

      - A :meth:`engine_provider.EngineProvider.get_engine` compliant 
service
        used to access and persist information into a relational 
database.

      - A :meth:`aurora.webcomponents.views.Views.render2response` 
compliant
        service used to implement the ``View`` part of the ``MVC`` 
design
        pattern.

      - A :meth:`aurora.webapp.infrastructure.Application.url_for` 
compliant
        service used to create suitable URLs for known Web request 
handlers.

     This component can be simply installed into your Web application by
     calling the two services (:meth:`setup_mapping` and 
:meth:`setup_views`)
     used to setup the component.

     The Web requests handler provided by this component produce partial
     content and therefore you need to use the
     :class:`aurora.webcomponents.layout.Layout` component to produce 
the
     final HTML output.
     """

     def __init__(self, get_engine: 
engine_provider.EngineProvider.get_engine,
                  render2response: views.Views.render2response,
                  url_for: infrastructure.Application.url_for):
         self.get_engine = get_engine
         self.render2response = render2response
         self.url_for = url_for

         # create the model tables if they don't exist
         models.Model.metadata.create_all(self.get_engine())

     #
     # stubs for services required by the component
     #

     def get_engine(self) -> engine.Engine:
         """ Return a :class:`sqlalchemy.engine.Engine` object.

         :return: A ready to use :class:`sqlalchemy.engine.Engine` 
object.
         """
         raise NotImplementedError()

     def render2response(self, request: foundation.Request, 
template_name: str,
                         **context) -> foundation.Response:
         """ Render a template into a 
:class:`~aurora.webapp.foundation.Response` object with context.

         Template file names must have two extensions. The first 
extension is
         used to identify the content type of the output and the second
         extension is used to identify the engine capable of handling
         correctly the syntax used in the template.

         :param request: The request object used to build the response.
         :param template_name: The relative template name string without 
the
             last extension.
         :param context: The context mapping.
         :return: The rendered 
:class:`~aurora.webapp.foundation.Response`
             object.
         """
         raise NotImplementedError()

     def url_for(self, **characteristics) -> str:
         """ Create a fully usable url.

         :param characteristics: The Web request path characteristics.
         :return: A fully usable url.
         """
         raise NotImplementedError()

     def get_profile_id(self, request: foundation.Request) -> str:
         """ Return the profile ID for the user related to the Web 
request.
         :param request: A Web request object.
         :return: The profile ID string.
         """
         return 'admin'

     #
     # services provided by the component
     #

     def setup_mapping(self, add_rule: mapping.Mapper.add_rule, 
base_path='/'):
         add_rule(mapping.Route(base_path), _handler=self.list_posts)
         add_rule(mapping.Route(''.join((base_path, '(?P<id>\d+)'))),
             _handler=self.show_post)
         add_rule(mapping.Route(''.join((base_path, 'compose'))),
             _handler=self.compose_post)

     def setup_views(self, add_path: views.Views.add_path):
         add_path(os.path.join(os.path.dirname(__file__), 'templates'))

     @layout.partial
     def list_posts(self, request: foundation.Request) -> 
foundation.Response:
         """ List summaries for posts added more recently. """
         orm_session = orm.sessionmaker(bind=self.get_engine())()

         return self.render2response(request, 'blog/list.html',
             posts=orm_session.query(models.Post).filter(
                 models.Post.published != None).order_by(
                 sqlalchemy.desc(models.Post.published))[:10],
             blog=self, url_for=self.url_for)

     @layout.partial
     def show_post(self, request: foundation.Request) -> 
foundation.Response:
         """ Present a Blog post on the client browser.

         The value of the element from the `params` request mapping with
         keyword `id` is used as table row id if present.

         :param request: The
             :class:`Web request <aurora.webapp.foundation.Request>` 
object.
         :return: A :class:`Web response 
<aurora.webapp.foundation.Response>`
             object.
         """

         id = request.params['id']

         orm_session = orm.sessionmaker(bind=self.get_engine())()

         post = orm_session.query(models.Post).filter_by(id=id).one()

         return self.render2response(request, 'blog/show.html', 
post=post,
             blog=self, url_for=self.url_for)

     @layout.partial
     def compose_post(self, request: foundation.Request) -> 
foundation.Response:
         """ Present a form on the client browser used to compose a new 
Post.

         If the request method is ``POST`` this Web request handler 
attempt to
         process and persist the Blog post.

         :param request: The
             :class:`Web request <aurora.webapp.foundation.Request>` 
object.
         :return: A :class:`Web response 
<aurora.webapp.foundation.Response>`
             object.
         """
         if request.method == 'POST':
             # process form submission
             # TODO: need to implement form validation here.
             post = models.Post(
                 title=request.POST['title'],
                 content=request.POST['content'],
                 author=self.get_profile_id(request),
                 created = datetime.datetime.utcnow(),
                 modified = datetime.datetime.utcnow(),
                 published=datetime.datetime.utcnow(),
             )

             orm_session = orm.sessionmaker(bind=self.get_engine())()
             orm_session.add(post)
             orm_session.commit()

             # redirect to the post page
             resp = request.response_factory()
             resp.status_int = 302
             resp.location = self.url_for(_handler=self.show_post,
                 id=str(post.id))

             return resp
         else:
             return self.render2response(request, 'blog/form.html', 
blog=self,
                 url_for=self.url_for)
============================ fin del modulo blog 
===============================

fijate que el modulo engine_provider solo se usa en el modulo blog como 
documentacion en las anotaciones del
constructor.

luego para realizar la inyeccion de el proveedor de conexion en el 
componente blog empleo el siguiente modulo
============================ inicio del modulo di 
================================
# Copyright (c) 2011, Yeiniel Suarez Sosa.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions 
are met:
#
#    * Redistributions of source code must retain the above copyright 
notice,
#      this list of conditions and the following disclaimer.
#
#    * Redistributions in binary form must reproduce the above 
copyright
#      notice, this list of conditions and the following disclaimer in 
the
#      documentation and/or other materials provided with the 
distribution.
#
#    * Neither the name of Yeiniel Suarez Sosa. nor the names of its
#      contributors may be used to endorse or promote products derived 
from
#      this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
"AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
SOFTWARE,
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
""" DI tools
"""

import collections

__all__ = ['Dependency', 'list', 'dict', 'Reference', 'Value', 
'inject',
            'create_descriptor']


class Dependency:
     """ Dependency definition

     A dependency definition is used to construct the DI specification 
providing
     an indirection level.

     You don't need to inherit from this class to create a new 
definition type.
     The DI framework use duck typing thus as long as you provide a 
callable
     attribute named `resolve` that accept a single positional argument,
     everything is OK.
     """

     def resolve(self, container: object) -> object:
         """ Resolve the target dependency

         This method is called by the DI framework in order to remove 
the
         indirection level at target construction. The only positional 
argument
         it receives is the DI container used as source.
         """
         raise NotImplementedError()


class list(Dependency, list):
     """ Definition that once it's resolved produce a list of 
dependencies

     It takes as constructor argument a list of dependency definition 
objects.
     """

     def resolve(self, container):
         for i, value in enumerate(self):
             value = _normalize_dependency(value)

             self[i] = value.resolve(container)

         return self

class dict(Dependency, dict):

     def resolve(self, container: object):
         for key, value in self.items():
             value = _normalize_dependency(value)

             self[key] = value.resolve(container)

         return self


class Reference(Dependency):
     """ Definition resolved as a named reference relative to the DI 
container.

     This is the most common definition type and every string found in 
the
     specification it is assumed to be a referenced and converted to an 
object
     of this type before the target object is built.
     """

     def __init__(self, reference: str):
         self.reference = reference

     def resolve(self, container):
         target = container
         for part in self.reference.split('.'):
             target = getattr(target, part)

         return target

class Value(Dependency):
     """ Definition resolved to a fixed value passed as constructor 
argument.

     This is a simple way used to pass configuration and fixed value 
objects.
     """

     def __init__(self, value):
         self.value = value

     def resolve(self, container):
         return self.value

def _normalize_dependency(dependency: Dependency) -> Dependency:

     if hasattr(dependency, 'resolve'):
         return dependency

     if isinstance(dependency, str):
         return Reference(dependency)

     return Value(dependency)

def inject(target_factory: collections.Callable, container: object, 
*arg_spec, **attr_spec):
     """ Produce target by injecting its dependencies.

     The first positional argument is the factory for constructing the 
target
     object. The second positional argument is the container used as
     source of dependencies and any other positional arguments are 
definitions
     for factory arguments.

     Named arguments are used as attribute definitions for the target 
object.

     :param target_factory: The target factory callable.
     :return: The ready to use target object.
     """
     # TODO: need to implement DI auto-resolution capabilities

     arg_spec = map(_normalize_dependency, arg_spec)
     attr_spec.update(map(
         lambda key, value: (key, _normalize_dependency(value)),
         attr_spec.items()
     ))

     args = list(map(lambda item: item.resolve(container), arg_spec))
     target = target_factory(*args)

     for key in list(attr_spec):
         setattr(target, key, attr_spec[key].resolve(container))

     return target


def create_descriptor(target_factory: collections.Callable, *arg_spec,
                       **attr_spec):
     """ Create a descriptor that produce target by injecting its 
dependencies.

     This function produce a descriptor object that create a target 
object
     the first time the attribute is accessed for every class instance. 
It use
     the class instance as DI container.
     """

     class Descriptor:

         def __init__(self):
             self.cache = {}

         def __get__(self, instance, owner):
             if instance is None:
                 return self
             try:
                 return self.cache[instance]
             except KeyError:
                 self.cache[instance] = inject(target_factory, instance,
                                               *arg_spec, **attr_spec)
                 return self.cache[instance]

     return Descriptor()
====================================== fin del modulo di 
=====================================

donde lo unico importante realmente son las dos ultimas funciones y 
finalmente un ejemplo de como emplearlo todo

====================================== inicio del modulo application 
=========================
# Copyright (c) 2011, Yeiniel Suarez Sosa.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions 
are met:
#
#    * Redistributions of source code must retain the above copyright 
notice,
#      this list of conditions and the following disclaimer.
#
#    * Redistributions in binary form must reproduce the above 
copyright
#      notice, this list of conditions and the following disclaimer in 
the
#      documentation and/or other materials provided with the 
distribution.
#
#    * Neither the name of Yeiniel Suarez Sosa. nor the names of its
#      contributors may be used to endorse or promote products derived 
from
#      this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
"AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
SOFTWARE,
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import os

from aurora import di, event, webapp
from aurora.webcomponents import assets, layout, views

from components import blog, engine_provider

__all__ = ['Application']


class Application(webapp.Application):
     """ Blogpress blogging application.
     """

     def __init__(self):
         super().__init__()

         self.mapper.add_rule(self.assets.rule_factory())

         self.blog.setup_mapping(self.mapper.add_rule)
         self.blog.setup_views(self.views.add_path)

         self.views.add_path(os.path.join(os.path.dirname(__file__),
             'templates'))
         self.views.add_default('url_for', self.url_for)
         self.views.add_default('assets', self.assets.handler)
         self.views.add_default('self', self)

         self.assets.add_path(os.path.join(os.path.dirname(__file__),
             'static'))

     assets = di.create_descriptor(assets.Assets)

     blog = di.create_descriptor(blog.Blog, 'db.get_engine',
         'views.render2response', 'url_for')

     db = di.create_descriptor(engine_provider.EngineProvider)

     layout = di.create_descriptor(layout.Layout, 'views.render')

     post_dispatch = di.create_descriptor(event.Event,
         di.list(['layout.post_dispatch']))

     views = di.create_descriptor(views.Views)

if __name__ == '__main__':
     from wsgiref import simple_server
     from aurora.webapp import foundation

     wsgi_app = foundation.wsgi(Application())
     httpd = simple_server.make_server('', 8008, wsgi_app)

     print("Serving on port 8008...")
     httpd.serve_forever()
================================ fin del modulo application 
========================

la parte de la clase Application donde se establecen estaticamente los 
atributos empleando di se pudiera reemplazar
por codigo en el constructor, solo que asi es mas elegante y las cosas 
estan a la vista. Lo que esta ocurriendo es simplemente que la primera 
vez que se trata de acceder a la propiedad blog de una instancia de la 
clase Applicacion, el decorador de di crea una instancia de la clase 
Blog y la cachea para este objeto de la clase application. se emplea el 
metodo get_engine del atributo db del objeto de la clase application 
como primer argumento del constructor del componente blog (que es el 
metodo que provee conexion a DB), donde el attributo db tambien se crea 
empleando DI. Lo interesante es que puedes modificar la base de datos 
adicionando un argumento a la llamada

db = di.create_descriptor(engine_provider.EngineProvider),

en el siguiente ejemplo empleamos una base de datos supuestamente para 
desarrollo

db = di.create_descriptor(engine_provider.EngineProvider, 
'sqlite:///application.dev.db')

bueno, asi es como hago las cosas, fijate que no uso herencia, porque 
el blog usa la conexion pero no es una conexion. DI es solo maquinaria 
para hacer el codigo as elegante donde usas los componentes, pero no 
para crear los componentes.

atentamente
Yeiniel


On 2013-10-23 05:54, Ander Garmendia wrote:
> Buenos dias,
>
> para empezar, Yeiniel, por favor, tuteame que si me siento mas viejo 
> ;)
>
> Siguiendo con el tema, lo que tenía yo en mente, era el segundo caso
> que has citado en el mensaje anterior: esto es B quiere exponer una
> funcionalidad F.
>
> Al respecto, un ejemplo, sería poder añadir conectividad con  BBDD 
> sin
> tener que modificar la clase B. El decorador se encargaria de
> gestionar la conexión y ejecutar las querys.
>
> Quizá este no sea el mejor ejemplo ( yo no lo haría de esa manera )
> pero creo que resume bien lo que planteé en el primer mensaje.
> Simplificando, el código sería el siguiente:
>
> class Decor(object):
>     '''Decora la clase para anadir funcionalidades'''
>     def __init__(self, arg):
>         self.arg = arg
>         print "Clase decoradora"
>         print self.arg
>
>     def __call__(self, cls):
>         class Wrapper(cls):
>             classattr = self.arg
>             print "En wrapper de clase decoradora"
>
>             def my_method(self, value):
>                 print "New my method {0}, conn string:
> {1}".format(value, self.classattr)
>
>             def new_method(self, value):
>                 print "New new method {0}".format(value)
>
>         return Wrapper
>
>
> @Decor('mysql')
> class B(object):
>     def __init__(self, nombre):
>         self.nombre = nombre
>         print "Creo objeto B"
>
>     def show_name(self):
>         print "My nombre es {0}".format(self.izena)
>
>     def __call__(self):
>         print "Inside call"
>
>
> a = B('Ander')
> a.my_method('Ander')
> a.new_method('Ander2')
>
> El código es válido y anade las funciones a la clase decorada, pero 
> no
> me acaba de gustar y es por eso por lo que formulé la pregunta.
>
> Un saludo.
>
> Ander.
>
> El día 22 de octubre de 2013 14:50, Yeiniel Suárez Sosa
> <yeiniel en uclv.cu> escribió:
>> Hola nuevamente
>> Yo normalmente no escribo en esta lista, lo cual es malo para la 
>> comunidad y
>> me disculpo, pero estoy haciéndolo ahora porque el tema es teórico y 
>> merece
>> la pena (al menos desde mi punto de vista) lograr buenas prácticas 
>> en la
>> comunidad.
>>
>> Ander Garmendia
>> en el post original usted dice que quiere añadir una funcionalidad F 
>> a una
>> clase B. Aquí es necesario dejar bien claro si el problema es que B 
>> necesita
>> la funcionalidad F para hacer su trabajo o si B tiene que exponer la
>> funcionalidad F. En el primer caso estamos tratando con un problema 
>> de
>> dependencia, del cual expuse mi forma de tratarlo. Si el problema es 
>> que B
>> necesita exponer a F, bueno el problema es otro, porque todo depende 
>> de que
>> tipo de elemento es F. La herencia es para crear objetos que son una
>> especialización de otro más general, que B herede de C solo es 
>> correcto si B
>> es un caso especifico de C, de lo contrario la herencia no tiene 
>> sentido. En
>> el caso de que B necesite exponer un método de C pero no es una
>> especialización de esta clase, entonces el problema todavía es de
>> dependencia (composición) y no de herencia.
>>
>> ahh, se me olvidaba, la sintaxis correcta para inyectar atributos a 
>> la clase
>> es la de Juan, saludos para el
>>
>> Hernan M. F
>> Particularmente encuentro el ejemplo que presentas como poco 
>> elegante.
>> Porque en ese caso la clase C tiene una dependencia fija en el 
>> código al
>> método F() de la clase D la cual no puede ser modificada (bueno, si 
>> puede
>> ser modificada empleando settatr(), lo que hace a Python excelente 
>> desde mi
>> punto de vista). La cuestión es que se programa una vez, pero se 
>> modifica el
>> código y se revisa múltiples veces y en este caso el usuario de C 
>> tiene que
>> leer todo el código para darse cuenta de que cambiar si necesita 
>> reemplazar
>> D.F por otra cosa. Y finalmente si F es una función que no dependa 
>> de que
>> argumentos reciba D en el constructor, entonces no es necesario que 
>> sea
>> miembro de la clase D. Yo pondría tu ejemplo de la siguiente forma:
>>
>> class C:
>>   def __init__(self, f=None):
>>     if f is None:
>>       d = D()
>>       f = d.f
>>
>>     settatr(self, 'f', f)
>>
>>   def f(self):
>>     raise NotImplementedError()
>>
>> Esta variante le deja claro al usuario que solo con parametrizar la 
>> clase C
>> puede reemplazar la maquinaria externa que consume la clase. 
>> Finalmente
>> quiero decir (mi criterio nuevamente y el de algunas personas que 
>> asi lo
>> ponen en sus blogs) que la herencia multiple no es la forma de 
>> inyectar
>> comportamiento en una clase y que debe ser usada con mucho cuidado. 
>> Herencia
>> es herencia, una persona hereda de animal pero un navegador web no 
>> hereda de
>> conexión de red solo porque la use y la herencia lo pueda hacer 
>> parecer que
>> funciona.
>>
>> Atentamente
>> Ing. Yeiniel Suárez Sosa
>> Profesor Instructor, Dep. Automática y Sistemas Computacionales
>> Facultad de Ingeniería Eléctrica, Universidad Central "Marta Abreu" 
>> de las
>> Villas (UCLV)
>>
>>
>> On 2013-10-22 04:14, Hernan M. F. wrote:
>>>>
>>>> gracias a todos por el interes. Creo que me ha quedado bastante 
>>>> claro el
>>>> asunto.
>>>>
>>>> - Yeiniel me ha gustado tu solución, solo que yo la utilizaría con 
>>>> la
>>>> sintaxis que ha utilizado Juan.
>>>> - Sergio, no estoy intentando resolver ningún problema, solamente
>>>> estoy "jugando" con los decoradores y viendo de lo que son 
>>>> capaces. Y
>>>> mi pregunta surge desde ese interes.
>>>> - Y enlazando la frase anterior, gracias Txema por tu post, ya que
>>>> bien explicas para que son bueno los decoradores y para que no.
>>>
>>>
>>> Ten cuidado cuando cambies el comportamiento de objetos al vuelo.
>>>
>>> Si vas a componer clases ¿por qué complicarse?. Usa lo estándar:
>>>   class C (B):
>>>      def __init__(self):
>>>         self._f_provider = D()
>>>      def F(self):
>>>         self._f_provider.F()
>>>
>>> Tampoco estás obligado a definir una clase y usar métodos, esto no 
>>> es
>>> Java.
>>> F() podría ser un procedimiento o función de un módulo.
>>>
>>> Con la herencia múltiple de Python (que a veces se nos olvida que 
>>> tiene),
>>> sería:
>>> 'class C (B,D)' y no tienes que hacer mas nada. Eso sí, te compras
>>> otros ocho mil
>>> problemas nuevos…
>>>
>>> Y si el problema y el marco de la solución lo merece lo mas formal 
>>> es usar
>>> abc.
>>>
>>> Keep it simple. ;-)
>>>
>>>>
>>>> El día 21 de octubre de 2013 18:48, Txema Vicente 
>>>> <txema en nabla.net>
>>>> escribió:
>>>>>
>>>>> Buenas.
>>>>>
>>>>> Aunque puedas usar decoradores para ampliar la clase que decoran, 
>>>>> yo no
>>>>> veo
>>>>> los decoradores como sustitutos de la herencia, ni ninguna 
>>>>> reduccion de
>>>>> codigo.
>>>>>
>>>>> No necesitas decoradores para hacer eso, puedes asignar una 
>>>>> funcion a un
>>>>> atributo de la clase (B.F = F). Ademas, como te pongas a crear 
>>>>> clases
>>>>> decoradas que se amplian en ejecucion, a ver como lo explicas 
>>>>> luego.
>>>>>
>>>>> Los decoradores vienen bien, por ejemplo, para "enchufar" 
>>>>> funciones que
>>>>> van
>>>>> a manejar algo, como funciones que van a tratar los eventos de un 
>>>>> GUI, o
>>>>> responder en una ruta URL @ruta("/admin"). Dependiendo de lo que 
>>>>> quieras
>>>>> hacer, sera con una funcion o con una clase, con argumentos o sin 
>>>>> ellos.
>>>>>
>>>>> Tambien tienes el decorador @classmethod por si quieres crear 
>>>>> clases que
>>>>> puedan tener casos particulares (miclase = B.ampliada_con_F()), o 
>>>>> actuar
>>>>> como "factoria" de clases.
>>>>> Y @staticmethod, que yo solo lo uso en raras ocasiones por 
>>>>> motivos de
>>>>> organizacion de API.
>>>>>
>>>>> La herencia es algo claro y maravilloso que te permite organizar 
>>>>> las
>>>>> cosas.
>>>>> El decorador es un "atajo del idioma" para trastear con las 
>>>>> funciones,
>>>>> no
>>>>> hay nada que realmente no puedas hacer sin usarlo.
>>>>>
>>>>>
>>>>> El 21/10/2013 15:37, Ander Garmendia escribió:
>>>>>
>>>>> Buenas,
>>>>>
>>>>> estoy 'jugando' con decoradores y haciendo diferentes pruebas y 
>>>>> tengo
>>>>> una duda que quizá alguien me pueda aclarar.
>>>>>
>>>>> Digamos que tenemos una clase ( llamemosla B ) a la que queremos
>>>>> añadir una funcionalidad (llamemosla F). El método clásico sería
>>>>> heredar desde la clase base ( B ) y crear una nueva clase ( 
>>>>> llamemosla
>>>>> C ) que implementase nuestra funcionalidad ( F ). Hasta aquí todo
>>>>> normal y corriente.
>>>>>
>>>>> Ahora llega python y nos ofrece los decoradores, por lo tanto, 
>>>>> podemos
>>>>> crear una clase decoradora ( llamemosla D ) que implemente la
>>>>> funcionalidad ( F ) y que decorando una clase ( volvamos a la 
>>>>> clase B
>>>>> ), añade la funcionalidad F en la clase B sin necesidad de 
>>>>> herencias
>>>>> de ningún tipo.
>>>>>
>>>>> Visto así, todo parece muy cómodo, se escribe menos código, hay 
>>>>> menos
>>>>> clases implicadas, etc.
>>>>> Y como todo parece muy bonito, aquí surge mi duda: ¿Está esta 
>>>>> practica
>>>>> extendida al escribir código en python ( es pythonico y aceptable 
>>>>> ) ?
>>>>> ¿ o es mas una prueba conceptual ?
>>>>>
>>>>> Gracias de antemano y un saludo.
>>>>>
>>>>> Ander.
>>>>> _______________________________________________
>>>>> Python-es mailing list
>>>>> Python-es en python.org
>>>>> https://mail.python.org/mailman/listinfo/python-es
>>>>> FAQ: http://python-es-faq.wikidot.com/
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> _______________________________________________
>>>>> Python-es mailing list
>>>>> Python-es en python.org
>>>>> https://mail.python.org/mailman/listinfo/python-es
>>>>> FAQ: http://python-es-faq.wikidot.com/
>>>>>
>>>> _______________________________________________
>>>> Python-es mailing list
>>>> Python-es en python.org
>>>> https://mail.python.org/mailman/listinfo/python-es
>>>> FAQ: http://python-es-faq.wikidot.com/
>>>
>>>
>>> _______________________________________________
>>> Python-es mailing list
>>> Python-es en python.org
>>> https://mail.python.org/mailman/listinfo/python-es
>>> FAQ: http://python-es-faq.wikidot.com/
>>
>>
>> --
>>
>> _______________________________________________
>> Python-es mailing list
>> Python-es en python.org
>> https://mail.python.org/mailman/listinfo/python-es
>> FAQ: http://python-es-faq.wikidot.com/
> _______________________________________________
> Python-es mailing list
> Python-es en python.org
> https://mail.python.org/mailman/listinfo/python-es
> FAQ: http://python-es-faq.wikidot.com/

-- 
Ing. Yeiniel Suárez Sosa
Profesor Instructor, Dep. Automática
FIE, UCLV


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