web-dev-qa-db-ja.com

辞書を「完全に」無効にする方法は?

可能な限りdictのサブクラスを「完璧」にするにはどうすればよいですか?最終目標は、キーが小文字である単純なdictにすることです。

この作業を行うためにオーバーライドできるプリミティブの小さなセットがあるはずですが、すべての私の研究と試みによると、そうではないようです:

  • __getitem__/__setitem__を上書き の場合、get/setは機能しません。どうすればそれらを機能させることができますか?確かにそれらを個別に実装する必要はありませんか?

  • 酸洗が機能しないのを防いでいますか?__setstate__などを実装する必要がありますか?

  • reprupdate、および__init__が必要

  • mutablemappingを使用UserDictまたはDictMixinを使用すべきではないようです)もしそうなら、どのように?ドキュメントは必ずしも啓発的なものではありません。

get()が機能せず、他にも多くの小さな問題があることは間違いありません。

class arbitrary_dict(dict):
    """A dictionary that applies an arbitrary key-altering function
       before accessing the keys."""

    def __keytransform__(self, key):
        return key

    # Overridden methods. List from 
    # https://stackoverflow.com/questions/2390827/how-to-properly-subclass-dict

    def __init__(self, *args, **kwargs):
        self.update(*args, **kwargs)

    # Note: I'm using dict directly, since super(dict, self) doesn't work.
    # I'm not sure why, perhaps dict is not a new-style class.

    def __getitem__(self, key):
        return dict.__getitem__(self, self.__keytransform__(key))

    def __setitem__(self, key, value):
        return dict.__setitem__(self, self.__keytransform__(key), value)

    def __delitem__(self, key):
        return dict.__delitem__(self, self.__keytransform__(key))

    def __contains__(self, key):
        return dict.__contains__(self, self.__keytransform__(key))


class lcdict(arbitrary_dict):
    def __keytransform__(self, key):
        return str(key).lower()
194
Paul Biggar

collections モジュールから ABC s(Abstract Base Classes)を使用すると、辞書のように動作するオブジェクトを簡単に作成できます。メソッドを見逃したかどうかもわかりますので、以下はABCをシャットダウンする最小バージョンです。

import collections


class TransformedDict(collections.MutableMapping):
    """A dictionary that applies an arbitrary key-altering
       function before accessing the keys"""

    def __init__(self, *args, **kwargs):
        self.store = dict()
        self.update(dict(*args, **kwargs))  # use the free update to set keys

    def __getitem__(self, key):
        return self.store[self.__keytransform__(key)]

    def __setitem__(self, key, value):
        self.store[self.__keytransform__(key)] = value

    def __delitem__(self, key):
        del self.store[self.__keytransform__(key)]

    def __iter__(self):
        return iter(self.store)

    def __len__(self):
        return len(self.store)

    def __keytransform__(self, key):
        return key

ABCからいくつかの無料のメソッドを取得します。

class MyTransformedDict(TransformedDict):

    def __keytransform__(self, key):
        return key.lower()


s = MyTransformedDict([('Test', 'test')])

assert s.get('TEST') is s['test']   # free get
assert 'TeSt' in s                  # free __contains__
                                    # free setdefault, __eq__, and so on

import pickle
assert pickle.loads(pickle.dumps(s)) == s
                                    # works too since we just use a normal dict

dict(または他のビルトイン)を直接サブクラス化しません。実際にやりたいことはdictのインターフェースを実装するであるため、それはしばしば意味をなさない。そして、それがまさにABCの目的です。

201
Jochen Ritzel

可能な限り辞書のサブクラスを「完璧」にするにはどうすればよいですか?

最終目標は、キーが小文字である単純な辞書を持つことです。

  • __getitem__/__setitem__をオーバーライドすると、get/setが機能しません。それらを機能させるにはどうすればよいですか?確かにそれらを個別に実装する必要はありませんか?

  • ピクルスが機能しないのを防いでいますか?__setstate__などを実装する必要がありますか?

  • Repr、update、__init__が必要ですか?

  • mutablemappingのみを使用する必要があります(UserDictまたはDictMixinを使用しないでください)。もしそうなら、どのように?ドキュメントは正確には啓発的ではありません。

受け入れられた答えは私の最初のアプローチですが、いくつかの問題があり、代替案に誰も対処していないため、実際にdictをサブクラス化するので、ここでそれを行います。

受け入れられた答えの何が問題になっていますか?

これは私にとってはかなり単純なリクエストのようです。

可能な限り辞書のサブクラスを「完璧」にするにはどうすればよいですか?最終目標は、キーが小文字である単純な辞書を持つことです。

受け入れられた答えは、実際にはdictをサブクラス化せず、このテストは失敗します:

>>> isinstance(MyTransformedDict([('Test', 'test')]), dict)
False

理想的には、型チェックコードは、期待するインターフェイスまたは抽象基本クラスをテストしますが、データオブジェクトがdictをテストする関数に渡される場合、これらの関数を「修正」することはできません。コードは失敗します。

作成できる他の小技:

  • 受け入れられた答えにもクラスメソッドがありません:fromkeys
  • 受け入れられた答えには、冗長な__dict__もあります。したがって、メモリのスペースをより多く使用します。

    >>> s.foo = 'bar'
    >>> s.__dict__
    {'foo': 'bar', 'store': {'test': 'test'}}
    

実際に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__())

キーを参照する任意のメソッドまたは特別なメソッドにはほぼ定型的なアプローチを使用しますが、継承によって、メソッドlenclearitemskeyspopitem、および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__などを実装する必要がありますか?

pickling

そして、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のみを使用する必要があります(UserDictまたはDictMixinを使用しないでください)。もしそうなら、どのように?ドキュメントは正確には啓発的ではありません。

ええ、これらはさらに数行のコードですが、包括的であることを意図しています。私の最初の傾向は、受け入れられた答えを使用することです。それに問題がある場合は、答えを見ていきます-それはもう少し複雑であり、インターフェイスを正しくするのに役立つABCはありません。

早すぎる最適化により、パフォーマンスの検索がより複雑になります。 MutableMappingの方が簡単です。したがって、それはすぐにEdgeを取得します。それにもかかわらず、すべての違いをレイアウトするために、比較して対比しましょう。

同様の辞書をcollectionsモジュールに入れるプッシュがありましたが、 拒否されました を追加する必要があります。おそらく代わりにこれを行う必要があります:

my_dict[transform(key)]

はるかに簡単にデバッグできるはずです。

比較対照

MutableMappingfromkeysが欠落している)で実装された6つのインターフェース関数と、dictサブクラスで実装された11のインターフェース関数があります。 __iter__または__len__を実装する必要はありませんが、代わりにgetsetdefaultpopupdatecopy__contains__、およびfromkeysを実装する必要がありますが、それらの実装のほとんどに継承を使用できるからです。

MutableMappingは、Cでdictが実装するPythonのいくつかのことを実装します。したがって、場合によっては、dictサブクラスのパフォーマンスが向上することが期待されます。

両方のアプローチで無料の__eq__を取得します-両方とも別のdictがすべて小文字の場合にのみ同等であると仮定しますが、dictサブクラスはより迅速に比較すると思います。

概要:

  • MutableMappingのサブクラス化はより簡単で、バグの機会は少なくなりますが、遅くなり、より多くのメモリを使用し(冗長なdictを参照)、失敗しますisinstance(x, dict)
  • dictのサブクラス化はより高速で、使用メモリが少なく、isinstance(x, dict)を渡しますが、実装するのはより複雑です。

どちらがより完璧ですか?それは完璧の定義に依存します。

83
Aaron Hall

私の要件は少し厳しかった:

  • 大文字と小文字の情報を保持する必要がありました(文字列はユーザ​​ーに表示されるファイルへのパスですが、Windowsアプリなので、内部的にはすべての操作で大文字と小文字を区別しない必要があります)
  • キーをできる限り小さくする必要がありました((did370のうち110 mbを切り捨てて、メモリパフォーマンスに差をつけました)。つまり、キーの小文字バージョンをキャッシュすることはオプションではありません。
  • データ構造をできるだけ速く作成する必要がありました(今回もパフォーマンスと速度に違いが生じました)。私はビルトインで行かなければなりませんでした

私の最初の考えは、大文字と小文字を区別しないユニコードサブクラスを不格好なPathクラスに置き換えることでした-しかし:

  • それを正しくするのは難しいことがわかった-参照: Pythonの大文字小文字を区別しない文字列クラス
  • 明示的なdictキーの処理により、コードが冗長で煩雑になり、エラーが発生しやすくなります(構造体はあちこちに渡され、キー/要素としてCIStrインスタンスがあるかどうかは不明であり、忘れがちですsome_dict[CIstr(path)]はisいです)

だから私は最終的にその大文字小文字を区別しない辞書を書き留めなければなりませんでした。 @ -AaronHallによる code のおかげで、10倍簡単になりました。

class CIstr(unicode):
    """See https://stackoverflow.com/a/43122305/281545, especially for inlines"""
    __slots__ = () # does make a difference in memory performance

    #--Hash/Compare
    def __hash__(self):
        return hash(self.lower())
    def __eq__(self, other):
        if isinstance(other, CIstr):
            return self.lower() == other.lower()
        return NotImplemented
    def __ne__(self, other):
        if isinstance(other, CIstr):
            return self.lower() != other.lower()
        return NotImplemented
    def __lt__(self, other):
        if isinstance(other, CIstr):
            return self.lower() < other.lower()
        return NotImplemented
    def __ge__(self, other):
        if isinstance(other, CIstr):
            return self.lower() >= other.lower()
        return NotImplemented
    def __gt__(self, other):
        if isinstance(other, CIstr):
            return self.lower() > other.lower()
        return NotImplemented
    def __le__(self, other):
        if isinstance(other, CIstr):
            return self.lower() <= other.lower()
        return NotImplemented
    #--repr
    def __repr__(self):
        return '{0}({1})'.format(type(self).__name__,
                                 super(CIstr, self).__repr__())

def _ci_str(maybe_str):
    """dict keys can be any hashable object - only call CIstr if str"""
    return CIstr(maybe_str) if isinstance(maybe_str, basestring) else maybe_str

class LowerDict(dict):
    """Dictionary that transforms its keys to CIstr instances.
    Adapted from: https://stackoverflow.com/a/39375731/281545
    """
    __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, 'iteritems'):
            mapping = getattr(mapping, 'iteritems')()
        return ((_ci_str(k), v) for k, v in
                chain(mapping, getattr(kwargs, 'iteritems')()))
    def __init__(self, mapping=(), **kwargs):
        # dicts take a mapping or iterable as their optional first argument
        super(LowerDict, self).__init__(self._process_args(mapping, **kwargs))
    def __getitem__(self, k):
        return super(LowerDict, self).__getitem__(_ci_str(k))
    def __setitem__(self, k, v):
        return super(LowerDict, self).__setitem__(_ci_str(k), v)
    def __delitem__(self, k):
        return super(LowerDict, self).__delitem__(_ci_str(k))
    def copy(self): # don't delegate w/ super - dict.copy() -> dict :(
        return type(self)(self)
    def get(self, k, default=None):
        return super(LowerDict, self).get(_ci_str(k), default)
    def setdefault(self, k, default=None):
        return super(LowerDict, self).setdefault(_ci_str(k), default)
    __no_default = object()
    def pop(self, k, v=__no_default):
        if v is LowerDict.__no_default:
            # super will raise KeyError if no default and key does not exist
            return super(LowerDict, self).pop(_ci_str(k))
        return super(LowerDict, self).pop(_ci_str(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__(_ci_str(k))
    @classmethod
    def fromkeys(cls, keys, v=None):
        return super(LowerDict, cls).fromkeys((_ci_str(k) for k in keys), v)
    def __repr__(self):
        return '{0}({1})'.format(type(self).__name__,
                                 super(LowerDict, self).__repr__())

暗黙的vs明示的は依然として問題ですが、塵が落ち着いたら、ciで始まる属性/変数の名前を変更します(ciは大文字と小文字を区別しないことを説明する太いドキュメントコメント)私は完璧な解決策だと思います-コードの読者は大文字と小文字を区別しない基礎となるデータ構造を扱っていることを十分に認識してください。これにより、バグを再現するのが難しいバグが修正されることを期待しています。

コメント/修正歓迎:)

4
Mr_and_Mrs_D

あなたがしなければならないのは

class BatchCollection(dict):
    def __init__(self, *args, **kwargs):
        dict.__init__(*args, **kwargs)

OR

class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

私個人の使用例

### EXAMPLE
class BatchCollection(dict):
    def __init__(self, inpt={}):
        dict.__init__(*args, **kwargs)

    def __setitem__(self, key, item):
        if (isinstance(key, Tuple) and len(key) == 2
                and isinstance(item, collections.Iterable)):
            # self.__dict__[key] = item
            super(BatchCollection, self).__setitem__(key, item)
        else:
            raise Exception(
                "Valid key should be a Tuple (database_name, table_name) "
                "and value should be iterable")

:python3でのみテスト済み

3
ravi404

toptwo の両方の提案を試した後、私はPython 2.7の陰に見える中間ルートに落ち着きました。たぶん3は賢明ですが、私にとっては:

class MyDict(MutableMapping):
   # ... the few __methods__ that mutablemapping requires
   # and then this monstrosity
   @classmethod
   def __class__(cls):
       return dict

私は本当に嫌いですが、私のニーズに合っているようです。

  • **my_dict をオーバーライドできます
    • dictから継承する場合、これはコードをバイパスします。やってみて。
    • これは、 #2 を常に受け​​入れられないようにします常に、これはpythonコードでは非常に一般的です
  • isinstance(my_dict, dict) としてマスカレード
    • mutableMappingのみを除外するため、 #1 では不十分です
    • #1 これを必要としないなら、それはシンプルで予測可能です
  • 完全に制御可能な動作
    • dictから継承できません

他の人と区別する必要がある場合は、個人的に次のようなものを使用します(より良い名前をお勧めします)

def __am_i_me(self):
  return True

@classmethod
def __is_it_me(cls, other):
  try:
    return other.__am_i_me()
  except Exception:
    return False

内部的に自分自身を認識する必要がある限り、この方法では、Pythonの名前変更により、誤って__am_i_meを呼び出すのが難しくなります(このクラスの外部から呼び出すものから_MyDict__am_i_meに名前が変更されます)。 _methodsより少し実践的であり、実際にも文化的にも。

これまでのところ、深刻な日陰に見える__class__オーバーライドを除き、苦情はありません。他の人がこれに遭遇する問題を聞いて私はワクワクします、しかしその結果を完全には理解していません。しかし、これまでのところ私はまったく問題がなかったため、変更を必要とせずに多くの場所で多くの中品質のコードを移行することができました。


証拠として: https://repl.it/repls/TraumaticToughCockatoo

基本的に: 現在の#2オプション をコピーし、すべてのメソッドにprint 'method_name'行を追加してから、これを試して出力を監視します。

d = LowerDict()  # prints "init", or whatever your print statement said
print '------'
splatted = dict(**d)  # note that there are no prints here

他のシナリオでも同様の動作が見られます。偽のdictは他のデータ型のラッパーであるため、backing-dictにデータを格納する合理的な方法はありません。 **your_dictは、他のすべてのメソッドが何をするかに関係なく空になります。

これはMutableMappingに対して正常に機能しますが、dictから継承するとすぐに制御不能になります。

2
Groxx