如何在Python中自动委托__special_methods__?

时间:2011-12-16 22:56:58

标签: python

spam成为某个类Spam的实例,并假设spam.ham是某个内置类型的对象,比如dict。尽管Spam不是dict的子类,但我希望它的实例具有与常规dict相同的API(即具有相同签名的相同方法),但我想要避免输入以下形式的bazillion样板方法:

    def apimethod(self, this, that):
        return self.ham.apimethod(this, that)

我尝试了以下内容:

class Spam(object):
    def __init__(self):
        self.ham = dict()

    def __getattr__(self, attr):
        return getattr(self.ham, attr)

...但它适用于“常规”方法,例如keysitems,但不适用于特殊方法,例如__setitem__,{{ 1}}和__getitem__

__len__


我尝试的所有特殊方法都产生了类似的错误。

  

如何自动定义特殊方法(以便将它们引用给委托)?

澄清:我不一定在寻找利用标准方法查找序列的解决方案。我的目标是最小化样板代码。

谢谢!

4 个答案:

答案 0 :(得分:5)

如果您需要一个禁止元类的解决方案,这可能没有用,但这是我提出的解决方案:

def _wrapper(func):
    def _wrapped(self, *args, **kwargs):
        return getattr(self.ham, func)(*args, **kwargs)
    return _wrapped

class DictMeta(type):
    def __new__(cls, name, bases, dct):
        default_attrs = dir(object)
        for attr in dir(dict):
            if attr not in default_attrs:
                dct[attr] = _wrapper(attr)
        return type.__new__(cls, name, bases, dct)

class Spam(object):
    __metaclass__ = DictMeta
    def __init__(self):
        self.ham = dict()

似乎要做你想要的事情:

>>> spam = Spam()
>>> spam['eggs'] = 42
>>> spam.items()
[('eggs', 42)]
>>> len(spam)
1
>>> spam.ham
{'eggs': 42}

如果在Python 3.x上使用class Spam(object, metaclass=DictMeta)并从__metaclass__的正文中删除Spam行。

答案 1 :(得分:1)

这看起来像......一个元类的工作!

def make_method(p, m):
    def method(self, *a, **k):
        return getattr(getattr(self, p),m)(*a, **k)
    return method

class Proxier(type):
    def __new__(cls, name, bases, dict):
        objs = dict.get('proxyobjs', [])
        if objs:
            old_init = dict.get('__init__', lambda self: None)
            def new_init(self, *a, **k):
                for (n,v) in objs.iteritems():
                    setattr(self, n, v())
                old_init(self, *a, **k)
            dict['__init__'] = new_init
            meths = dict.get('proxymethods', {})
            for (proxyname, methnames) in meths.iteritems():
                for methname in methnames:                
                    dict[methname] = make_method(proxyname, methname)
        return super(Proxier, cls).__new__(cls, name, bases, dict)


class Spam(object):
    __metaclass__ = Proxier
    proxyobjs = {'ham': dict,
                 'eggs': list,
                 }
    proxymethods = {'ham': ('__setitem__', '__getitem__', '__delitem__'),
                    'eggs': ('__contains__', 'append')
                    } 

有效!

In [28]: s = Spam()

In [29]: s[4] = 'hi'

In [30]: s.append(3)

In [31]: 3 in s
Out[31]: True

In [32]: 4 in s
Out[32]: False

In [33]: s[4]
Out[33]: 'hi'

请注意,您必须指定您正在使用的界面的哪些部分(否则,为什么不继承?)。我们__contains__来自list__getitem__来自dict,两者都来自__iter__。 (并且只有一种方法可以改变基础列表,使用append而不是extend__delitem__。)所以(像火星人)我不确定这将是多么有用。

答案 2 :(得分:1)

特殊方法的属性访问不遵守常规属性访问规则,基本上那些方法必须存在于类级别,阅读http://docs.python.org/reference/datamodel.html#special-method-lookup-for-new-style-classes

所以你需要手动添加所有这些方法,或者你可以通过编程方式将它们添加到类中,最好的方法就是通过元类。另请注意,我不是在dict中添加所有方法,而只是添加特殊方法,因为可以通过__getattr__轻松重定向休息

def redirect(methodname):
    def _redirect(self, *args, **kwargs):
        print "redirecting",methodname
        method = getattr(self.ham, methodname) 
        return method(*args, **kwargs)

    return _redirect

class DictRedirect(object):
    def __new__(cls, name, bases, attrs):

        # re-create all special methods from dict
        dict_attr_names = set(dir(dict))
        common_names = set(dir(cls))
        for methodname in dict_attr_names-common_names:
            if not methodname.startswith('__'):
                continue
            attrs[methodname] = redirect(methodname)
        return type(name, bases, attrs)

class Spam(object):
    __metaclass__ = DictRedirect

    def __init__(self):
        self.ham = dict()

    def __getattr__(self, name):
        return getattr(self.ham, name)

spam = Spam()
spam['eggs'] = 'yolk'
print 'keys =',spam.keys()
print spam['eggs']

输出:

redirecting __setitem__
keys = ['eggs']
redirecting __getitem__
yolk

免责声明:IMO这太神奇了,除了玩得开心之外应该避免:)

答案 3 :(得分:0)

不确定__getattribute__会有所帮助,但原因是在类中没有查找特殊方法而不是实例:http://docs.python.org/reference/datamodel.html#special-method-lookup-for-new-style-classes,例如__getattr__等特殊方法__getattribute__自己必须在某处查找。

像这样代理似乎在没有仔细思考的情况下向我提出麻烦,例如,如果您的包装器碰巧有任何方法,那么__dict____class__之类的行为应如何表现以及可能的方法冲突还有其他问题。

Re:is-a vs. has-a:

如果你只复制包含成员的整个界面,对我来说似乎是反模式,因为这是继承的目的。如果你有两个与两个dict对象有关系怎么办?

在has-a关系中,人们通常选择有用的方法,通常以不同的名称导出它们以制作合理的API。因此,Spam.append(item)代替Spam.addBot(bot)