Python hashable diktleri


94

Bir alıştırma olarak ve çoğunlukla kendi eğlencem için, geriye dönük bir paket ayrıştırıcı uyguluyorum. Bunun ilham kaynağı, hijyenik makroların algol benzeri bir dilde (normalde bulduğunuz sözdizimsiz lisp lehçelerine uygulandığı gibi) nasıl çalışacağı hakkında daha iyi bir fikre sahip olmak istiyorum. Bu nedenle, girişteki farklı geçişler farklı gramerler görebilir, bu nedenle önbelleğe alınmış ayrıştırma sonuçları, dilbilgisinin geçerli sürümünü önbelleğe alınmış ayrıştırma sonuçlarıyla birlikte saklamadığım sürece geçersizdir. ( DÜZENLEME : Anahtar-değer koleksiyonlarının bu kullanımının bir sonucu, bunların değişmez olmaları gerektiğidir, ancak değiştirilmelerine izin vermek için arabirimi ortaya çıkarmayı düşünmüyorum, bu nedenle değiştirilebilir veya değişmez koleksiyonlar iyidir)

Sorun şu ki, python dicts diğer diktlere anahtar olarak görünemez. Bir demet kullanmak bile (zaten yapacağım gibi) yardımcı olmuyor.

>>> cache = {}
>>> rule = {"foo":"bar"}
>>> cache[(rule, "baz")] = "quux"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>> 

Sanırım baştan sona tuple olmalı. Şimdi piton standart kütüphane ihtiyacım yapardım yaklaşık sağlar, collections.namedtupleçok farklı bir sözdizimi vardır, ama olabilir bir anahtar olarak kullanılabilir. yukarıdaki oturumdan devam ediyor:

>>> from collections import namedtuple
>>> Rule = namedtuple("Rule",rule.keys())
>>> cache[(Rule(**rule), "baz")] = "quux"
>>> cache
{(Rule(foo='bar'), 'baz'): 'quux'}

Tamam. Ancak, kullanmak istediğim kuraldaki her olası anahtar kombinasyonu için bir sınıf oluşturmam gerekiyor, bu o kadar da kötü değil çünkü her ayrıştırma kuralı tam olarak hangi parametreleri kullandığını biliyor, böylece sınıf aynı anda tanımlanabilir. kuralı ayrıştıran işlev olarak.

Düzenleme: namedtuples ile ilgili ek bir sorun , kesinlikle konumsal olmalarıdır. Farklı olmaları gerektiği gibi görünen iki grup aslında aynı olabilir:

>>> you = namedtuple("foo",["bar","baz"])
>>> me = namedtuple("foo",["bar","quux"])
>>> you(bar=1,baz=2) == me(bar=1,quux=2)
True
>>> bob = namedtuple("foo",["baz","bar"])
>>> you(bar=1,baz=2) == bob(bar=1,baz=2)
False

tl'dr: Diğer e-postaların dictanahtarı olarak kullanılabilecek e- postaları nasıl edinebilirim dict?

Cevapları biraz hackledikten sonra, işte kullandığım daha eksiksiz çözüm. Bunun, sonuçta ortaya çıkan kararları pratik amaçlar için belirsiz bir şekilde değişmez hale getirmek için biraz fazladan çalışma yaptığını unutmayın. Elbette arayarak etrafından dolaşmak oldukça kolay dict.__setitem__(instance, key, value)ama burada hepimiz yetişkiniz.

class hashdict(dict):
    """
    hashable dict implementation, suitable for use as a key into
    other dicts.

        >>> h1 = hashdict({"apples": 1, "bananas":2})
        >>> h2 = hashdict({"bananas": 3, "mangoes": 5})
        >>> h1+h2
        hashdict(apples=1, bananas=3, mangoes=5)
        >>> d1 = {}
        >>> d1[h1] = "salad"
        >>> d1[h1]
        'salad'
        >>> d1[h2]
        Traceback (most recent call last):
        ...
        KeyError: hashdict(bananas=3, mangoes=5)

    based on answers from
       http://stackoverflow.com/questions/1151658/python-hashable-dicts

    """
    def __key(self):
        return tuple(sorted(self.items()))
    def __repr__(self):
        return "{0}({1})".format(self.__class__.__name__,
            ", ".join("{0}={1}".format(
                    str(i[0]),repr(i[1])) for i in self.__key()))

    def __hash__(self):
        return hash(self.__key())
    def __setitem__(self, key, value):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def __delitem__(self, key):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def clear(self):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def pop(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def popitem(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def setdefault(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    def update(self, *args, **kwargs):
        raise TypeError("{0} does not support item assignment"
                         .format(self.__class__.__name__))
    # update is not ok because it mutates the object
    # __add__ is ok because it creates a new object
    # while the new object is under construction, it's ok to mutate it
    def __add__(self, right):
        result = hashdict(self)
        dict.update(result, right)
        return result

if __name__ == "__main__":
    import doctest
    doctest.testmod()

En hashdictazından hashing oluşturmaya başladıktan sonra değişmez olmalıdır, öyleyse neden keyve hashdeğerlerini hashdictnesnenin öznitelikleri olarak önbelleğe almayasınız? Çok daha hızlı olduğunu doğrulamak için değiştirdim __key()ve __hash__()test ettim. SO yorumlarda biçimlendirilmiş koda izin vermez, bu yüzden buraya bağlayacağım
Sam Watkins

Yanıtlar:


71

İşte karma bir sözlük yapmanın kolay yolu. Açık nedenlerle başka bir sözlüğe yerleştirdikten sonra onları mutasyona uğratmamayı unutma.

class hashabledict(dict):
    def __hash__(self):
        return hash(tuple(sorted(self.items())))

7
Bu, daha önceki cevabım __key yönteminin kullanımıyla yapılırken , bu, eq ve hash tutarlılığını kesin bir şekilde sağlamaz (pratikte her iki yaklaşım da işe yaramalıdır, ancak bu, gereksiz bir yinelemeli liste oluşturarak yavaşlatılabilir - s / öğeler tarafından düzeltilebilir / iteritems / - Söylemediğiniz gibi Python 2. * varsayarsak ;-).
Alex Martelli

5
Sıralama ile bir demet yerine sadece bir frozenset kullanmak muhtemelen daha iyi olacaktır. Bu sadece daha hızlı olmakla kalmaz, aynı zamanda sözlük tuşlarının karşılaştırılabilir olduğunu varsayamazsınız.
asmeurer

1
Girişlerin sayısının O(n*log(n))olduğu bir hash işlevinden kaçınmanın bir yolu olmalı gibi görünüyor . Python'un hash fonksiyonunun doğrusal zamanda çalışıp çalışmadığını bilen var mı? ndictfrozenset
Tom Karzes

2
@HelloGoodbye bir dict da böyle oluşturulabilir dict(key1=value1, key2=value2,...)veya bu dict([(key1, value1), (key2, value2),...)]). Aynısı bunun için de geçerlidir.
Gönderdiğiniz eserin

2
@smido: Teşekkürler. Ayrıca, sadece birebir döküm yapabileceğinizi de buldum, yani hashabledict({key_a: val_a, key_b: val_b, ...}).
HelloGoodbye

62

Hashable'lar değişmez olmalıdır - bunu zorlamamalı, ancak bir anahtar olarak ilk kullanımından sonra bir dikteyi mutasyona uğratmamaya GÜVENİN, aşağıdaki yaklaşım işe yarayacaktır:

class hashabledict(dict):
  def __key(self):
    return tuple((k,self[k]) for k in sorted(self))
  def __hash__(self):
    return hash(self.__key())
  def __eq__(self, other):
    return self.__key() == other.__key()

Diktlerinizi mutasyona uğratmanız gerekiyorsa ve HALA bunları anahtar olarak kullanmak istiyorsanız, karmaşıklık yüz kat patlar - bunun yapılamayacağını söylemiyorum, ama O inanılmaz bataklığa girmeden önce ÇOK spesifik bir göstergeye kadar bekleyeceğim! -)


Kurallar hazırlandıktan sonra kesinlikle mutasyona uğratmak istemiyorum. Bu, packrad algoritmasının geri kalanının parçalanmasına neden olur.
SingleNegationElimination

Sonra önerdiğim alt sınıf çalışacaktır - "konumsal" sorunu nasıl atladığına dikkat edin ( sorunuzu belirtmek için düzenlemeden önce ;-) sortedin __key ;-) ile.
Alex Martelli

İsimlendirilmiş tuple'ın konuma bağlı davranışı beni çok şaşırttı. Bununla oynuyordum, sorunu çözmek için hala daha kolay bir yol olabileceğini düşünüyordum, ancak bu tüm umutlarımı
neredeyse boşa çıkardı

Diyelim ki bir diktim var ve onu bir hashabledict'e çevirmek istiyorum. Bunu nasıl yaparım?
jononomo


32

Sözlükleri amacınız için kullanılabilir hale getirmek için gereken tek şey bir __hash__ yöntemi eklemektir:

class Hashabledict(dict):
    def __hash__(self):
        return hash(frozenset(self))

Frozenset dönüşümünün tüm sözlüklerde çalışacağını unutmayın (yani anahtarların sıralanabilir olmasını gerektirmez). Aynı şekilde sözlük değerlerinde de herhangi bir sınırlama yoktur.

Aynı anahtarlara sahip ancak farklı değerlere sahip birçok sözlük varsa, hash'in değerleri hesaba katması gerekir. Bunu yapmanın en hızlı yolu:

class Hashabledict(dict):
    def __hash__(self):
        return hash((frozenset(self), frozenset(self.itervalues())))

Bu, frozenset(self.iteritems())iki nedenden daha hızlıdır . İlk olarak, frozenset(self)adım sözlükte depolanan hash değerlerini yeniden kullanır ve gereksiz çağrıları adresine kaydeder hash(key). İkinci olarak, yinelemelerin kullanılması değerlere doğrudan erişecek ve her arama yaptığınızda bellekte yeni birçok anahtar / değer demeti oluşturmak için öğeler tarafından kullanılan birçok bellek ayırıcı çağrısından kaçınacaktır .


@RaymondHettinger Yanılıyorsam düzeltin, ancak dictkendi anahtarlarının hash değerlerini önbelleğe almadığını düşündüm - ancak bireysel sınıflar (gibi str) karmalarını önbelleğe almayı seçebilir ve seçebilir. En azından dictanahtar olarak kullanılan özel sınıf örneklerimle bir oluşturduğumda , __hash__yöntemleri her erişim işleminde çağrıldı (python 3.4). Haklı olsam da olmasam da hash(frozenset(self)), anahtarların içinde önbelleğe alınmadıkları sürece önceden hesaplanmış hash değerlerini nasıl yeniden kullanabileceğimden emin değilim (bu durumda hash(frozenset(self.items())onları da yeniden kullanır).
en fazla

(Anahtar / değer) tuple oluşturma hakkındaki ikinci noktanıza gelince, .items () yöntemlerinin bir tuple listesi yerine bir görünüm döndürdüğünü ve bu görünümün oluşturulmasının temeldeki anahtarların ve değerlerin kopyalanmasını içermediğini düşündüm. (Yine Python 3.4.) Bununla birlikte, çoğu giriş farklı anahtarlara sahipse, sadece anahtarları hash etmenin avantajını görüyorum - çünkü (1) değerlerin hash edilmesi oldukça pahalıdır ve (2) değerlerin hashable olmasını zorunlu kılmak oldukça kısıtlayıcı
en fazla

6
Bu aynı zamanda iki farklı sözlük için aynı hashi oluşturma olasılığına da sahiptir. Değerlendirin {'one': 1, 'two': 2}ve{'one': 2, 'two': 1}
AgDude

Mike Graham yorumunda , başka bir nedenle dikte türetmenin ancak tanımlamak __missing__için kötü bir fikir olduğunu belirtir . Ne düşünüyorsun?
Piotr Dobrogost

1
Dikteden alt sınıflandırma Python 2.2'den beri iyi tanımlanmıştır. Python standart kitaplığından örnekler için collections.OrderedDict ve collections.Counter bakın. Diğer yorum, yalnızca MutableMapping'in alt sınıflarının iyi tanımlandığı yönündeki temelsiz inanca dayanmaktadır.
Raymond Hettinger

23

Verilen cevaplar tamam, ancak karma oluşturmak frozenset(...)yerine kullanarak geliştirilebilirler tuple(sorted(...)):

>>> import timeit
>>> timeit.timeit('hash(tuple(sorted(d.iteritems())))', "d = dict(a=3, b='4', c=2345, asdfsdkjfew=0.23424, x='sadfsadfadfsaf')")
4.7758948802947998
>>> timeit.timeit('hash(frozenset(d.iteritems()))', "d = dict(a=3, b='4', c=2345, asdfsdkjfew=0.23424, x='sadfsadfadfsaf')")
1.8153600692749023

Performans avantajı sözlüğün içeriğine bağlıdır, ancak test ettiğim çoğu durumda, hashing frozenseten az 2 kat daha hızlıdır (esas olarak sıralaması gerekmediği için).


1
Hem anahtarları hem de değerleri eklemeye gerek olmadığını unutmayın. Bu çözüm olacağını çok daha hızlı olarak: hash(frozenset(d)).
Raymond Hettinger

10
@RaymondHettinger: hash(frozenset(d))benzer anahtarlara ancak farklı değerlere sahip 2 dikt için aynı karmalarla sonuçlanır!
Oben Sonne

4
Bu bir problem değil. Farklı değerlere sahip kuralları birbirinden ayırt etmek __eq__'nin işidir. __Hash__’ın işi yalnızca arama alanını azaltmaktır.
Raymond Hettinger

5
Bu, karma ve eşlemelerin teorik konsepti için doğrudur, ancak arama olarak sözlükler içeren önbellekler için pratik değildir - benzer anahtarlara sahip sözlüklerin, ancak farklı değerlerin mem-önbelleğe alınmış bir işleve aktarılması alışılmadık bir durum değildir. Bu durumda, bir hash oluşturmak için sadece anahtarlar kullanılıyorsa, önbellek pratik olarak bir eşleme yerine bir listeye dönüşür.
Oben Sonne

3
Girintili anahtarlara ve farklı değerlere sahip özel diktler durumunda, sadece bir hash'i temel alarak depolamak daha iyi olur frozenset(d.itervalues()). Diktlerin farklı anahtarlara sahip olduğu durumlarda frozenset(d), çok daha hızlıdır ve anahtarların hashabilitesine hiçbir kısıtlama getirmez. Son olarak, dict .__ eq__ yönteminin eşit anahtar / değer çiftlerini çok daha hızlı kontrol edeceğini ve her şeyin tüm anahtar / değer çifti demetlerinin karmasını hesaplayabileceğini unutmayın. Anahtar / değer demetlerini kullanmak da sorunludur çünkü tüm anahtarlar için depolanan karmaları atar (bu yüzden frozenset(d)bu kadar hızlıdır).
Raymond Hettinger

11

Oldukça temiz, basit bir uygulama

import collections

class FrozenDict(collections.Mapping):
    """Don't forget the docstrings!!"""

    def __init__(self, *args, **kwargs):
        self._d = dict(*args, **kwargs)

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

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

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

    def __hash__(self):
        return hash(tuple(sorted(self._d.iteritems())))

Neden bu kadar makul, temiz ve anlaşılır? Yani gerekliliğini örneğin, diğer cevaplara farklılıkları açıklayınız __iter__ve __len__.
Karl Richter

1
@KarlRichter, makul olduğunu asla söylemedim, sadece makul derecede temiz. ;)
Mike Graham

@KarlRichter, ben tanımlamak __iter__ve __len__ben mecbur olduğum için, ben türetmek ediyorum beri collections.Mapping; nasıl kullanılacağı collections.Mapping, koleksiyonlar modülü belgelerinde oldukça iyi anlatılmıştır. Diğer insanlar türedikleri için buna ihtiyaç duymazlar dict. dictBaşka bir nedenle türetmek , ancak tanımlamak __missing__kötü bir fikirdir. Dikte spesifikasyonu, böyle bir durumda diktenin nasıl çalıştığını söylemiyor ve gerçekte bu, genel olarak daha az yararlı olan tonlarca sanal olmayan yönteme sahip olacak ve bu özel durumda ilgisiz davranışlara sahip körelmiş yöntemlere sahip olacak.
Mike Graham

7

Bu konuya dönüp duruyorum ... İşte başka bir varyasyon. dictBir __hash__yöntem eklemek için alt sınıflandırma yapmaktan rahatsız oluyorum ; Diktelerin değişken olduğu sorundan neredeyse hiçbir kaçış yoktur ve değişmeyeceklerine güvenmek zayıf bir fikir gibi görünür. Bunun yerine, kendisi değişmez olan yerleşik bir türe dayalı bir eşleme oluşturmaya baktım. tuplebariz bir seçim olmasına rağmen , içindeki değerlere erişmek bir çeşit ve bir ikiye bölme anlamına gelir; bir sorun değil, ancak üzerine kurulu olduğu türden gücün çoğunu kullanıyor gibi görünmüyor.

Ya anahtarı, değer çiftlerini a'ya sıkıştırırsanız frozenset? Bu ne gerektirir, nasıl çalışır?

Bölüm 1, 'öğeleri bir frozenset'in onları esas olarak anahtarlarıyla ele alacak şekilde kodlamanın bir yoluna ihtiyacınız var; Bunun için küçük bir alt sınıf yapacağım.

import collections
class pair(collections.namedtuple('pair_base', 'key value')):
    def __hash__(self):
        return hash((self.key, None))
    def __eq__(self, other):
        if type(self) != type(other):
            return NotImplemented
        return self.key == other.key
    def __repr__(self):
        return repr((self.key, self.value))

Tek başına bu, sizi değişmez bir haritalamanın ötesine götürür:

>>> frozenset(pair(k, v) for k, v in enumerate('abcd'))
frozenset([(0, 'a'), (2, 'c'), (1, 'b'), (3, 'd')])
>>> pairs = frozenset(pair(k, v) for k, v in enumerate('abcd'))
>>> pair(2, None) in pairs
True
>>> pair(5, None) in pairs
False
>>> goal = frozenset((pair(2, None),))
>>> pairs & goal
frozenset([(2, None)])

D'oh! Ne yazık ki, set işleçlerini kullandığınızda ve elemanlar eşittir ancak aynı nesne değildir; hangisinin dönüş değerinde bitmesi tanımsız ise , daha fazla dönüş yapmamız gerekecek.

>>> pairs - (pairs - goal)
frozenset([(2, 'c')])
>>> iter(pairs - (pairs - goal)).next().value
'c'

Bununla birlikte, değerlere bu şekilde bakmak zahmetlidir ve daha da kötüsü, birçok ara küme oluşturur; bu işe yaramaz! Bunu aşmak için 'sahte' bir anahtar / değer çifti oluşturacağız:

class Thief(object):
    def __init__(self, key):
        self.key = key
    def __hash__(self):
        return hash(pair(self.key, None))
    def __eq__(self, other):
        self.value = other.value
        return pair(self.key, None) == other

Hangi daha az sorunlu sonuçlanır:

>>> thief = Thief(2)
>>> thief in pairs
True
>>> thief.value
'c'

Hepsi derin sihir bu; geri kalanı ise hepsini bir dikte gibi bir arayüze sahip bir şeye sarmaktır. frozensetÇok farklı bir arayüze sahip olan alt sınıfa geçtiğimiz için oldukça fazla yöntem var; biraz yardım alıyoruz collections.Mapping, ancak işin çoğu frozenset, bunun yerine diktler gibi çalışan sürümler için yöntemleri geçersiz kılıyor:

class FrozenDict(frozenset, collections.Mapping):
    def __new__(cls, seq=()):
        return frozenset.__new__(cls, (pair(k, v) for k, v in seq))
    def __getitem__(self, key):
        thief = Thief(key)
        if frozenset.__contains__(self, thief):
            return thief.value
        raise KeyError(key)
    def __eq__(self, other):
        if not isinstance(other, FrozenDict):
            return dict(self.iteritems()) == other
        if len(self) != len(other):
            return False
        for key, value in self.iteritems():
            try:
                if value != other[key]:
                    return False
            except KeyError:
                return False
        return True
    def __hash__(self):
        return hash(frozenset(self.iteritems()))
    def get(self, key, default=None):
        thief = Thief(key)
        if frozenset.__contains__(self, thief):
            return thief.value
        return default
    def __iter__(self):
        for item in frozenset.__iter__(self):
            yield item.key
    def iteritems(self):
        for item in frozenset.__iter__(self):
            yield (item.key, item.value)
    def iterkeys(self):
        for item in frozenset.__iter__(self):
            yield item.key
    def itervalues(self):
        for item in frozenset.__iter__(self):
            yield item.value
    def __contains__(self, key):
        return frozenset.__contains__(self, pair(key, None))
    has_key = __contains__
    def __repr__(self):
        return type(self).__name__ + (', '.join(repr(item) for item in self.iteritems())).join('()')
    @classmethod
    def fromkeys(cls, keys, value=None):
        return cls((key, value) for key in keys)

bu da nihayetinde kendi sorumu yanıtlıyor:

>>> myDict = {}
>>> myDict[FrozenDict(enumerate('ab'))] = 5
>>> FrozenDict(enumerate('ab')) in myDict
True
>>> FrozenDict(enumerate('bc')) in myDict
False
>>> FrozenDict(enumerate('ab', 3)) in myDict
False
>>> myDict[FrozenDict(enumerate('ab'))]
5

5

@Unknown tarafından kabul edilen yanıt ve @AlexMartelli tarafından verilen yanıt mükemmel bir şekilde çalışıyor, ancak yalnızca aşağıdaki kısıtlamalar altında:

  1. Sözlüğün değerleri karma hale getirilebilir olmalıdır. Örneğin, hash(hashabledict({'a':[1,2]}))yükselecek TypeError.
  2. Anahtarlar, karşılaştırma işlemini desteklemelidir. Örneğin, hash(hashabledict({'a':'a', 1:1}))yükselecek TypeError.
  3. Anahtarlardaki karşılaştırma operatörü, toplam sıralamayı uygular. Örneğin, bir sözlükteki iki anahtar frozenset((1,2,3))ve ise frozenset((4,5,6)), her iki yönde de eşit olmayanları karşılaştırırlar. Bu nedenle, bir sözlüğün öğelerini bu tür anahtarlarla sıralamak keyfi bir sıraya neden olabilir ve bu nedenle, eşit nesnelerin aynı hash değerine sahip olması gerektiği kuralını ihlal eder.

@ObenSonne tarafından verilen çok daha hızlı yanıt, kısıtlamalar 2 ve 3'ü kaldırır, ancak yine de kısıtlama 1'e bağlıdır (değerler karma değer olmalıdır).

@RaymondHettinger tarafından verilen daha hızlı ancak yanıt .values(), karma hesaplamasına dahil olmadığı için 3 kısıtlamayı da kaldırır . Ancak performansı yalnızca aşağıdaki durumlarda iyidir:

  1. Karma hale getirilmesi gereken (eşit olmayan) sözlüklerin çoğu aynı değildir .keys().

Bu koşul yerine getirilmezse, hash işlevi yine de geçerli olacaktır, ancak çok fazla çarpışmaya neden olabilir. Örneğin, tüm sözlüklerin bir web sitesi şablonundan üretildiği uç durumda (anahtarlar olarak alan adları, değerler olarak kullanıcı girişi), anahtarlar her zaman aynı olacak ve karma işlevi tüm girdiler için aynı değeri döndürecektir. . Sonuç olarak, böyle bir hash işlevine dayanan bir hashtable, bir öğeyi alırken ( O(N)yerine O(1)) bir liste kadar yavaşlayacaktır .

Yukarıda listelediğim 4 kısıtlamanın tümü ihlal edilse bile aşağıdaki çözümün makul derecede iyi çalışacağını düşünüyorum. Yalnızca sözlükleri değil, aynı zamanda değiştirilebilir kaplar iç içe geçmiş olsalar bile herhangi bir kapsayıcıyı karma hale getirebilmesi gibi ek bir avantajı da vardır.

Şimdiye kadar bunu sadece hafifçe test ettiğim için bu konudaki geri bildirimlerden çok memnun olurum.

# python 3.4
import collections
import operator
import sys
import itertools
import reprlib

# a wrapper to make an object hashable, while preserving equality
class AutoHash:
    # for each known container type, we can optionally provide a tuple
    # specifying: type, transform, aggregator
    # even immutable types need to be included, since their items
    # may make them unhashable

    # transformation may be used to enforce the desired iteration
    # the result of a transformation must be an iterable
    # default: no change; for dictionaries, we use .items() to see values

    # usually transformation choice only affects efficiency, not correctness

    # aggregator is the function that combines all items into one object
    # default: frozenset; for ordered containers, we can use tuple

    # aggregator choice affects both efficiency and correctness
    # e.g., using a tuple aggregator for a set is incorrect,
    # since identical sets may end up with different hash values
    # frozenset is safe since at worst it just causes more collisions
    # unfortunately, no collections.ABC class is available that helps
    # distinguish ordered from unordered containers
    # so we need to just list them out manually as needed

    type_info = collections.namedtuple(
        'type_info',
        'type transformation aggregator')

    ident = lambda x: x
    # order matters; first match is used to handle a datatype
    known_types = (
        # dict also handles defaultdict
        type_info(dict, lambda d: d.items(), frozenset), 
        # no need to include set and frozenset, since they are fine with defaults
        type_info(collections.OrderedDict, ident, tuple),
        type_info(list, ident, tuple),
        type_info(tuple, ident, tuple),
        type_info(collections.deque, ident, tuple),
        type_info(collections.Iterable, ident, frozenset) # other iterables
    )

    # hash_func can be set to replace the built-in hash function
    # cache can be turned on; if it is, cycles will be detected,
    # otherwise cycles in a data structure will cause failure
    def __init__(self, data, hash_func=hash, cache=False, verbose=False):
        self._data=data
        self.hash_func=hash_func
        self.verbose=verbose
        self.cache=cache
        # cache objects' hashes for performance and to deal with cycles
        if self.cache:
            self.seen={}

    def hash_ex(self, o):
        # note: isinstance(o, Hashable) won't check inner types
        try:
            if self.verbose:
                print(type(o),
                    reprlib.repr(o),
                    self.hash_func(o),
                    file=sys.stderr)
            return self.hash_func(o)
        except TypeError:
            pass

        # we let built-in hash decide if the hash value is worth caching
        # so we don't cache the built-in hash results
        if self.cache and id(o) in self.seen:
            return self.seen[id(o)][0] # found in cache

        # check if o can be handled by decomposing it into components
        for typ, transformation, aggregator in AutoHash.known_types:
            if isinstance(o, typ):
                # another option is:
                # result = reduce(operator.xor, map(_hash_ex, handler(o)))
                # but collisions are more likely with xor than with frozenset
                # e.g. hash_ex([1,2,3,4])==0 with xor

                try:
                    # try to frozenset the actual components, it's faster
                    h = self.hash_func(aggregator(transformation(o)))
                except TypeError:
                    # components not hashable with built-in;
                    # apply our extended hash function to them
                    h = self.hash_func(aggregator(map(self.hash_ex, transformation(o))))
                if self.cache:
                    # storing the object too, otherwise memory location will be reused
                    self.seen[id(o)] = (h, o)
                if self.verbose:
                    print(type(o), reprlib.repr(o), h, file=sys.stderr)
                return h

        raise TypeError('Object {} of type {} not hashable'.format(repr(o), type(o)))

    def __hash__(self):
        return self.hash_ex(self._data)

    def __eq__(self, other):
        # short circuit to save time
        if self is other:
            return True

        # 1) type(self) a proper subclass of type(other) => self.__eq__ will be called first
        # 2) any other situation => lhs.__eq__ will be called first

        # case 1. one side is a subclass of the other, and AutoHash.__eq__ is not overridden in either
        # => the subclass instance's __eq__ is called first, and we should compare self._data and other._data
        # case 2. neither side is a subclass of the other; self is lhs
        # => we can't compare to another type; we should let the other side decide what to do, return NotImplemented
        # case 3. neither side is a subclass of the other; self is rhs
        # => we can't compare to another type, and the other side already tried and failed;
        # we should return False, but NotImplemented will have the same effect
        # any other case: we won't reach the __eq__ code in this class, no need to worry about it

        if isinstance(self, type(other)): # identifies case 1
            return self._data == other._data
        else: # identifies cases 2 and 3
            return NotImplemented

d1 = {'a':[1,2], 2:{3:4}}
print(hash(AutoHash(d1, cache=True, verbose=True)))

d = AutoHash(dict(a=1, b=2, c=3, d=[4,5,6,7], e='a string of chars'),cache=True, verbose=True)
print(hash(d))

2

Ayrıca, v2 asitleme protokolünün hashdict örnekleriyle çalışmasını sağlamak için bu iki yöntemi eklemek isteyebilirsiniz. Aksi takdirde cPickle hashdict .____ setitem____ 'i kullanmayı deneyecek ve bir TypeError ile sonuçlanacaktır. İlginç bir şekilde, protokolün diğer iki sürümüyle kodunuz gayet iyi çalışıyor.

def __setstate__(self, objstate):
    for k,v in objstate.items():
        dict.__setitem__(self,k,v)
def __reduce__(self):
    return (hashdict, (), dict(self),)

-2

Sözlüğe sayı koymazsanız ve sözlüklerinizi içeren değişkenleri asla kaybetmezseniz, bunu yapabilirsiniz:

cache[id(rule)] = "whatever"

id () her sözlük için benzersiz olduğundan

DÜZENLE:

Üzgünüm, evet bu durumda diğerlerinin söyledikleri daha iyi olur. Sanırım sözlüklerinizi bir dizi olarak da seri hale getirebilirsiniz, örneğin

cache[ 'foo:bar' ] = 'baz'

Sözlüklerinizi anahtarlardan kurtarmanız gerekiyorsa, o zaman daha çirkin bir şey yapmanız gerekir.

cache[ 'foo:bar' ] = ( {'foo':'bar'}, 'baz' )

Sanırım bunun avantajı, çok fazla kod yazmanıza gerek kalmaması.


Hmmm, hayır; Aradığım şey bu değil: cache[id({'foo':'bar'})] = 'baz'; id({'foo':'bar'}) not in cacheDinamik olarak anahtarlar yaratabilmek, ilk etapta diktleri anahtar olarak kullanmak istediğimde önemli.
SingleNegationElimination

1
Diktleri serileştirmek uygun olabilir, onları serileştirmek için bir öneriniz var mı? aradığım şey bu.
SingleNegationElimination
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.