0

Block基本クラスとして機能するクラスがあります。そのサブクラスの 1 つがTemplateBlock.

class Block(object):

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

class TemplateBlock(Block):

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

サブパッケージblocks.ext.django(これblocksは私の最上位モジュールです) は、元のモジュールと同じクラスを提供することを目的としていますが、機能が改善されています (例: 追加のメソッド)。

# 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.djangoTemplateBlock

# blocks.ext.django

# ...

class TemplateBlock(blocks.TemplateBlock, Block):

    pass

これを実現するためのより良い設計を考えられますか?


注: 質問を完全に抽象化したくなかったので、元の名前をそのまま使用しました。django と関係があるかどうかは関係ありません。

4

2 に答える 2

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)
于 2012-09-29T19:02:25.620 に答える
0

これが今思いつくことができたものです。オブジェクト構成の方向性を教えてくれた@JakobBowyerに感謝します。このソリューションに完全に満足しているわけではありませんが、期待どおりに機能します。

クラスへの拡張機能を管理するために使用されるPython の記述子インターフェイスを実装するクラスを作成しました。拡張機能は、メソッド、クラス メソッド、またはプロパティのいずれかです。

このクラスを入れて、次のように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

免責事項: 上記のコードを公開することにより、ソースの複製と変更、およびそのような公開を許可します。

于 2012-09-29T17:22:28.587 に答える