どうすれば、dictのサブクラスをできるだけ「完璧」にすることができますか?
最終的な目標は、キーが小文字である単純な辞書を持つことです。
__getitem__
/ をオーバーライドする__setitem__
と、get / setが機能しなくなります。それらをどのように機能させるのですか?確かに個別に実装する必要はありませんか?
酸洗いが機能しないようにしてい__setstate__
ますか?実装する必要があり
ますか?
repr、update、および必要__init__
ですか?
私はちょうど使用する必要がありますmutablemapping
(それは1つが使うべきではないと思われますUserDict
かDictMixin
)?もしそうなら、どうですか?ドキュメントは正確には啓蒙的ではありません。
受け入れられた答えは私の最初のアプローチですが、いくつかの問題があり、誰も代替案に取り組んでいないため、実際にはをサブクラス化dict
しているので、ここでそれを行います。
受け入れられた答えの何が問題になっていますか?
これは私にはかなり単純な要求のようです:
どうすれば、dictのサブクラスをできるだけ「完璧」にすることができますか?最終的な目標は、キーが小文字である単純な辞書を持つことです。
受け入れられた答えは実際にはサブクラスdict
ではなく、これに対するテストは失敗します:
>>> isinstance(MyTransformedDict([('Test', 'test')]), dict)
False
理想的には、型チェックコードは、期待するインターフェイスまたは抽象基本クラスをテストすることになりますが、データオブジェクトがテスト対象の関数に渡されておりdict
、これらの関数を「修正」できない場合、このコード失敗します。
人が作るかもしれない他の問題:
- 受け入れられた回答にもclassmethod:がありません
fromkeys
。
受け入れられた回答にも冗長性があります__dict__
-したがって、メモリ内でより多くのスペースを使用します。
>>> s.foo = 'bar'
>>> s.__dict__
{'foo': 'bar', 'store': {'test': 'test'}}
実際にサブクラス化 dict
継承を通じてdictメソッドを再利用できます。文字列の場合、キーが小文字の形式でdictに渡されるようにするインターフェイスレイヤーを作成するだけです。
__getitem__
/ をオーバーライドする__setitem__
と、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)
酸洗いが機能しないようにしてい__setstate__
ますか?実装する必要があり
ますか?
酸洗い
そしてdictサブクラスはピクルスでうまくいきます:
>>> 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__
repr、update、および必要__init__
ですか?
とを定義update
しましたが__init__
、__repr__
デフォルトでは美しいです。
>>> ld # without __repr__ defined for the class, we get this
{'foo': None}
ただし、__repr__
コードのデバッグ性を向上させるためにを書くことは良いことです。理想的なテストは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})
結論
私はちょうど使用する必要がありますmutablemapping
(それは1つが使うべきではないと思われますUserDict
かDictMixin
)?もしそうなら、どうですか?ドキュメントは正確には啓蒙的ではありません。
ええ、これらはさらに数行のコードですが、包括的であることを目的としています。私の最初の傾向は、受け入れられた回答を使用することであり、それに問題がある場合は、私の回答を調べます。少し複雑で、インターフェースを正しくするためのABCがありません。
時期尚早な最適化は、パフォーマンスを追求する上でさらに複雑になります。
MutableMapping
より単純です-したがって、すぐに優位に立ち、他のすべては等しくなります。それでも、すべての違いを明らかにするために、比較して対比しましょう。
同様のディクショナリをcollections
モジュールに挿入するように要求されたが、拒否されたことを付け加えておきます。あなたはおそらく代わりにこれを行うべきです:
my_dict[transform(key)]
はるかに簡単にデバッグできるはずです。
比較対照
MutableMapping
(欠落fromkeys
)で実装された6つのインターフェース関数と、dict
サブクラスで実装された11のインターフェース関数があります。私が実装する必要はありません__iter__
か__len__
、代わりに私が実装する必要がありget
、setdefault
、pop
、update
、copy
、__contains__
、とfromkeys
私はそれらの実装のほとんどのための継承を使用することができるので、これらはかなり些細です- 。
MutableMapping
道具Pythonでいくつかのものというdict
Cで実装-私が期待するように、dict
サブクラスがいくつかのケースでよりパフォーマンスします。
__eq__
両方のアプローチで自由が得られます-どちらも、別の辞書がすべて小文字である場合にのみ同等であると仮定します-しかし、繰り返しになりますが、dict
サブクラスはより速く比較されると思います。
概要:
- サブクラス化
MutableMapping
は単純で、バグが発生する可能性は少なくなりますが、遅くなり、より多くのメモリを消費し(冗長な辞書を参照)、失敗しますisinstance(x, dict)
- サブクラス化
dict
は高速で、使用するメモリが少なく、合格しますがisinstance(x, dict)
、実装が複雑になります。
どちらがより完璧ですか?それはあなたの完璧の定義に依存します。