Bagaimana saya bisa membuat subkelas dict sebagai "sesempurna"?
Tujuan akhirnya adalah memiliki dict sederhana di mana kuncinya adalah huruf kecil.
Jika saya mengganti __getitem__
/ __setitem__
, maka get / set tidak berfungsi. Bagaimana saya 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, perbarui dan __init__
?
Haruskah saya hanya menggunakan mutablemapping
(sepertinya orang tidak boleh menggunakan UserDict
atau DictMixin
)? Jika ya, bagaimana caranya? Dokumen tidak sepenuhnya mencerahkan.
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:
Bagaimana saya bisa membuat subkelas dict sebagai "sesempurna"? Tujuan akhirnya adalah memiliki dict sederhana di mana kuncinya adalah huruf kecil.
Jawaban yang diterima sebenarnya bukan subkelas dict
, dan tes untuk ini gagal:
>>> isinstance(MyTransformedDict([('Test', 'test')]), dict)
False
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:
- Jawaban yang diterima juga hilang classmethod yang:
fromkeys
.
Jawaban yang diterima juga memiliki redundan __dict__
- karena itu mengambil lebih banyak ruang dalam memori:
>>> s.foo = 'bar'
>>> s.__dict__
{'foo': 'bar', 'store': {'test': 'test'}}
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.
Jika saya mengganti __getitem__
/ __setitem__
, maka get / set tidak berfungsi. Bagaimana saya membuatnya bekerja? Tentunya saya tidak perlu mengimplementasikannya secara individual?
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 argumen dict.pop
, dan membuat fungsi untuk memastikan kunci string kita huruf kecil:
from itertools import chain
try: # Python 2
str_base = basestring
items = 'iteritems'
except NameError: # Python 3
str_base = str, bytes, bytearray
items = 'items'
_RaiseKeyError = object() # singleton for no-default behavior
def ensure_lower(maybe_str):
"""dict keys can be any hashable object - only call lower if str"""
return maybe_str.lower() if isinstance(maybe_str, str_base) else maybe_str
Sekarang kita implementasikan - saya menggunakan super
dengan argumen lengkap sehingga kode ini berfungsi untuk Python 2 dan 3:
class LowerDict(dict): # dicts take a mapping or iterable as their optional first argument
__slots__ = () # no __dict__ - that would be redundant
@staticmethod # because this doesn't make sense as a global function.
def _process_args(mapping=(), **kwargs):
if hasattr(mapping, items):
mapping = getattr(mapping, items)()
return ((ensure_lower(k), v) for k, v in chain(mapping, getattr(kwargs, items)()))
def __init__(self, mapping=(), **kwargs):
super(LowerDict, self).__init__(self._process_args(mapping, **kwargs))
def __getitem__(self, k):
return super(LowerDict, self).__getitem__(ensure_lower(k))
def __setitem__(self, k, v):
return super(LowerDict, self).__setitem__(ensure_lower(k), v)
def __delitem__(self, k):
return super(LowerDict, self).__delitem__(ensure_lower(k))
def get(self, k, default=None):
return super(LowerDict, self).get(ensure_lower(k), default)
def setdefault(self, k, default=None):
return super(LowerDict, self).setdefault(ensure_lower(k), default)
def pop(self, k, v=_RaiseKeyError):
if v is _RaiseKeyError:
return super(LowerDict, self).pop(ensure_lower(k))
return super(LowerDict, self).pop(ensure_lower(k), v)
def update(self, mapping=(), **kwargs):
super(LowerDict, self).update(self._process_args(mapping, **kwargs))
def __contains__(self, k):
return super(LowerDict, self).__contains__(ensure_lower(k))
def copy(self): # don't delegate w/ super - dict.copy() -> dict :(
return type(self)(self)
@classmethod
def fromkeys(cls, keys, v=None):
return super(LowerDict, cls).fromkeys((ensure_lower(k) for k in keys), v)
def __repr__(self):
return '{0}({1})'.format(type(self).__name__, super(LowerDict, self).__repr__())
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
, dan values
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:
>>> ld = LowerDict(dict(foo='bar'))
>>> ld['FOO']
'bar'
>>> ld['foo']
'bar'
>>> ld.pop('FoO')
'bar'
>>> ld.setdefault('Foo')
>>> ld
{'foo': None}
>>> ld.get('Bar')
>>> ld.setdefault('Bar')
>>> ld
{'bar': None, 'foo': None}
>>> ld.popitem()
('bar', None)
Apakah saya mencegah acar tidak bekerja, dan apakah saya perlu menerapkan
__setstate__
dll?
acar
Dan acar subclass dict baik-baik saja:
>>> import pickle
>>> pickle.dumps(ld)
b'\x80\x03c__main__\nLowerDict\nq\x00)\x81q\x01X\x03\x00\x00\x00fooq\x02Ns.'
>>> pickle.loads(pickle.dumps(ld))
{'foo': None}
>>> type(pickle.loads(pickle.dumps(ld)))
<class '__main__.LowerDict'>
__repr__
Apakah saya perlu repr, perbarui dan __init__
?
Kami mendefinisikan update
dan __init__
, tetapi Anda memiliki cantik __repr__
secara default:
>>> ld # without __repr__ defined for the class, we get this
{'foo': None}
Namun, ada baiknya menulis __repr__
untuk meningkatkan kemampuan debug kode Anda. Tes yang ideal adalah eval(repr(obj)) == obj
. Jika mudah dilakukan untuk kode Anda, saya sangat merekomendasikannya:
>>> ld = LowerDict({})
>>> eval(repr(ld)) == ld
True
>>> ld = LowerDict(dict(a=1, b=2, c=3))
>>> eval(repr(ld)) == ld
True
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:
>>> ld
LowerDict({'a': 1, 'c': 3, 'b': 2})
Kesimpulan
Haruskah saya hanya menggunakan mutablemapping
(sepertinya orang tidak boleh menggunakan UserDict
atau DictMixin
)? Jika ya, bagaimana caranya? Dokumen tidak sepenuhnya mencerahkan.
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:
my_dict[transform(key)]
Seharusnya jauh lebih mudah di-debug.
Membandingkan dan kontras
Ada 6 fungsi antarmuka diimplementasikan dengan MutableMapping
(yang hilang fromkeys
) dan 11 dengan dict
subclass. Saya tidak perlu untuk mengimplementasikan __iter__
atau __len__
, tetapi aku harus melaksanakan get
, setdefault
, pop
, update
, copy
, __contains__
, dan fromkeys
- tetapi ini cukup sepele, karena saya bisa menggunakan warisan untuk sebagian dari mereka implementasi.
The MutableMapping
alat beberapa hal di Python yang dict
mengimplementasikan dalam C - jadi saya akan mengharapkan dict
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 pikir dict
subclass akan membandingkan lebih cepat.
Ringkasan:
- subclassing
MutableMapping
lebih sederhana dengan peluang bug yang lebih sedikit, tetapi lebih lambat, membutuhkan lebih banyak memori (lihat redundant dict), dan gagalisinstance(x, dict)
- subkelas
dict
lebih cepat, menggunakan lebih sedikit memori, dan lulus isinstance(x, dict)
, tetapi memiliki kompleksitas yang lebih besar untuk diterapkan.
Mana yang lebih sempurna? Itu tergantung pada definisi Anda tentang sempurna.