我怎样才能使dict的子类尽可能“完美”?
最终目标是要有一个简单的dict,其中的键是小写的。
-
如果我超驰
__getitem__
/
__setitem__
,则get/set不起作用。怎么用?
我能让它们工作吗?当然我不需要执行它们
个别地?
-
我是不是在阻止酸洗工作,我需要执行
__setstate__
等?
-
我需要报告,更新和
__init__
?
-
我应该用一下吗
mutablemapping
(似乎不应该用
UserDict
或
DictMixin
)?如果是这样,怎么办?医生们并不是很有启发性。
接受的答案将是我的第一个方法,但由于它有一些问题,
由于没有人讨论过替代方案,因此实际上将
dict
我要在这里做。
接受的答案有什么问题?
对我来说,这似乎是一个相当简单的请求:
我怎样才能使dict的子类尽可能“完美”?
最终目标是要有一个简单的dict,其中的键是小写的。
接受的答案实际上不是子类
双关语
,测试失败:
>>> isinstance(MyTransformedDict([('Test', 'test')]), dict)
False
理想情况下,任何类型检查代码都将测试我们期望的接口或抽象基类,但如果我们的数据对象正被传递到正在测试的函数中,
双关语
-我们不能“修复”那些函数,这段代码会失败。
一个人可能会制造的其他诡辩:
-
接受的答案也缺少ClassMethod:
fromkeys
.
-
接受的答案也有一个多余的
__dict__
-因此占用了更多的内存空间:
>>> s.foo = 'bar'
>>> s.__dict__
{'foo': 'bar', 'store': {'test': 'test'}}
实际子类化
双关语
我们可以通过继承重用dict方法。我们所需要做的就是创建一个接口层,以确保键以小写形式(如果它们是字符串)传递到dict中。
如果我超驰
第二章
/
第七节
,则get/set不起作用。我如何让它们工作?当然,我不需要单独执行它们吗?
好吧,单独实现它们是这种方法的缺点,也是使用它们的好处
MutableMapping
(见公认的答案),但实际上并没有那么多的工作。
首先,让我们考虑一下python 2和3之间的区别,创建一个单例(
_RaiseKeyError
)以确保我们知道我们是否真的得到一个论点
dict.pop
,并创建一个函数以确保字符串键是小写的:
from itertools import chain
try: # Python 2
str_base = basestring
items = 'iteritems'
except NameError: # Python 3
str_base = str, bytes, bytearray
items = 'items'
_RaiseKeyError = object() # singleton for no-default behavior
def ensure_lower(maybe_str):
"""dict keys can be any hashable object - only call lower if str"""
return maybe_str.lower() if isinstance(maybe_str, str_base) else maybe_str
现在我们实施-我正在使用
super
使用完整的参数,以便此代码适用于python 2和3:
class LowerDict(dict): # dicts take a mapping or iterable as their optional first argument
__slots__ = () # no __dict__ - that would be redundant
@staticmethod # because this doesn't make sense as a global function.
def _process_args(mapping=(), **kwargs):
if hasattr(mapping, items):
mapping = getattr(mapping, items)()
return ((ensure_lower(k), v) for k, v in chain(mapping, getattr(kwargs, items)()))
def __init__(self, mapping=(), **kwargs):
super(LowerDict, self).__init__(self._process_args(mapping, **kwargs))
def __getitem__(self, k):
return super(LowerDict, self).__getitem__(ensure_lower(k))
def __setitem__(self, k, v):
return super(LowerDict, self).__setitem__(ensure_lower(k), v)
def __delitem__(self, k):
return super(LowerDict, self).__delitem__(ensure_lower(k))
def get(self, k, default=None):
return super(LowerDict, self).get(ensure_lower(k), default)
def setdefault(self, k, default=None):
return super(LowerDict, self).setdefault(ensure_lower(k), default)
def pop(self, k, v=_RaiseKeyError):
if v is _RaiseKeyError:
return super(LowerDict, self).pop(ensure_lower(k))
return super(LowerDict, self).pop(ensure_lower(k), v)
def update(self, mapping=(), **kwargs):
super(LowerDict, self).update(self._process_args(mapping, **kwargs))
def __contains__(self, k):
return super(LowerDict, self).__contains__(ensure_lower(k))
def copy(self): # don't delegate w/ super - dict.copy() -> dict :(
return type(self)(self)
@classmethod
def fromkeys(cls, keys, v=None):
return super(LowerDict, cls).fromkeys((ensure_lower(k) for k in keys), v)
def __repr__(self):
return '{0}({1})'.format(type(self).__name__, super(LowerDict, self).__repr__())
对于引用键的任何方法或特殊方法,我们都使用一种类似于锅炉板的方法,但否则,通过继承,我们得到方法:
len
,
clear
,
items
,
keys
,
popitem
和
values
免费。虽然这需要仔细考虑才能纠正,但看到这一点是不重要的。
(注意
haskey
在python 2中已弃用,在python 3中已删除。)
以下是一些用法:
>>> ld = LowerDict(dict(foo='bar'))
>>> ld['FOO']
'bar'
>>> ld['foo']
'bar'
>>> ld.pop('FoO')
'bar'
>>> ld.setdefault('Foo')
>>> ld
{'foo': None}
>>> ld.get('Bar')
>>> ld.setdefault('Bar')
>>> ld
{'bar': None, 'foo': None}
>>> ld.popitem()
('bar', None)
我是不是在阻止酸洗工作,我需要执行
α-塞特他汀
等?
酸洗
而dict子类pickles就很好了:
>>> import pickle
>>> pickle.dumps(ld)
b'\x80\x03c__main__\nLowerDict\nq\x00)\x81q\x01X\x03\x00\x00\x00fooq\x02Ns.'
>>> pickle.loads(pickle.dumps(ld))
{'foo': None}
>>> type(pickle.loads(pickle.dumps(ld)))
<class '__main__.LowerDict'>
__repr__
我需要报告,更新和
爱因斯坦
?
我们定义
update
和
爱因斯坦
但是你有一个美丽的
第二乐章
默认情况下:
>>> ld # without __repr__ defined for the class, we get this
{'foo': None}
不过,写一篇
第二乐章
以提高代码的可调试性。理想的测试是
eval(repr(obj)) == obj
. 如果您的代码很容易实现,我强烈建议您:
>>> ld = LowerDict({})
>>> eval(repr(ld)) == ld
True
>>> ld = LowerDict(dict(a=1, b=2, c=3))
>>> eval(repr(ld)) == ld
True
你看,这正是我们重新创建等效对象所需要的——这可能会出现在我们的日志或回溯中:
>>> ld
LowerDict({'a': 1, 'c': 3, 'b': 2})
结论
我应该用一下吗
诱变
(似乎不应该用
用户名
或
独奏曲
)?如果是这样,怎么办?医生们并不是很有启发性。
是的,这是另外几行代码,但它们的目的是全面的。我的第一个倾向是使用公认的答案,
如果有问题的话,我会看一下我的答案——因为它有点复杂,没有ABC来帮助我正确的界面。
在寻找性能方面,过早的优化将带来更大的复杂性。
诱变
更简单-所以它有一个直接的边缘,所有其他的都是相等的。不过,为了说明所有的区别,我们来比较一下。
我应该补充一下,有人推动把一本类似的字典放进
collections
模块,但
it was rejected
. 你应该改为这样做:
my_dict[transform(key)]
它应该更容易调试。
比较和对比
有6个接口功能通过
诱变
(不见了
从钥匙
)和11一起
双关语
子类。我不需要执行
__iter__
或
__len__
但是我必须执行
get
,
setdefault
,
pop
,
更新
,
copy
,
__contains__
和
从钥匙
-但是这些都是相当小的,因为我可以为大多数实现使用继承。
这个
诱变
在python中实现一些
双关语
在C中实现-所以我希望
双关语
子类在某些情况下更具性能。
我们得到自由
__eq__
在这两种方法中(这两种方法都假定只有当另一个dict都是小写时才相等),但我再次认为
双关语
子类比较更快。
总结:
-
子类化
诱变
更简单,错误机会更少,但速度较慢,占用更多内存(参见冗余dict),并且失败
isinstance(x, dict)
-
子类化
双关语
更快,占用更少的内存,并通过
IsInstance(X,dict)
但实施起来更为复杂。
哪个更完美?这取决于你对完美的定义。