代码之家  ›  专栏  ›  技术社区  ›  Paul Biggar

如何“完美地”重写听写?

  •  183
  • Paul Biggar  · 技术社区  · 15 年前

    我怎样才能使“完美”成为 双关语 尽可能?最终目标是 双关语 其中键是小写的。

    似乎应该有一些我可以覆盖的小的原语集来完成这个工作,但是根据我所有的研究和尝试,情况似乎不是这样的:

    这是我第一次尝试, 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()
    
    5 回复  |  直到 7 年前
        1
  •  191
  •   Alois Mahdal    12 年前

    您可以编写一个对象,该对象的行为类似于dict,使用 ABC S (抽象基类) collections 模块。它甚至告诉你 如果您错过了一个方法,那么下面是关闭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的宗旨。

        2
  •  75
  •   Aaron Hall    8 年前

    我怎样才能使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) 但实施起来更为复杂。

    哪个更完美?这取决于你对完美的定义。

        3
  •  3
  •   Community Mohan Dere    8 年前

    我的要求有点严格:

    • 我必须保留大小写信息(字符串是显示给用户的文件的路径,但它是一个Windows应用程序,因此在内部所有操作都必须不区分大小写)
    • 我需要钥匙尽可能小 改变内存性能,从370中切掉110 MB)。这意味着缓存小写版本的密钥不是一个选项。
    • 我需要尽可能快地创建数据结构(这次再次改变了性能和速度)。我得用一个内置的

    我最初的想法是用笨拙的path类替换不区分大小写的unicode子类,但是:

    • 事实证明很难做到这一点——看: A case insensitive string class in python
    • 结果表明,显式的dict键处理使代码变得冗长、混乱和容易出错(结构在这里和那里传递,不清楚它们是否将citr实例作为键/元素,易于忘记加上 some_dict[CIstr(path)] 丑陋)

    所以我终于写下了那个不区分大小写的口述。 code 通过@aaronhall,这是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__())
    

    隐式和显式仍然是一个问题,但一旦尘埃落定,重新命名属性/变量以开始CI(和一个大的胖文档注释解释CI代表不区分大小写)我认为是一个完美的解决方案-因为代码的读者必须充分意识到我们正在处理不区分大小写的底层数据结构。 这将有望修复一些难以复制的错误,我怀疑归结为大小写敏感度。

    欢迎评论/更正:)

        4
  •  2
  •   ravi404 Reza    8 年前

    你要做的就是

    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")
    

    注释 :仅在python3中测试

        5
  •  1
  •   Groxx    8 年前

    在尝试了两个 top two 建议,我已经为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)
      • 排除了可变映射,所以 #1 还不够
      • 我衷心推荐 α1 如果你不需要这个,它是简单的和可预测的
    • 完全可控行为
      • 所以我不能继承 双关语

    如果你需要把自己和别人区分开来,我个人会用这样的名字(尽管我会推荐更好的名字):

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

    只要你只需要在内部识别自己,这样很难不小心打电话给你。 __am_i_me 由于python的名字munging(这被重命名为 _MyDict__am_i_me 从这个班以外的任何呼叫)。比 _method 在实践和文化上。

    到目前为止,我没有任何抱怨,除了严重的阴暗面 __class__ 重写。我会 激动的 不过,要知道其他人遇到的任何问题,我并不完全理解其后果。但是到目前为止,我还没有遇到任何问题,这使得我可以在很多地方迁移大量中等质量的代码,而不需要做任何更改。


    作为证据: https://repl.it/repls/TraumaticToughCockatoo

    基本:复制 the current #2 option ,添加 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中; **your_dict 将为空,不管其他方法做什么。

    这对 MutableMapping 但是一旦你继承了 双关语 它变得无法控制。