让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)
...但它适用于“常规”方法,例如keys
和items
,但不适用于特殊方法,例如__setitem__
,{{ 1}}和__getitem__
:
__len__
我尝试的所有特殊方法都产生了类似的错误。
如何自动定义特殊方法(以便将它们引用给委托)?
澄清:我不一定在寻找利用标准方法查找序列的解决方案。我的目标是最小化样板代码。
谢谢!
答案 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)
。