如何在Python中自动化特殊方法的委派?

如何在Python中自动化特殊方法的委派?,python,Python,让spam成为某个类spam的实例,并假设spam.ham是某种内置类型的对象,例如dict。尽管Spam不是dict的子类,但我希望它的实例具有与常规dict相同的API(即,具有相同签名的相同方法),但我希望避免键入以下形式的大量样板方法: def apimethod(self, this, that): return self.ham.apimethod(this, that) 我尝试了以下方法: class Spam(object): def __ini

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

    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)
…但它适用于“常规”方法,如
,但不适用于特殊方法,如
设置项
获取项
,和
\uu列

>>> spam = Spam()
>>> spam.keys()
[]
>>> spam['eggs'] = 42
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'Spam' object does not support item assignment
>>> spam.ham['eggs'] = 42
>>> foo.items()
[('eggs', 42)]
>>> spam['eggs']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'Spam' object is not subscritable
>>> len(spam)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'Spam' object has no len()
垃圾邮件=垃圾邮件() >>>spam.keys() [] >>>垃圾[鸡蛋]=42 回溯(最近一次呼叫最后一次): 文件“”,第1行,在 TypeError:“垃圾邮件”对象不支持项分配 >>>火腿[鸡蛋]=42 >>>foo.items() [(‘鸡蛋’,42)] >>>垃圾[鸡蛋] 回溯(最近一次呼叫最后一次): 文件“”,第1行,在 TypeError:“垃圾邮件”对象不可订阅 >>>len(垃圾邮件) 回溯(最近一次呼叫最后一次): 文件“”,第1行,在 TypeError:“垃圾邮件”对象没有len()

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

如何自动定义特殊方法(以便将它们引用到代理)

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


谢谢

不确定
\uuuuuu getattribute\uuuuuuu
是否有帮助,但原因是特殊方法是在类中而不是在实例中查找的:,例如,像
\uuuuuuu getattr\uuuuuuu
\uuuuuu getattribute\uuuuuuuuuuuu
这样的特殊方法本身必须在某个地方查找

这样的代理似乎没有仔细考虑就给我带来了麻烦,例如,
\uuu dict\uuuu
\uuu class\uuuu
之类的东西应该如何表现,如果你的包装器碰巧有任何方法,并且确定还有其他问题,那么可能会发生方法冲突

回复:is-a vs.has-a:

若您只是复制包含成员的整个接口,那个么对我来说似乎是反模式的,因为这就是继承的目的。如果你和两个dict对象有一个two-has-a关系呢


在has-a关系中,人们通常会选择有用的方法,并经常以不同的名称导出它们,以生成合理的API。因此,取而代之的是
Spam.append(item)
您将拥有
Spam.addBot(bot)
如果您需要禁止元类的解决方案,这可能没有帮助,但我提出了以下解决方案:

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}

如果在Python3.x上使用
类垃圾邮件(object,metaclass=DictMeta)
并从
垃圾邮件的主体中删除
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。。。元类

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'

请注意,您必须指定正在使用的接口的哪些部分(否则,为什么不直接继承?)。因此,我们有
\u包含
列表中的
/code>,和
目录中的
\u获取项目/code>,以及两者都不包含的
\u iter
。(而且只有一种方法可以改变基础列表,使用
append
,而不是
extend
\uuu delitem\uuuu
)因此(像火星人一样),我不确定这是否有用

特殊方法的属性访问不遵循正常的属性访问规则,基本上这些方法必须存在于类级别,读取

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

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

免责声明:在我看来,这太神奇了,除了好玩之外,应该避免:)

我相信你有一个很好的理由,但是你能解释一下为什么你不想让
垃圾邮件成为
dict
的子类吗。第二个扎克:如果你想看起来像一个
dict
,那么继承自
dict