Python模式的RestrictingWrapper与元类

时间:2012-09-25 04:18:30

标签: python design-patterns numpy pandas metaclass

我正在尝试创建一个阻止某些方法执行的包装器。经典的解决方案是使用这种模式:

class RestrictingWrapper(object):
    def __init__(self, w, block):
        self._w = w
        self._block = block
    def __getattr__(self, n):
        if n in self._block:
            raise AttributeError, n
        return getattr(self._w, n)

此解决方案的问题是每次调用时引入的开销,因此我尝试使用MetaClass来完成相同的任务。这是我的解决方案:

class RestrictingMetaWrapper(type):
    def __new__(cls, name, bases, dic):
        wrapped = dic['_w']
        block = dic.get('_block', [])

        new_class_dict = {}
        new_class_dict.update(wrapped.__dict__)
        for attr_to_block in block:
            del new_class_dict[attr_to_block]
        new_class_dict.update(dic)

        return type.__new__(cls, name, bases, new_class_dict)

与简单的课程完美配合:

class A(object):
    def __init__(self, i):
        self.i = i
    def blocked(self):
        return 'BAD: executed'
    def no_blocked(self):
        return 'OK: executed'
class B(object):
    __metaclass__ = RestrictingMetaWrapper
    _w = A
    _block = ['blocked']

b= B('something')
b.no_blocked  # 'OK: executed'
b.blocked     # OK: AttributeError: 'B' object has no attribute 'blocked'

问题来自'更复杂'的类,如来自numpy的ndarray

class NArray(object):
    __metaclass__ = RestrictingMetaWrapper
    _w = np.ndarray
    _block = ['max']

na = NArray()        # OK
na.max()             # OK: AttributeError: 'NArray' object has no attribute 'max'
na = NArray([3,3])   # TypeError: object.__new__() takes no parameters
na.min()             # TypeError: descriptor 'min' for 'numpy.ndarray' objects doesn't apply to 'NArray' object

我认为我的元类没有很好地定义,因为其他类(例如:pandas.Series)会遇到奇怪的错误,比如没有阻止指定的方法。

你能找到错误的位置吗?还有其他想法来解决这个问题吗?

更新 nneonneo的解决方案效果很好,但似乎包装类可以在类定义中使用一些黑魔法打破阻塞。

使用nneonneo的解决方案:

import pandas

@restrict_methods('max')
class Row(pandas.Series):
    pass

r = Row([1,2,3])
r.max()        # BAD: 3      AttributeError expected  

1 个答案:

答案 0 :(得分:1)

正如TypeError中所述,min(以及相关函数)仅适用于np.ndarray的实例;因此,新的子类必须从您尝试包装的类继承。

然后,由于扩展了基类,因此必须使用合适的描述符替换方法:

class RestrictedMethod(object):
    def __get__(self, obj, objtype):
        raise AttributeError("Access denied.")

class RestrictingMetaWrapper(type):
    def __new__(cls, name, bases, dic):
        block = dic.get('_block', [])

        for attr in block:
            dic[attr] = RestrictedMethod()

        return type.__new__(cls, name, bases, dic) # note we inject the base class here

class NArray(np.ndarray):
    __metaclass__ = RestrictingMetaWrapper
    _block = ['max']

注意:有进取心的应用程序仍然可以通过基类方法访问“受限”功能(例如np.ndarray.max(na))。

编辑:简化包装并使其透明地可子类化。

请注意,这可以使用类装饰器以更简单的方式完成:

class RestrictedMethod(object):
    def __get__(self, obj, objtype):
        raise AttributeError("Access denied.")

def restrict_methods(*args):
    def wrap(cls):
        for attr in args:
            setattr(cls, attr, RestrictedMethod())
        return cls
    return wrap

@restrict_methods('max', 'abs')
class NArray(np.ndarray):
    pass