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
<yein...@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 <tx...@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@python.org
https://mail.python.org/mailman/listinfo/python-es
FAQ: http://python-es-faq.wikidot.com/




_______________________________________________
Python-es mailing list
Python-es@python.org
https://mail.python.org/mailman/listinfo/python-es
FAQ: http://python-es-faq.wikidot.com/

_______________________________________________
Python-es mailing list
Python-es@python.org
https://mail.python.org/mailman/listinfo/python-es
FAQ: http://python-es-faq.wikidot.com/


_______________________________________________
Python-es mailing list
Python-es@python.org
https://mail.python.org/mailman/listinfo/python-es
FAQ: http://python-es-faq.wikidot.com/


--

_______________________________________________
Python-es mailing list
Python-es@python.org
https://mail.python.org/mailman/listinfo/python-es
FAQ: http://python-es-faq.wikidot.com/
_______________________________________________
Python-es mailing list
Python-es@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
_______________________________________________
Python-es mailing list
Python-es@python.org
https://mail.python.org/mailman/listinfo/python-es
FAQ: http://python-es-faq.wikidot.com/

Responder a