寻找更好的设计(继承)

时间:2012-09-29 15:47:40

标签: python inheritance multiple-inheritance

我有一个类Block,它作为基类。其子类之一是TemplateBlock

class Block(object):

    def render(self, dest):
        # ...
        pass

class TemplateBlock(Block):

    def render(self, dest):
        # ...
        pass

子包blocks.ext.django(而blocks是我的顶级模块)旨在提供与原始模块相同的类,但具有改进的功能(例如,其他方法)。 / p>

# blocks.ext.django

import blocks
import django.http

class Block(blocks.Block):

    def render_to_response(self):
        # ...
        result = self.render(dest)
        return django.http.HtppRequest(result)

但是,我应该如何使render_to_response方法可用于blocks.ext.django等级的TemplateBlock类?以下对我来说实际上看起来不是一个好的设计:

# blocks.ext.django

# ...

class TemplateBlock(blocks.TemplateBlock, Block):

    pass

您能想到更好的设计来实现这一目标吗?


注意:我不想完全抽象问题,这就是我保留原名的原因。如果它与django有什么关系并不重要。

2 个答案:

答案 0 :(得分:1)

这次我非常满意。 :)我认为ExtensionManager的想法并不坏,所以我增强了整体设计。

现在可以将扩展程序添加到扩展管理器中,如下所示:

class Test(object):

    ext = ExtensionManager()

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

class Extensions():
    __metaclass__ = ExtensionClassMeta
    managers = [Test.ext]

    @Extension('method')
    def print_v(self):
        print self.v

Test('Value of Test instance').ext.print_v()
print Extensions

打印以下内容:

C:\Users\niklas\Desktop\blog>python -m blocks.utils.ext_manager
Value of Test instance
(<__main__.ExtensionManager object at 0x021725B0>,)

扩展管理器设置完全可自定义。例如,您可以创建自己的LookupManager实例,ExtensionManager将使用它来查找和包装扩展。

class CoolLookupManager(LookupManager):

    extension_types = ('ice',)

    def wrap_ice(self, name, object, instance, owner):
        return "%s is cool as ice." % object(instance)

class Test(object):

    ext = ExtensionManager(lookup_manager=CoolLookupManager())

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

class Extensions():
    __metaclass__ = ExtensionClassMeta
    managers = [Test.ext]

    @Extension('ice')
    def get_v(self):
        return self.v

print Test('StackOverflow').ext.get_v

导致以下输出:

C:\Users\niklas\Desktop\blog>python -m blocks.utils.ext_manager
StackOverflow is cool as ice.

我正在考虑将它放入一个单独的模块并将其发布到PyPi。到目前为止,这是代码:

# coding: UTF-8
# file:   blocks/utils/ext_manager.py
#
# Copyright (C) 2012, Niklas Rosenstein
""" blocks.utils.ext_manager - Class extension-manager. """

import functools

class ExtensionTypeError(Exception):
    """ Raised when an extension type is not supported. """

class Extension(object):
    """ This decorator is used to mark an attribute on an extension class
        as being actually an extension. """

    def __init__(self, type):
        super(Extension, self).__init__()
        self.type = type
        self.object = None

    def __str__(self):
        return '<Extension: %s>' % self.type

    def __call__(self, object):
        self.object = object
        return self

class ExtensionClassMeta(type):
    """ This meta-class processes an extension class and adds the defined
        extensions into the `ExtensionManager` objects defined in the
        extension class. """

    def __new__(self, name, bases, dict):
        # Ensure there is no base.
        if bases:
            raise ValueError('the ExtensionClassMeta meta-class does not accept bases.')

        # Obtain a list of the managers that need to be extended.
        managers = dict.pop('managers', None)
        if not managers:
            raise ValueError('at least one manager must be given in the class.')

        # A single ExtensionManager instance of the `managers` attribute is
        # allowed, so convert it to a list to ensure that the next test
        # will not fail.
        if isinstance(managers, ExtensionManager):
            managers = [managers]

        # Make sure the managers is a list.
        if not isinstance(managers, (list, tuple)):
            raise ValueError('managers names must be list or tuple.')

        # Iterate over all managers to ensure they're all ExtensionManager
        # instances.
        for manager in managers:
            if not isinstance(manager, ExtensionManager):
                raise ValueError('object in managers not instance of ExtensionManager class.')

        # Iterate over all attributes of the class and extend the managers.
        for name, value in dict.iteritems():
            # Only `Extension` instances will be registered to the extension
            # managers. Other values are just ignored.
            if isinstance(value, Extension):
                for manager in managers:
                    manager.register_extension(name, value.object, value.type)

        return tuple(managers)

class ExtensionManager(object):
    """ This class is used as a property to dynamically add methods and
        data-fields (also called extensions in this context) to a class.

        Any attribute  that will be gathered from this object will be wrapped
        according to the type of extension (see `register_extension()`). """

    def __init__(self, lookup_manager=None):
        super(ExtensionManager, self).__init__()
        self._extensions = {}

        if not lookup_manager:
            lookup_manager = StandartLookupManager()
        self.lookup_manager = lookup_manager

    def __get__(self, instance, owner):
        if not instance:
            return self
        else:
            return ExtensionToAttributeConnector(self, instance, owner)

    def __set__(self, instance, value):
        raise AttributeError("can't overwrite ExtensionManager property.")

    def __delete__(self, instance):
        raise AttributeError("can't delete ExtensionManager property.")

    def register_extension(self, name, object, type='method'):
        """ Register an extension to the manager. The type of *object* depends
            on the value of *type*. The extensions name must be passed with
            *name*. It is associated with *object* and used on attribute
            lookup. If the type is not valid, the lookup manager will
            raise an *ExtensionTypeError*.
            """
        self.lookup_manager.validate_type(type)
        self._extensions[name] = [object, type]

    def do_lookup(self, name, instance, owner):
        """ Forward the extension lookup to the lookup manager to obtain the
            value of an extension. """
        return self.lookup_manager.do_lookup(self._extensions, name, instance, owner)

class LookupManager(object):
    """ This is the base-class for lookup managers. A lookup manager is created
        by an `ExtensionManager` instance when watching out for a specific
        attribute on an instance.

        The `ExtensionManager` will ask the `LookupManager` to validate the
        type of an extension. The lookup manager itself will call functions
        depending on the type of an extension.

        If you have a lookup manager which supports the type `'FOO'`,
        and an extension of that type is requested, it will call the
        function `wrap_FOO()`. Such a method has the following signature:

            * `self`: The `LookupManager` instance.
            * `ext_name`: A string defining the name of the extension that
                          is looked up.
            * `instance`: The invoking instance, as passed by `__get__`.
            * `owner`: The invoking class, as passed by `__get__`.

        The `wrap_FOO()` function must wrap and return *object* so it can
        be used by the requestor.

        The types of extensions the lookup manager supports is defined in
        the `extension_types` attribute which *must* be an iterable of string.
        """

    extension_types = ()

    def do_lookup(self, extensions, name, instance, owner):
        """ Perform a lookup on the passed *extensions* and call the
            corresponding `wrap_FOO()` method. *extensions* should be a
            dictionary containing `(object, type)` pairs only where *object*
            is the registered extension and *type* is its type.

            *connector* is an instance of `ExtensionToAttributeConnector`. """

        object = extensions.get(name, None)
        if not object:
            raise AttributeError('no extension named %s.' % name)

        object, type = object
        lookup_name = 'wrap_%s' % type
        processor = getattr(self, lookup_name, None)
        if not processor:
            raise RuntimeError('no processor %s found in lookup manager.' % lookup_name)

        return processor(name, object, instance, owner)

    def validate_type(self, type):
        """ Validate the passed *type* by raising *ExtensionTypeError* if
            it is not supported. The default implementation checks if the
            passed type is defined in the `extension_types` field. """
        if not type in self.extension_types:
            raise ExtensionTypeError('Invalid type %s passed.' % type)

class StandartLookupManager(LookupManager):
    """ This is the standart lookup manager implementing the `'method'`,
        `'property'` and `'attr'` extension types. """

    extension_types = ('method', 'property', 'attr')

    def wrap_method(self, name, object, instance, owner):
        func = lambda *args, **kwargs: object(instance, *args, **kwargs)
        func = functools.wraps(object)(func)
        func.func_name = name
        func.__name__ = name
        return func

    def wrap_property(self, name, object, instance, owner):
        return object(instance)

    def wrap_attr(self, name, object, instance, owner):
        return object

class ExtensionToAttributeConnector(object):
    """ This class is the direct communication layer between the extensions
        and the user of the `ExtensionManager`. It is returned when the
        `ExtensionManager` is requested on an instance, so an attribute-lookup
        on an instance of this class will result in an extension-lookup. """

    def __init__(self, manager, instance, caller):
        super(ExtensionToAttributeConnector, self).__init__()
        self.manager = manager
        self.instance = instance
        self.caller = caller

    def __getattr__(self, name):
        return self.manager.do_lookup(name, self.instance, self.caller)

答案 1 :(得分:0)

这就是我现在能想到的。感谢@JakobBowyer指出了对象组合的方向。我对此解决方案并不完全满意,但它按预期工作。

我创建了一个实现Pythons' descriptor interface的类,用于管理类的扩展。扩展可以是方法,类方法或属性。

我已将此类放入blocks.utils.ext_manager并在主Block类中使用它,如下所示:

from blocks.utils.ext_manager import ExtensionManager

class Block(object):

    ext = ExtensionManager()

    # ...

现在可以在blocks.ext.django中注册此扩展程序:
(我还在寻找让它看起来更漂亮的方法......)

def Block_render_response(self):
    # ...

blocks.Block.ext.register_extension('render_response', Block_render_response, 'method')

通过实现描述符接口,ExtensionManager能够从调用实例获取引用ext属性的实例,并将其传递给注册到扩展管理器的方法。请参阅以下示例调用:

from blocks.ext.django import TemplateView

def index(request):
    block = TemplateView(template_name='foo.html')
    return block.ext.render_response()

缺点:我还没有实现,可以从类中调用注册为'classmethod'的方法,因为从类中引用ext属性将返回ExtensionManager本身未实现通过__getattr__获取已注册的扩展程序。

您可以找到ExtensionManager类打击的源代码。

# coding: UTF-8
# file:   blocks/utils/ext_manager.py
""" blocks.utils.ext_manager - Class extension-manager. """

import functools

class ExtensionManager(object):
    """ This class is used as a property to dynamically add methods and
        data-fields (also called extensions in this context) to a class.

        Any attribute  that will be gathered from this object will be wrapped
        according to the type of extension (see `register_extension()`). """

    def __init__(self):
        super(ExtensionManager, self).__init__()
        self._extensions = {}

    def __get__(self, instance, owner):
        if not instance:
            return self
        else:
            return ExtensionLookup(self._extensions, instance, owner)

    def __set__(self, instance, value):
        raise AttributeError("can't overwrite ExtensionManager property.")

    def register_extension(self, name, object, type='method'):
        """ Register an extension to the manager. The type of *object* depends
            on the value of *type*. The extensions name must be passed with
            *name*. It is associated with *object* and used on attribute
            lookup.

            * `type == 'method'`:

                *object* is assumed to be callable and is passed the calling
                instance of the host-class plus the arguments passed on
                method invocation.

            * `type == 'classmethod`:

                *object* is assumed to be callable and is passed the host-class
                plus the arguments passed on invocation.

            * `type == 'property'`:

                *object* can be of anytype and is returned as is.
            """

        self._extensions[name] = [object, type]

class ExtensionLookup(object):
    """ This is a private class used by the `ExtensionManager` class to
        wrap the registered together with an instance.

        Attribute lookup will be redirected to the registered extensions. """

    def __init__(self, extensions, instance, owner):
        super(ExtensionLookup, self).__init__()
        self.extensions = extensions
        self.instance = instance
        self.owner = owner

    def __getattr__(self, name):
        object, type = self.extensions[name]
        if type == 'method':
            func = lambda *args, **kwargs: object(self.instance, *args, **kwargs)
        elif type == 'staticmethod':
            func = lambda *args, **kwargs: object(self.owner, *args, **kwargs)
        elif type == 'property':
            return object
        else:
            raise RuntimeError('invalid extension-type found.')

        func = functools.wraps(object)(func)
        return func

免责声明:通过向公众展示上述代码,我允许复制和修改源代码,以及此类出版物。