【问题标题】:Keyed Collection in Python?Python中的键控集合?
【发布时间】:2011-10-10 11:13:18
【问题描述】:

在 Python 中是否有任何等效于 KeyedCollection 的方法,即元素具有(或动态生成)自己的键的集合?

即这里的目标是避免将密钥存储在两个地方,因此字典不太理想(因此提出了问题)。

【问题讨论】:

  • 您能否详细说明为什么您不想拥有键和值? Python 通常只会引用同一个对象作为键和值,因此不会产生两倍的内存。
  • @Ned:因为从语义上讲,它没有多大意义。当一个对象知道它的键时,把它放在字典中是没有意义的——它不是键值对。这更像是一个语义问题而不是其他任何事情。 (还有一个问题是,KeyedCollection 也可以被序数索引,但我在这里并不需要它。不过,它在其他情况下很有用。)

标签: python keyedcollection


【解决方案1】:

要更详细地了解@Gabi Purcaru 的答案中已经正确的答案,这里有一个与 gabi one's 一样的类,但它也检查键和值的正确给定类型(作为 TKey 和 TValue .net KeyedCollection)。

class KeyedCollection(MutableMapping):
    """
    Provides the abstract base class for a collection (:class:`MutableMappinp`) whose keys are embedded in the values.
    """
    __metaclass__ = abc.ABCMeta
    _dict = None  # type: dict

    def __init__(self, seq={}):
        self._dict = dict(seq)

    @abc.abstractmethod
    def __is_type_key_correct__(self, key):
        """
        Returns: The type of keys in the collection
        """
        pass

    @abc.abstractmethod
    def __is_type_value_correct__(self, value):
        """
        Returns: The type of values in the collection
        """
        pass

    @abc.abstractmethod
    def get_key_for_item(self, value):
        """
        When implemented in a derivated class, extracts the key from the specified element.
        Args:
            value: the element from which to extract the key (of type specified by :meth:`type_value`)

        Returns: The key of specified element (of type specified by :meth:`type_key`)
        """
        pass

    def __assert_type_key(self, key, arg_name='key'):
        if not self.__is_type_key_correct__(key) :
            raise ValueError("{} type is not correct".format(arg_name))

    def __assert_type_value(self, value, arg_name='value'):
        if not self.__is_type_value_correct__(value) :
            raise ValueError("{} type is not correct".format(arg_name))

    def add(self, value):
        """
        Adds an object to the KeyedCollection.
        Args:
            value: The object to be added to the KeyedCollection (of type specified by :meth:`type_value`).
        """
        key = self.get_key_for_item(value)
        self._dict[key] = value

    # Implements abstract method __setitem__ from MutableMapping parent class
    def __setitem__(self, key, value):
        self.__assert_type_key(key)
        self.__assert_type_value(value)
        if value.get_key() != key:
            raise ValueError("provided key does not correspond to the given KeyedObject value")
        self._dict[key] = value

    # Implements abstract method __delitem__ from MutableMapping parent class
    def __delitem__(self, key):
        self.__assert_type_key(key)
        self._dict.pop(key)

    # Implements abstract method __getitem__ from MutableMapping parent class (Mapping base class)
    def __getitem__(self, key):
        self.__assert_type_key(key)
        return self._dict[key]

    # Implements abstract method __len__ from MutableMapping parent class (Sized mixin on Mapping base class)
    def __len__(self):
        return len(self._dict)

    # Implements abstract method __iter__ from MutableMapping parent class (Iterable mixin on Mapping base class)
    def __iter__(self):
        return iter(self._dict)
        pass

    # Implements abstract method __contains__ from MutableMapping parent class (Container mixin on Mapping base class)
    def __contains__(self, x):
        self.__assert_type_key(x, 'x')
        return x in self._dict

【讨论】:

    【解决方案2】:

    set() 怎么样?元素可以有自己的k

    【讨论】:

      【解决方案3】:

      考虑到您的限制,每个尝试使用dict 来实现您正在寻找的东西的人都在寻找错误的树。相反,您应该编写一个覆盖__getitem__list 子类以提供您想要的行为。我已经编写了它,因此它首先尝试通过索引获取所需的项目,然后返回到通过包含对象的 key 属性搜索项目。 (如果对象需要动态确定,这可能是一个属性。)

      如果您不想在某处复制某些内容,则无法避免线性搜索;如果您不允许 C# 实现使用字典来存储键,我确信 C# 实现会做同样的事情。

      class KeyedCollection(list):
           def __getitem__(self, key):
               if isinstance(key, int) or isinstance(key, slice):
                   return list.__getitem__(key)
               for item in self:
                   if getattr(item, "key", 0) == key:
                       return item
               raise KeyError('item with key `%s` not found' % key)
      

      您可能还想以类似的方式覆盖__contains__,这样您就可以说if "key" in kc...。如果你想让它更像dict,你也可以实现keys()等等。它们同样效率低下,但您将拥有像 dict 这样的 API,它也像列表一样工作。

      【讨论】:

      • 感谢您的信息!虽然我可以手动实现它,但我想知道是否有内置的东西,这样我就不必在我需要的每个项目中定义/包含该类。但无论如何,这绝对是一个解决方案; +1 寻求帮助。 :)
      【解决方案4】:

      @Mehrdad 说:

      因为在语义上,它没有多大意义。当一个物体 知道它的键,把它放在字典里是没有意义的——它是 不是键值对。这更像是一个语义问题而不是任何事情 否则。

      有了这个约束,Python 中就没有什么可以做你想做的事了。我建议您使用 dict 而不必担心语义上的这种详细程度。 @Gabi Purcaru 的回答显示了如何使用所需的界面创建对象。为什么要为它在内部的工作方式而烦恼?

      可能是 C# 的 KeyedCollection 在幕后做同样的事情:向对象询问其密钥,然后存储该密钥以便快速访问。事实上,来自文档:

      默认情况下,KeyedCollection(Of TKey, TItem) 包含一个查找 可以使用 Dictionary 属性获取的字典。当一个 item 被添加到 KeyedCollection(Of TKey, TItem),item 的 key 提取一次并保存在查找字典中以便更快 搜索。通过指定字典覆盖此行为 创建 KeyedCollection(Of TKey, 项)。第一次创建查找字典的次数 元素超过该阈值。如果您指定 –1 作为阈值, 永远不会创建查找字典。

      【讨论】:

      • 可以,但是 (1) 它可以被禁用,并且 (2) 它仍然允许按序号访问。
      • @Mehrdad:下定决心。你说你不关心序数访问。
      • 我说不关心 this 的情况,但我确实关心它(即大多数其他时候我需要 KeyedCollection)。但也要注意第 (1) 点。
      • 好的,我们正在构建 KeyedCollection 的 Python 实现,我们也可以添加索引查找,但请记住:这将需要存储对该值的另一​​个引用。
      【解决方案5】:

      我不确定这是否是你的意思,但是当你添加到它时,这个字典会创建它自己的键......

      class KeyedCollection(dict):
          def __init__(self):
              self.current_key = 0
          def add(self, item):
              self[self.current_key] = item
      
      abc = KeyedCollection()
      abc.add('bob')
      abc.add('jane')
      >>> abc
      {0: 'bob', 1: 'jane'}
      

      【讨论】:

      • 我试图避免将密钥存储两次,但这不会。 :\
      【解决方案6】:

      为什么不直接使用dict?如果 key 已经存在,则在 dict 中将使用对 key 的引用;它不会被无意义地复制。

      class MyExample(object):
          def __init__(self, key, value):
              self.key = key
              self.value = value
      
      m = MyExample("foo", "bar")
      d = {}
      
      d[m.key] = m
      
      first_key = d.keys()[0]
      first_key is m.key  # returns True
      

      如果密钥尚不存在,则会保存其副本,但我不认为这是一个问题。

      def lame_hash(s):
          h = 0
          for ch in s:
              h ^= ord(ch)
          return h
      
      d = {}
      d[lame_hash(m.key)] = m
      print d  # key value is 102 which is stored in the dict
      
      lame_hash(m.key) in d  # returns True
      

      【讨论】:

        【解决方案7】:

        你可以很容易地模拟:

        class KeyedObject(object):
            def get_key(self):
                raise NotImplementedError("You must subclass this before you can use it.")
        
        class KeyedDict(dict):
            def append(self, obj):
                self[obj.get_key()] = obj
        

        现在您可以使用 KeyedDict 而不是 dictKeyedObject 的子类(其中 get_key 根据某些对象属性返回有效键)。

        【讨论】:

        • @Mehrdad:这不会将密钥存储两次,它会存储两个对密钥的引用。如果你有一个大字符串作为key,那么这个字符串在内存中只存在一次。
        • @Ned:是的,我知道它存储了一个引用,但它存储了两次引用。我不仅不喜欢重复,更重要的是,请参阅我对您其他评论的评论。
        • @Mehrdad:内存通常很充足(直到没有)。您的用户更喜欢哪一个:一个运行速度快的程序,因为它使用适合手头任务的数据结构,还是一个运行速度较慢但内部数据结构没有不必要重复的程序?
        • @kindall:这不是内存问题——而是可读性和语义问题。如果我必须存储一些东西两次,那么我必须跟踪它两次,这变得更加难以阅读。在这里担心内存有点过头了。这不是问题。
        【解决方案8】:
        猜你喜欢
        • 1970-01-01
        • 2011-07-29
        • 1970-01-01
        • 1970-01-01
        • 2013-06-28
        • 1970-01-01
        • 1970-01-01
        • 2017-03-31
        • 1970-01-01
        相关资源
        最近更新 更多