Apakah ada cara subclassing dari dict dan collections.abc.MutableMapping bersama?

26

Sebagai contoh mari kita asumsikan saya ingin subclass dictdan memiliki semua kunci dikapitalisasi:

class capdict(dict):
    def __init__(self,*args,**kwds):
        super().__init__(*args,**kwds)
        mod = [(k.capitalize(),v) for k,v in super().items()]
        super().clear()
        super().update(mod)
    def __getitem__(self,key):
        return super().__getitem__(key.capitalize())
    def __setitem__(self,key,value):
        super().__setitem__(key.capitalize(),value)
    def __delitem__(self,key):
        super().__detitem__(key.capitalize())

Ini berfungsi sampai batas tertentu,

>>> ex = capdict(map(reversed,enumerate("abc")))
>>> ex
{'A': 0, 'B': 1, 'C': 2}
>>> ex['a']
0

tetapi, tentu saja, hanya untuk metode yang saya ingat untuk diterapkan, misalnya

>>> 'a' in ex
False

bukan perilaku yang diinginkan.

Sekarang, cara malas mengisi semua metode yang dapat diturunkan dari yang "inti" akan bercampur collections.abc.MutableMapping. Hanya saja, itu tidak berfungsi di sini. Saya kira karena metode yang dimaksud ( __contains__dalam contoh) sudah disediakan oleh dict.

Apakah ada cara mendapatkan kue dan memakannya? Beberapa sihir membiarkan MutableMappinghanya melihat metode yang telah saya timpa sehingga itu meningkatkan yang lain berdasarkan itu?

Paul Panzer
sumber
Anda mungkin tidak perlu menggunakan MutableMapping. Lihat kamus case-sensitive .
martineau
@martineau terima kasih, seperti yang saya katakan itu hanya sebuah contoh.
Paul Panzer
Anda bisa menggunakannya os._Environ.
Peter Wood

Jawaban:

26

Apa yang dapat Anda lakukan:

Hal ini kemungkinan besar tidak akan bekerja dengan baik (yaitu tidak desain terbersih), tapi Anda bisa mewarisi dari MutableMapping pertama dan kemudian dari dict kedua.

Maka MutableMapping akan menggunakan metode apa pun yang telah Anda terapkan (karena mereka adalah yang pertama dalam rantai pencarian):

>>> class D(MutableMapping, dict):
        def __getitem__(self, key):
            print(f'Intercepted a lookup for {key!r}')
            return dict.__getitem__(self, key)


>>> d = D(x=10, y=20)
>>> d.get('x', 0)
Intercepted a lookup for 'x'
10
>>> d.get('z', 0)
Intercepted a lookup for 'z'
0

Cara yang lebih baik:

Pendekatan terbersih (mudah dipahami dan diuji) adalah dengan hanya mewarisi dari MutableMapping dan kemudian menerapkan metode yang diperlukan menggunakan dict biasa sebagai penyimpanan data dasar (dengan komposisi daripada pewarisan):

>>> class CapitalizingDict(MutableMapping):
        def __init__(self, *args, **kwds):
            self.store = {}
            self.update(*args, **kwds)
        def __getitem__(self, key):
            key = key.capitalize()
            return self.store[key]
        def __setitem__(self, key, value):
            key = key.capitalize()
            self.store[key] = value
        def __delitem__(self, key):
            del self.store[key]
        def __len__(self):
            return len(self.store)
        def __iter__(self):
            return iter(self.store)
        def __repr__(self):
            return repr(self.store)


>>> d = CapitalizingDict(x=10, y=20)
>>> d
{'X': 10, 'Y': 20}
>>> d['x']
10
>>> d.get('x', 0)
10
>>> d.get('z', 0)
0
>>> d['w'] = 30
>>> d['W']
30
Raymond Hettinger
sumber
Terima kasih! Saya bisa bersumpah saya mencoba kedua pesanan ... Tidak menarik, ketika saya menggunakan metode "bisa melakukan" menukar semua supers untuk dicts eksplisit maka tampaknya berfungsi, kecuali lenpengembalian 0. Dari mana datangnya?
Paul Panzer
2
Super () panggilan dari __len _ () __ pergi ke berikutnya dalam MRO tersebut: (D, MutableMapping, dict). Itu adalah metode MutableMappiing .__ len __ () yang selalu mengembalikan 0. Itu tidak dimaksudkan untuk dipanggil secara langsung - itu selalu seharusnya diganti. Itu sebabnya Anda harus menelepon dict.__len__(self)langsung. Dan itulah salah satu alasan mengapa saya mengatakan "ini kemungkinan tidak akan berhasil" ;-)
Raymond Hettinger