Bagaimana saya bisa membuat subkelas dict sebagai "sesempurna" ? Tujuan akhirnya adalah memiliki dict sederhana di mana kuncinya adalah huruf kecil.
Tampaknya harus ada sekelompok kecil primitif yang bisa saya timpa untuk membuat ini berhasil, tetapi menurut semua penelitian dan upaya saya, sepertinya ini bukan masalahnya:
Jika saya mengganti
__getitem__
/__setitem__
, makaget
/set
tidak berfungsi. Bagaimana saya bisa membuatnya bekerja? Tentunya saya tidak perlu mengimplementasikannya secara individual?Apakah saya mencegah acar tidak bekerja, dan apakah saya perlu menerapkan
__setstate__
dll?Apakah saya perlu
repr
,update
dan__init__
?Haruskah saya menggunakan mutablemapping (sepertinya orang tidak boleh menggunakan
UserDict
atauDictMixin
)? Jika ya, bagaimana caranya? Dokumen tidak sepenuhnya mencerahkan.
Inilah yang pertama saya lakukan, get()
tidak bekerja dan tidak diragukan lagi ada banyak masalah kecil lainnya:
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
# /programming/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()
sumber
Jawaban:
Anda dapat menulis objek yang berperilaku seperti
dict
cukup mudah dengan ABC (Abstract Base Classes) daricollections.abc
modul. Bahkan memberi tahu Anda jika Anda melewatkan suatu metode, jadi di bawah ini adalah versi minimal yang menutup ABC.Anda mendapatkan beberapa metode gratis dari ABC:
Saya tidak akan subkelas
dict
(atau builtin lainnya) secara langsung. Seringkali tidak masuk akal, karena apa yang sebenarnya ingin Anda lakukan adalah mengimplementasikan antarmuka adict
. Dan itulah tepatnya untuk apa ABC.sumber
__keytransform__()
karena melanggar panduan gaya PEP 8 yang menyarankan "Jangan pernah menemukan nama seperti itu; gunakan saja seperti yang didokumentasikan" di akhir bagian Deskriptif: Penamaan Gaya .if isinstance(t, collections.MutableMapping): print t, "can be used like a dict"
. Jangan periksa jenis objek, periksa antarmuka.Jawaban yang diterima akan menjadi pendekatan pertama saya, tetapi karena memiliki beberapa masalah, dan karena tidak ada yang membahas alternatifnya, sebenarnya subklasifikasi a
dict
, saya akan melakukannya di sini.Apa yang salah dengan jawaban yang diterima?
Ini sepertinya permintaan yang agak sederhana bagi saya:
Jawaban yang diterima sebenarnya bukan subkelas
dict
, dan tes untuk ini gagal:Idealnya, kode pemeriksaan jenis apa pun akan menguji antarmuka yang kami harapkan, atau kelas dasar abstrak, tetapi jika objek data kami diteruskan ke fungsi yang sedang diuji
dict
- dan kami tidak dapat "memperbaiki" fungsi-fungsi itu, kode ini akan gagal.Pertengkaran lain yang mungkin terjadi:
fromkeys
.Jawaban yang diterima juga memiliki redundan
__dict__
- karena itu mengambil lebih banyak ruang dalam memori:Sebenarnya subklasifikasi
dict
Kita dapat menggunakan kembali metode dikt melalui pewarisan. Yang perlu kita lakukan adalah membuat lapisan antarmuka yang memastikan kunci dilewatkan ke dalam dikt dalam bentuk huruf kecil jika mereka adalah string.
Ya, menerapkannya masing-masing secara perorangan adalah kelemahan dari pendekatan ini dan sisi baiknya untuk menggunakan
MutableMapping
(lihat jawaban yang diterima), tetapi sebenarnya tidak terlalu banyak pekerjaan.Pertama, mari kita faktor perbedaan antara Python 2 dan 3, buat singleton (
_RaiseKeyError
) untuk memastikan kita tahu jika kita benar-benar mendapatkan argumendict.pop
, dan membuat fungsi untuk memastikan kunci string kita huruf kecil:Sekarang kita implementasikan - saya menggunakan
super
dengan argumen lengkap sehingga kode ini berfungsi untuk Python 2 dan 3:Kami menggunakan pendekatan hampir boiler-piring untuk setiap metode atau metode khusus yang referensi kunci, tetapi sebaliknya, oleh warisan, kita mendapatkan metode:
len
,clear
,items
,keys
,popitem
, danvalues
gratis. Sementara ini membutuhkan beberapa pemikiran yang cermat untuk mendapatkan yang benar, itu sepele untuk melihat bahwa ini berhasil.(Catatan yang
haskey
sudah usang dalam Python 2, dihapus dalam Python 3.)Inilah beberapa penggunaan:
acar
Dan acar subclass dict baik-baik saja:
__repr__
Kami mendefinisikan
update
dan__init__
, tetapi Anda memiliki cantik__repr__
secara default:Namun, ada baiknya menulis
__repr__
untuk meningkatkan kemampuan debug kode Anda. Tes yang ideal adalaheval(repr(obj)) == obj
. Jika mudah dilakukan untuk kode Anda, saya sangat merekomendasikannya:Anda tahu, itu persis apa yang kita butuhkan untuk membuat ulang objek yang setara - ini adalah sesuatu yang mungkin muncul di log kami atau di backtraces:
Kesimpulan
Ya, ini adalah beberapa baris kode lagi, tetapi dimaksudkan untuk bersifat komprehensif. Kecenderungan pertama saya adalah menggunakan jawaban yang diterima, dan jika ada masalah dengannya, maka saya akan melihat jawaban saya - karena ini sedikit lebih rumit, dan tidak ada ABC untuk membantu saya mengatur antarmuka saya dengan benar.
Optimalisasi prematur akan meningkatkan kompleksitas dalam mencari kinerja.
MutableMapping
lebih sederhana - sehingga mendapat keunggulan langsung, semuanya sama. Namun demikian, untuk menjelaskan semua perbedaan, mari kita bandingkan dan kontraskan.Saya harus menambahkan bahwa ada dorongan untuk memasukkan kamus serupa ke dalam
collections
modul, tetapi ditolak . Anda mungkin harus melakukan ini sebagai gantinya:Seharusnya jauh lebih mudah di-debug.
Membandingkan dan kontras
Ada 6 fungsi antarmuka diimplementasikan dengan
MutableMapping
(yang hilangfromkeys
) dan 11 dengandict
subclass. Saya tidak perlu untuk mengimplementasikan__iter__
atau__len__
, tetapi aku harus melaksanakanget
,setdefault
,pop
,update
,copy
,__contains__
, danfromkeys
- tetapi ini cukup sepele, karena saya bisa menggunakan warisan untuk sebagian dari mereka implementasi.The
MutableMapping
alat beberapa hal di Python yangdict
mengimplementasikan dalam C - jadi saya akan mengharapkandict
subclass untuk lebih performant dalam beberapa kasus.Kami mendapatkan gratis
__eq__
di kedua pendekatan - yang keduanya mengasumsikan kesetaraan hanya jika dict lain semua huruf kecil - tapi sekali lagi, saya pikirdict
subclass akan membandingkan lebih cepat.Ringkasan:
MutableMapping
lebih sederhana dengan peluang bug yang lebih sedikit, tetapi lebih lambat, membutuhkan lebih banyak memori (lihat redundant dict), dan gagalisinstance(x, dict)
dict
lebih cepat, menggunakan lebih sedikit memori, dan lulusisinstance(x, dict)
, tetapi memiliki kompleksitas yang lebih besar untuk diterapkan.Mana yang lebih sempurna? Itu tergantung pada definisi Anda tentang sempurna.
sumber
__slots__
atau mungkin menggunakan kembali__dict__
sebagai toko, tetapi itu mencampur semantik, titik kritik potensial lainnya.ensure_lower
pada arguemtn pertama (yang selalu kuncinya)? Maka itu akan menjadi jumlah override yang sama, tetapi mereka semua akan menjadi bentuk__getitem__ = ensure_lower_decorator(super(LowerDict, self).__getitem__)
.copy
- Saya pikir itu harus dilakukan, bukan? Saya pikir itu harus menguji untuk antarmuka - misalnya objek DataFrame panda bukan contoh pemetaan (pada pemeriksaan terakhir) tetapi memiliki item / iteritem.Persyaratan saya sedikit lebih ketat:
Pikiran awal saya adalah mengganti kelas Path kami yang kikuk dengan subkelas unicode yang tidak sensitif - tetapi:
some_dict[CIstr(path)]
jelek)Jadi saya akhirnya harus menuliskan dict case yang tidak sensitif. Berkat kode oleh @AaronHall yang dibuat 10 kali lebih mudah.
Tersirat vs eksplisit masih menjadi masalah, tetapi begitu debu mengendap, penggantian nama atribut / variabel untuk memulai dengan ci (dan komentar dokter besar yang menjelaskan bahwa ci singkatan dari case-sensitive) Saya pikir ini adalah solusi sempurna - karena pembaca kode harus menyadari sepenuhnya bahwa kita sedang berhadapan dengan struktur data yang mendasari kasus sensitif. Mudah-mudahan ini akan memperbaiki beberapa mereproduksi bug yang sulit, yang saya duga bermuara pada sensitivitas case.
Komentar / koreksi diterima :)
sumber
__repr__
harus menggunakan kelas induk__repr__
untuk lulus evaluasi (repr (obj)) == tes obj (saya tidak berpikir itu benar sekarang) dan tidak bergantung pada__str__
.total_ordering
dekorator kelas - yang akan menghilangkan 4 metode dari subkelas unicode Anda. Tapi subclass dict terlihat sangat pintar diimplementasikan. : PCIstr.__repr__
, dalam kasus Anda , dapat lulus tes repr dengan sangat sedikit kerumitan, dan itu akan membuat debugging jauh lebih baik. Saya juga menambahkan__repr__
untuk dict Anda. Saya akan melakukannya dalam jawaban saya untuk menunjukkan.__slots__
dalam CIstr - tidak membuat perbedaan dalam kinerja (CIstr tidak dimaksudkan untuk subkelas atau memang digunakan di luar LowerDict, harus menjadi kelas akhir bersarang statis). Masih tidak yakin bagaimana menyelesaikan masalah repr secara elegan (sengatan mungkin berisi kombinasi'
dan"
kutipan)Yang harus Anda lakukan adalah
ATAU
Contoh penggunaan untuk penggunaan pribadi saya
Catatan : hanya diuji di python3
sumber
Setelah mencoba kedua atas dua saran, saya sudah menetap pada rute menengah tampak teduh untuk Python 2.7. Mungkin 3 lebih waras, tetapi bagi saya:
yang benar-benar saya benci, tetapi tampaknya sesuai dengan kebutuhan saya, yaitu:
**my_dict
dict
, ini mem-bypass kode Anda . coba itu.isinstance(my_dict, dict)
dict
Jika Anda perlu membedakan diri dari orang lain, secara pribadi saya menggunakan sesuatu seperti ini (meskipun saya akan merekomendasikan nama yang lebih baik):
Selama Anda hanya perlu mengenali diri Anda secara internal, cara ini lebih sulit untuk secara tidak sengaja memanggil
__am_i_me
karena nama-python (ini diubah namanya menjadi_MyDict__am_i_me
dari apa pun yang memanggil di luar kelas ini). Sedikit lebih pribadi daripada_method
s, baik dalam praktik maupun secara budaya.Sejauh ini saya tidak memiliki keluhan, selain dari
__class__
penimpaan yang terlihat sangat teduh . Saya akan senang mendengar masalah yang orang lain hadapi dengan ini, saya tidak sepenuhnya mengerti konsekuensinya. Tapi sejauh ini saya tidak punya masalah apa pun, dan ini memungkinkan saya untuk memigrasi banyak kode berkualitas menengah di banyak lokasi tanpa perlu perubahan apa pun.Sebagai bukti: https://repl.it/repls/TraumaticToughCockatoo
Pada dasarnya: salin opsi # 2 saat ini , tambahkan
print 'method_name'
baris ke setiap metode, lalu coba ini dan perhatikan hasilnya:Anda akan melihat perilaku serupa untuk skenario lainnya. Katakanlah palsu Anda
dict
adalah pembungkus di sekitar jenis data lain, jadi tidak ada cara yang masuk akal untuk menyimpan data di dalam backing-dict;**your_dict
akan kosong, terlepas dari apa yang dilakukan setiap metode lainnya.Ini berfungsi dengan benar
MutableMapping
, tetapi begitu Anda mewarisinyadict
menjadi tidak terkendali.Sunting: sebagai pembaruan, ini telah berjalan tanpa masalah tunggal selama hampir dua tahun sekarang, di beberapa ratus ribu (eh, mungkin beberapa juta) garis-garis rumit, warisan python yang ditunggangi. Jadi saya cukup senang dengan itu :)
Sunting 2: ternyata saya salah menyalin ini atau sesuatu yang sudah lama.
@classmethod __class__
tidak bekerja untukisinstance
cek -@property __class__
tidak: https://repl.it/repls/UnitedScientificSequencesumber
**your_dict
akan kosong" (jika Anda subkelas daridict
)? Saya belum melihat masalah dengan dikt membongkar ...**your_dict
tidak menjalankan kode Anda, jadi tidak dapat menampilkan sesuatu yang "spesial". Misalnya Anda tidak dapat menghitung "membaca" karena tidak menjalankan kode penghitungan baca Anda. MutableMapping melakukan pekerjaan untuk ini (menggunakannya jika Anda bisa!), Tapi gagalisinstance(..., dict)
jadi saya tidak bisa menggunakannya. perangkat lunak warisan yay.**your_dict
, tetapi saya merasa sangat menarik yangMutableMapping
akan melakukan itu.**some_dict
cukup umum. Setidaknya itu terjadi sangat sering di dekorator, jadi jika Anda memiliki apa , Anda segera berisiko perilaku yang tampaknya mustahil jika Anda tidak memperhitungkan itu.def __class__()
triknya tampaknya tidak bekerja dengan Python 2 atau 3, setidaknya untuk contoh kode dalam pertanyaan. Bagaimana cara mendaftar implementasi abc.MutableMapping sebagai subclass dict? (dimodifikasi agar tidak berfungsi di dua versi). Saya inginisinstance(SpreadSheet(), dict)
kembaliTrue
.