如何在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
。