Python 如何";完美地;推翻命令?

Python 如何";完美地;推翻命令?,python,inheritance,dictionary,get,set,Python,Inheritance,Dictionary,Get,Set,我怎样才能使dict的子类尽可能的“完美”?最终目标是有一个简单的dict,其中键是小写的 似乎应该有一组很小的原语我可以覆盖以使其工作,但根据我所有的研究和尝试,似乎情况并非如此: 如果是我,那么get/set不起作用。我怎样才能让它们工作?我当然不需要单独实现它们 我是否阻止酸洗工作,是否需要实施\uuuu setstate\uuuu等 是吗 我应该(似乎不应该使用UserDict 或DictMixin)?如果是,怎么做?这些文件并不是很有启发性 这是我的第一次尝试,get()不起作用,

我怎样才能使dict的子类尽可能的“完美”?最终目标是有一个简单的dict,其中键是小写的

似乎应该有一组很小的原语我可以覆盖以使其工作,但根据我所有的研究和尝试,似乎情况并非如此:

  • 如果是我,那么
    get
    /
    set
    不起作用。我怎样才能让它们工作?我当然不需要单独实现它们

  • 我是否阻止酸洗工作,是否需要实施
    \uuuu setstate\uuuu

  • 是吗

  • 我应该(似乎不应该使用
    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()

使用模块中的抽象基类,您可以很容易地编写一个行为类似于dict的对象。它甚至会告诉你是否错过了一个方法,所以下面是关闭ABC的最低版本

from collections.abc import MutableMapping


class TransformedDict(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
# works too since we just use a normal dict
assert pickle.loads(pickle.dumps(s)) == s
我不会直接将
dict
(或其他内置项)子类化。这通常是没有意义的,因为您真正想要做的是实现
dict
的接口。这正是ABC的目的

我怎样才能使dict的子类尽可能的“完美”? 最终目标是有一个简单的dict,其中键是小写的

  • 如果我覆盖
    \uuuuGetItem\uuuuuuuuuuuuuuuu
    /
    \uuuuuuuuuSetItem\uuuuuuuuuuuuuuuuuuuuuuuuuu
    ,那么get/set不起作用。怎么 我能让它们工作吗?我当然不需要实施它们 单独的

  • 我是否阻止酸洗工作,是否需要实施
    \uuuu设置状态\uuuuu

  • 我需要报告、更新和
    初始化吗

  • 我应该只使用
    mutablemapping
    (似乎不应该使用
    UserDict
    DictMixin
    )?如果是,怎么做?这些文件并不是很有启发性

被接受的答案将是我的第一个方法,但由于它有一些问题, 既然还没有人提出替代方案,实际上是将一个
dict
子类化,我将在这里这样做

被接受的答案有什么问题? 对我来说,这似乎是一个相当简单的要求:

我怎样才能使dict的子类尽可能的“完美”? 最终目标是有一个简单的dict,其中键是小写的

被接受的答案实际上不是dict的子类,对此的测试失败:

>>> isinstance(MyTransformedDict([('Test', 'test')]), dict)
False
理想情况下,任何类型检查代码都将针对我们期望的接口或抽象基类进行测试,但是如果我们的数据对象被传递到测试
dict
的函数中,并且我们无法“修复”这些函数,那么这段代码将失败

人们可能会提出的其他质疑:

  • 接受的答案也缺少classmethod:
    fromkeys
  • 接受的答案还有一个冗余的
    \uuuuu dict\uuuuu
    ——因此占用了更多的内存空间:

    >>> s.foo = 'bar'
    >>> s.__dict__
    {'foo': 'bar', 'store': {'test': 'test'}}
    
实际上,子类化
dict
我们可以通过继承重用dict方法。我们需要做的就是创建一个接口层,确保键以小写形式传递到dict中(如果它们是字符串)

如果我覆盖
\uuuuGetItem\uuuuuuuuuuuuuuuu
/
\uuuuuuuuuSetItem\uuuuuuuuuuuuuuuuuuuuuuuuuu
,那么get/set不起作用。我如何让它们工作?我当然不需要单独实现它们

好的,分别实现它们是这种方法的缺点,使用
可变映射(参见公认的答案)的优点,但实际上没有那么多工作要做

首先,让我们考虑一下Python 2和Python 3之间的差异,创建一个单例(
\u 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
value
。虽然这需要一些仔细的思考才能正确,但看到这一点是微不足道的

(注意,
haskey
在Python2中被弃用,在Python3中被删除。)

以下是一些用法:

>>> 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)
我是否阻止酸洗工作,是否需要实施
\uuuu设置状态\uuuuu

酸洗 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'>
但是,编写一个
\uu repr\uu
来提高代码的可调试性是很好的。理想的测试是
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
更简单,因此在其他条件相同的情况下,它可以获得一个直接的边缘。然而,为了展示所有的差异,让我们进行比较和对比

>>> ld
LowerDict({'a': 1, 'c': 3, 'b': 2})
my_dict[transform(key)]
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__())
class BatchCollection(dict):
    def __init__(self, *args, **kwargs):
        dict.__init__(*args, **kwargs)
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")
class MyDict(MutableMapping):
   # ... the few __methods__ that mutablemapping requires
   # and then this monstrosity
   @property
   def __class__(self):
       return 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
d = LowerDict()  # prints "init", or whatever your print statement said
print '------'
splatted = dict(**d)  # note that there are no prints here