这次我很满意。:) 我觉得这个想法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)