Kelas python yang bertindak seperti dict

113

Saya ingin menulis kelas khusus yang berperilaku seperti dict- jadi, saya mewarisi dari dict.

Pertanyaan saya, adalah: Apakah saya perlu membuat anggota pribadi dictdalam __init__()metode saya ?. Saya tidak mengerti maksudnya, karena saya sudah memiliki dictperilaku jika saya hanya mewarisi dict.

Adakah yang bisa menjelaskan mengapa sebagian besar cuplikan warisan terlihat seperti di bawah ini?

class CustomDictOne(dict):
   def __init__(self):
      self._mydict = {} 

   # other methods follow

Alih-alih yang lebih sederhana ...

class CustomDictTwo(dict):
   def __init__(self):
      # initialize my other stuff here ...

   # other methods follow

Sebenarnya, saya rasa saya curiga jawaban dari pertanyaan tersebut adalah agar pengguna tidak dapat langsung mengakses kamus Anda (yaitu mereka harus menggunakan metode akses yang telah Anda sediakan).

Namun, bagaimana dengan operator akses array []? Bagaimana cara menerapkan itu? Sejauh ini, saya belum melihat contoh yang menunjukkan cara menimpa []operator.

Jadi jika []fungsi akses tidak disediakan di kelas kustom, metode dasar yang diwarisi akan beroperasi pada kamus yang berbeda?

Saya mencoba potongan berikut untuk menguji pemahaman saya tentang warisan Python:

class myDict(dict):
    def __init__(self):
        self._dict = {}

    def add(self, id, val):
        self._dict[id] = val


md = myDict()
md.add('id', 123)
print md[id]

Saya mendapat kesalahan berikut:

KeyError: <built-in function id>

Ada apa dengan kode di atas?

Bagaimana cara mengoreksi kelas myDictagar saya dapat menulis kode seperti ini?

md = myDict()
md['id'] = 123

[Sunting]

Saya telah mengedit contoh kode di atas untuk menghilangkan kesalahan konyol yang saya buat sebelum saya lari dari meja saya. Itu salah ketik (saya seharusnya melihatnya dari pesan kesalahan).

skyeagle
sumber

Jawaban:

43

Periksa dokumentasi tentang meniru jenis wadah . Dalam kasus Anda, parameter pertama addharus self.

Björn Pollex
sumber
105
class Mapping(dict):

    def __setitem__(self, key, item):
        self.__dict__[key] = item

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

    def __repr__(self):
        return repr(self.__dict__)

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

    def __delitem__(self, key):
        del self.__dict__[key]

    def clear(self):
        return self.__dict__.clear()

    def copy(self):
        return self.__dict__.copy()

    def has_key(self, k):
        return k in self.__dict__

    def update(self, *args, **kwargs):
        return self.__dict__.update(*args, **kwargs)

    def keys(self):
        return self.__dict__.keys()

    def values(self):
        return self.__dict__.values()

    def items(self):
        return self.__dict__.items()

    def pop(self, *args):
        return self.__dict__.pop(*args)

    def __cmp__(self, dict_):
        return self.__cmp__(self.__dict__, dict_)

    def __contains__(self, item):
        return item in self.__dict__

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

    def __unicode__(self):
        return unicode(repr(self.__dict__))


o = Mapping()
o.foo = "bar"
o['lumberjack'] = 'foo'
o.update({'a': 'b'}, c=44)
print 'lumberjack' in o
print o

In [187]: run mapping.py
True
{'a': 'b', 'lumberjack': 'foo', 'foo': 'bar', 'c': 44}
Ricky Wilson
sumber
37
Jika Anda akan ke subclass dict, maka Anda harus menggunakan objek itu sendiri (menggunakan super) daripada hanya mendelegasikan ke instance __dict__- yang pada dasarnya berarti Anda membuat dua dicts untuk setiap instance.
Aaron Hall
8
self .__ dict__ tidak sama dengan konten kamus yang sebenarnya. Setiap objek python, apa pun tipenya, memiliki a _dict__yang berisi semua atribut objek (metode, bidang, dll). Anda tidak ingin main-main dengan ini kecuali jika Anda ingin menulis kode yang memodifikasi dirinya sendiri ...
Raik
86

Seperti ini

class CustomDictOne(dict):
   def __init__(self,*arg,**kw):
      super(CustomDictOne, self).__init__(*arg, **kw)

Sekarang Anda dapat menggunakan fungsi bawaan, seperti dict.get()sebagai self.get().

Anda tidak perlu membungkus yang tersembunyi self._dict. Kelas Anda sudah merupakan sebuah dict.

S. Lott
sumber
3
Ini. Tidak ada gunanya mewarisi dari dicttanpa memanggil konstruktornya terlebih dahulu.
sykora
1
Perhatikan bahwa warisan Anda dictsebenarnya berisi 2 dict-instance: Yang pertama adalah container yang diwarisi, dan yang kedua adalah dict yang memegang atribut kelas - Anda dapat menghindarinya dengan menggunakan slot .
ankostis
1
Ini __dict__sebenarnya hanya dibuat saat pertama kali diakses, jadi selama pengguna tidak mencoba menggunakannya, tidak masalah. __slots__akan menyenangkan.
Aaron Hall
9
Gunakan spasi setelah koma yang Anda biadab! ;-)
James Burke
10

Demi kelengkapan, berikut ini tautan ke dokumentasi yang disebutkan oleh @ björn-pollex untuk Python 2.x terbaru (2.7.7 pada saat penulisan):

Meniru Jenis Kontainer

(Maaf karena tidak menggunakan fungsi komentar, saya tidak diizinkan melakukannya dengan stackoverflow.)

he1ix
sumber
3

Masalah dengan potongan kode ini:

class myDict(dict):
    def __init__(self):
        self._dict = {}

    def add(id, val):
        self._dict[id] = val


md = myDict()
md.add('id', 123)

... adalah metode 'tambah' Anda (... dan metode apa pun yang Anda inginkan untuk menjadi anggota kelas) harus memiliki 'diri' eksplisit yang dideklarasikan sebagai argumen pertamanya, seperti:

def add(self, 'id', 23):

Untuk menerapkan operator yang kelebihan beban untuk mengakses item dengan kunci, lihat di dokumen untuk metode ajaib __getitem__dan __setitem__.

Perhatikan bahwa karena Python menggunakan Pengetikan Bebek, sebenarnya tidak ada alasan untuk mendapatkan kelas dict khusus dari kelas dict bahasa - tanpa mengetahui lebih banyak tentang apa yang Anda coba lakukan (misalnya, jika Anda perlu meneruskan contoh ini kelas ke beberapa kode di suatu tempat yang akan rusak kecuali isinstance(MyDict(), dict) == True), Anda mungkin lebih baik hanya menerapkan API yang membuat kelas Anda cukup seperti dikt dan berhenti di sana.

bgporter
sumber
3

Berikut solusi alternatifnya:

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__dict__ = self

a = AttrDict()
a.a = 1
a.b = 2
张小诚
sumber
Ini buruk karena Anda tidak menentukan metode kustom apa pun dan juga ada masalah lain seperti yang dikatakan jawaban lain.
Shital Shah
inilah yang saya butuhkan. Terima kasih!
jakebrinkmann
2

Saya benar-benar tidak melihat jawaban yang benar untuk ini di mana pun

class MyClass(dict):
    
    def __init__(self, a_property):
        self[a_property] = a_property

Yang harus Anda lakukan hanyalah mendefinisikan milik Anda sendiri __init__- hanya itu yang ada juga.

Contoh lain (sedikit lebih kompleks):

class MyClass(dict):

    def __init__(self, planet):
        self[planet] = planet
        info = self.do_something_that_returns_a_dict()
        if info:
            for k, v in info.items():
                self[k] = v

    def do_something_that_returns_a_dict(self):
        return {"mercury": "venus", "mars": "jupiter"}

Contoh terakhir ini berguna saat Anda ingin menyematkan beberapa jenis logika.

Pokoknya ... singkatnya class GiveYourClassAName(dict)sudah cukup untuk membuat kelas Anda bertingkah seperti dikt. Operasi dict apa pun yang Anda lakukan selfakan menjadi seperti dict biasa.

Danny Meijer
sumber
1

Ini adalah solusi terbaik saya. Saya menggunakan ini berkali-kali.

class DictLikeClass:
    ...
    def __getitem__(self, key):
        return getattr(self, key)

    def __setitem__(self, key, value):
        setattr(self, key, value)
    ...

Anda bisa menggunakan seperti:

>>> d = DictLikeClass()
>>> d["key"] = "value"
>>> print(d["key"])
madogan
sumber
0

Jangan pernah mewarisi dari diktik bawaan Python! Misalnya updatemetode yang tidak akan digunakan __setitem__, mereka melakukan banyak hal untuk pengoptimalan. Gunakan UserDict.

from collections import UserDict

class MyDict(UserDict):
    def __delitem__(self, key):
        pass
    def __setitem__(self, key, value):
        pass
pengguna2674414
sumber
1
Berdasarkan apa yang seharusnya tidak pernah diwarisi dari dikt yang ada di dalamnya? Dari dokumentasi ( docs.python.org/3/library/collections.html#collections.UserDict ): "Kebutuhan akan kelas ini sebagian telah digantikan oleh kemampuan untuk membuat subkelas langsung dari dict; namun, kelas ini bisa lebih mudah untuk bekerja dengan karena kamus yang mendasari dapat diakses sebagai atribut. " Juga di halaman yang sama: Modul koleksi "Tidak digunakan lagi sejak versi 3.3, akan dihapus di versi 3.9: Koleksi Abstrak Kelas Basis Pindah ke modul collections.abc." ... yang tidak memiliki UserDict.
NumesSanguis
Saya curiga ini terinspirasi oleh, atau setidaknya beresonansi dengan, treyhunner.com/2019/04/…
tripleee