¿Hay algún equivalente a KeyedCollection en Python, es decir, un conjunto donde los elementos tiene (o genera dinámicamente) sus propias claves?

Es decir, el objetivo aquí es evitar almacenar la clave en dos lugares y, por lo tanto, los diccionarios no son ideales (de ahí la pregunta).

6
user541686 21 jul. 2011 a las 22:02

8 respuestas

La mejor respuesta

@Mehrdad dijo:

Porque semánticamente, no tiene tanto sentido. Cuando un objeto conoce su clave, no tiene sentido ponerlo en un diccionario, no es un par clave-valor. Es más un problema semántico que otra cosa.

Con esta restricción, no hay nada en Python que haga lo que quieres. Le sugiero que use un dict y no se preocupe por este nivel de detalle en la semántica. La respuesta de @Gabi Purcaru muestra cómo puedes crear un objeto con la interfaz que deseas. ¿Por qué preocuparse de cómo funciona internamente?

Podría ser que KeyedCollection de C # esté haciendo lo mismo debajo de las cubiertas: pidiendo al objeto su clave y luego almacenando la clave para un acceso rápido. De hecho, de los documentos:

De forma predeterminada, KeyedCollection (Of TKey, TItem) incluye un diccionario de búsqueda que puede obtener con la propiedad Dictionary. Cuando se agrega un elemento a KeyedCollection (Of TKey, TItem), la clave del elemento se extrae una vez y se guarda en el diccionario de búsqueda para búsquedas más rápidas. Este comportamiento se anula al especificar un umbral de creación de diccionario al crear KeyedCollection (Of TKey, TItem). El diccionario de búsqueda se crea la primera vez que el número de elementos supera ese umbral. Si especifica –1 como umbral, el diccionario de búsqueda nunca se crea.

1
Ned Batchelder 21 jul. 2011 a las 23:19

No estoy seguro de si esto es lo que querías decir, pero este diccionario creará sus propias claves a medida que agregues ...

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'}
0
sampwing 21 jul. 2011 a las 21:35

Dadas sus limitaciones, todos los que intentan implementar lo que está buscando con un dict están ladrando por el árbol equivocado. En su lugar, debe escribir una subclase list que anule __getitem__ para proporcionar el comportamiento que desea. Lo escribí para que primero intente obtener el elemento deseado por índice, luego recurre a la búsqueda del elemento por el atributo key de los objetos contenidos. (Esto podría ser una propiedad si el objeto necesita determinar esto dinámicamente).

No hay forma de evitar una búsqueda lineal si no desea duplicar algo en alguna parte; Estoy seguro de que la implementación de C # hace exactamente lo mismo si no permite que use un diccionario para almacenar las claves.

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)

Probablemente también desee anular __contains__ de manera similar para poder decir if "key" in kc.... Si desea hacerlo aún más como un dict, también puede implementar keys() y así sucesivamente. Serán igualmente ineficientes, pero tendrá una API como dict, que también funciona como una lista.

2
kindall 22 jul. 2011 a las 00:18

Para ir un poco más en detalle que la respuesta ya correcta de la respuesta de @Gabi Purcaru, aquí hay una clase que hace lo mismo que la de gabi pero que también verifica el tipo correcto de clave y valor (como TKey y TValue de .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
0
Fabien Letort 14 dic. 2017 a las 16:37

No soy un C # 'er, pero creo que los diccionarios son lo que necesita.

http://docs.python.org/tutorial/datastructures.html#dictionaries

http://docs.python.org/tutorial/datastructures.html

O tal vez listas:

http://docs.python.org/library/functions.html#list

1
Paul 21 jul. 2011 a las 18:13

Puedes simular eso muy fácilmente:

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

Ahora puede usar un KeyedDict en lugar de dict con subclases de KeyedObject (donde get_key devuelve una clave válida basada en alguna propiedad del objeto).

3
Gabi Purcaru 21 jul. 2011 a las 18:23

¿Por qué no simplemente usar un dict? Si la clave ya existe, se usará una referencia a la clave en el dict; No se duplicará sin sentido.

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

Si la clave aún no existe, se guardará una copia, pero no lo veo como un problema.

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
0
steveha 21 jul. 2011 a las 18:43

¿Qué tal un set()? Los elementos pueden tener su propia k

0
Zzz 22 jul. 2011 a las 03:56