Sebagai latihan, dan sebagian besar untuk hiburan saya sendiri, saya menerapkan pengurai paket mundur. Inspirasi untuk ini adalah saya ingin memiliki gagasan yang lebih baik tentang bagaimana makro higenis akan bekerja dalam bahasa seperti algol (seperti yang diterapkan pada dialek cadel bebas sintaks yang biasanya Anda temukan di dalamnya). Karena itu, masukan yang berbeda melalui masukan mungkin melihat tata bahasa yang berbeda, sehingga hasil parse yang disimpan dalam cache tidak valid, kecuali saya juga menyimpan versi tata bahasa saat ini bersama dengan hasil parse yang disimpan dalam cache. ( EDIT : konsekuensi dari penggunaan koleksi nilai kunci ini adalah bahwa koleksi tersebut harus tidak dapat diubah, tetapi saya tidak bermaksud untuk mengekspos antarmuka untuk memungkinkannya diubah, jadi koleksi yang dapat diubah atau tidak dapat diubah baik-baik saja)
Masalahnya adalah bahwa dicts python tidak dapat muncul sebagai kunci untuk dicts lain. Bahkan menggunakan tupel (seperti yang akan saya lakukan) tidak membantu.
>>> cache = {}
>>> rule = {"foo":"bar"}
>>> cache[(rule, "baz")] = "quux"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'
>>>
Saya kira itu harus menjadi tupel sampai ke bawah. Sekarang pustaka standar python menyediakan kira-kira apa yang saya perlukan, collections.namedtuple
memiliki sintaks yang sangat berbeda, tetapi dapat digunakan sebagai kunci. melanjutkan dari sesi di atas:
>>> from collections import namedtuple
>>> Rule = namedtuple("Rule",rule.keys())
>>> cache[(Rule(**rule), "baz")] = "quux"
>>> cache
{(Rule(foo='bar'), 'baz'): 'quux'}
Baik. Tetapi saya harus membuat kelas untuk setiap kemungkinan kombinasi kunci dalam aturan yang ingin saya gunakan, yang tidak terlalu buruk, karena setiap aturan parse tahu persis parameter apa yang digunakannya, sehingga kelas tersebut dapat didefinisikan pada saat yang sama sebagai fungsi yang mengurai aturan.
Sunting: Masalah tambahan dengan namedtuple
s adalah bahwa mereka ketat posisional. Dua tupel yang terlihat berbeda sebenarnya bisa sama:
>>> 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: Bagaimana cara mendapatkan dict
s yang dapat digunakan sebagai kunci untuk dict
s lain ?
Setelah sedikit meretas jawabannya, inilah solusi yang lebih lengkap yang saya gunakan. Perhatikan bahwa ini melakukan sedikit pekerjaan ekstra untuk membuat penis yang dihasilkan tidak dapat diubah untuk tujuan praktis. Tentu saja masih cukup mudah untuk meretasnya dengan menelepon dict.__setitem__(instance, key, value)
tetapi kita semua orang dewasa di sini.
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()
hashdict
harus berubah, setidaknya setelah Anda mulai hashing, jadi mengapa tidak cachekey
danhash
nilai-nilai sebagai atribut darihashdict
objek? Saya memodifikasi__key()
dan__hash__()
, dan menguji untuk memastikan bahwa ini jauh lebih cepat. SO tidak mengizinkan kode berformat dalam komentar, jadi saya akan menautkannya di sini: sam.aiki.info/hashdict.py