我有一个类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有什么关系并不重要。
答案 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
免责声明:通过向公众展示上述代码,我允许复制和修改源代码,以及此类出版物。