Mengakses kunci dict seperti atribut?

303

Saya merasa lebih nyaman untuk mengakses kunci dikt sebagai obj.foopengganti obj['foo'], jadi saya menulis cuplikan ini:

class AttributeDict(dict):
    def __getattr__(self, attr):
        return self[attr]
    def __setattr__(self, attr, value):
        self[attr] = value

Namun, saya berasumsi bahwa pasti ada alasan mengapa Python tidak menyediakan fungsi ini di luar kotak. Apa yang akan menjadi peringatan dan perangkap mengakses kunci dikt dengan cara ini?

Izz ad-Din Ruhulessin
sumber
16
Jika Anda mengakses kunci kode sandi dari perangkat terbatas ukuran tetap di mana-mana, Anda mungkin lebih baik membuat objek yang menyimpannya. collections.namedtuplesangat berguna untuk ini.
6
stackoverflow.com/questions/3031219/… memiliki solusi serupa tetapi melangkah lebih jauh
keflavich
1
Menemukan modul untuk ini di github.com/bcj/AttrDict . Saya tidak tahu bagaimana perbandingannya dengan solusi di sini dan di pertanyaan terkait.
matt wilkie
Saya juga menggunakan peretasan yang serupa, sekarang saya gunakaneasydict.EasyDict
muon
Lebih banyak cara untuk mengakses anggota kamus dengan tanda '.' : stackoverflow.com/questions/2352181/…
Pale Blue Dot

Jawaban:

304

Cara terbaik untuk melakukan ini adalah:

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

Beberapa pro:

  • Ini benar-benar berfungsi!
  • Tidak ada metode kelas kamus yang dibayangi (mis .keys(). Berfungsi dengan baik. Kecuali - tentu saja - Anda memberikan beberapa nilai kepada mereka, lihat di bawah)
  • Atribut dan item selalu sinkron
  • Mencoba mengakses kunci yang tidak ada sebagai atribut dengan benar memunculkan AttributeErrorbukanKeyError

Cons:

  • Metode seperti tidak.keys() akan berfungsi dengan baik jika ditimpa oleh data yang masuk
  • Menyebabkan kebocoran memori di Python <2.7.4 / Python3 <3.2.3
  • Pylint pergi pisang dengan E1123(unexpected-keyword-arg)danE1103(maybe-no-member)
  • Bagi yang belum tahu, itu seperti sihir murni.

Penjelasan singkat tentang cara kerjanya

  • Semua objek python secara internal menyimpan atribut mereka dalam kamus yang diberi nama __dict__.
  • Tidak ada persyaratan bahwa kamus internal __dict__harus "hanya dict biasa", sehingga kami dapat menetapkan subkelas apa pun dict()untuk kamus internal.
  • Dalam kasus kami, kami hanya menetapkan AttrDict()contoh kami instantiating (seperti yang kita lakukan __init__).
  • Dengan memanggil super()'s __init__()metode kami memastikan bahwa itu (sudah) berperilaku persis seperti kamus, karena fungsi yang memanggil semua kamus Instansiasi kode.

Salah satu alasan mengapa Python tidak menyediakan fungsi ini di luar kotak

Seperti yang tercantum dalam daftar "kontra", ini menggabungkan namespace dari kunci yang disimpan (yang mungkin berasal dari data yang sewenang-wenang dan / atau tidak dipercaya!) Dengan namespace atribut metode dict bawaan. Sebagai contoh:

d = AttrDict()
d.update({'items':["jacket", "necktie", "trousers"]})
for k, v in d.items():    # TypeError: 'list' object is not callable
    print "Never reached!"
Kimvais
sumber
1
Menurut Anda apakah kebocoran memori akan terjadi dengan objek sederhana seperti: >>> kelas MyD (objek): ... def init __ (self, d): ... self .__ dict = d
Rafe
Menyebabkan kebocoran bahkan di 2,7
pi.
1
Buat itu <= 2.7.3, karena itulah yang saya gunakan.
pi.
1
Dalam catatan rilis 2.7.4 mereka menyebutkan itu diperbaiki (bukan sebelumnya).
Robert Siemer
1
@viveksinghggits hanya karena Anda mengakses sesuatu melalui ., Anda tidak dapat melanggar aturan bahasa :) Dan saya tidak ingin secara AttrDictotomatis mengkonversi bidang yang mengandung ruang menjadi sesuatu yang berbeda.
Yurik
125

Anda dapat memiliki semua karakter string hukum sebagai bagian dari kunci jika Anda menggunakan notasi array. Sebagai contoh,obj['!#$%^&*()_']

Hery
sumber
1
@Izkata ya. lucunya tentang SE yang biasanya ada 'pertanyaan atas' yaitu. judul, dan 'pertanyaan dasar', mungkin karena SE tidak suka mendengar "judul mengatakan itu semua"; 'peringatan' menjadi yang paling bawah di sini.
n611x007
2
Bukan berarti JavaScript adalah contoh bahasa pemrograman yang sangat baik, tetapi objek di JS mendukung akses atribut dan notasi array, yang memungkinkan kenyamanan untuk kasus umum dan cadangan umum untuk simbol yang bukan nama atribut yang legal.
André Caron
@Izkata Bagaimana ini menjawab pertanyaan. Jawaban ini hanya mengatakan bahwa kunci dapat memiliki nama apa saja.
Melab
4
@Melab Pertanyaannya adalah What would be the caveats and pitfalls of accessing dict keys in this manner?(sebagai atribut), dan jawabannya adalah bahwa sebagian besar karakter yang ditampilkan di sini tidak dapat digunakan.
Izkata
83

Dari pertanyaan SO lainnya ini ada contoh implementasi yang bagus yang menyederhanakan kode Anda yang ada. Bagaimana tentang:

class AttributeDict(dict): 
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__

Jauh lebih ringkas dan tidak meninggalkan ruang untuk ekstra masuk ke __getattr__dan __setattr__fungsi Anda di masa depan.

slacy
sumber
Apakah Anda dapat memanggil AttributeDict.update atau AttributeDict.get menggunakan metode ini?
Dor
13
Anda harus ingat bahwa jika Anda menambahkan atribut baru pada saat runtime mereka tidak ditambahkan ke dict itu sendiri tetapi ke atribut dict . Misalnya d = AttributeDict(foo=1). d.bar = 1atribut bar disimpan di dalam atribut dict tetapi tidak dalam dict itu sendiri. pencetakan dhanya menunjukkan item foo.
P3trus
7
+1 karena berfungsi sejauh yang saya tahu. @GringoSuave, @Izkata, @ P3trus Saya meminta siapa pun yang mengaku gagal menunjukkan contoh yang tidak berfungsi d = AttributeDict(foo=1);d.bar = 1;print d=> {'foo': 1, 'bar': 1}Bekerja untuk saya!
Dave Abrahams
4
@DaveAbrahams Baca pertanyaan lengkap dan lihat jawaban oleh Hery, Ryan, dan TheCommunistDuck. Bukan bertanya tentang bagaimana melakukan ini, tetapi tentang masalah yang mungkin timbul .
Izkata
6
Anda harus memberikan __getattr__metode yang memunculkanAttributeError jika atribut yang diberikan tidak ada, jika tidak hal-hal seperti getattr(obj, attr, default_value)tidak berfungsi (yaitu tidak kembali default_valuejika attrtidak ada pada obj)
jcdude
83

Di mana Saya Menjawab Pertanyaan Yang Ditanyakan

Mengapa Python tidak menawarkannya di luar kotak?

Saya menduga itu ada hubungannya dengan Zen Python : "Seharusnya ada satu - dan lebih disukai hanya satu - cara yang jelas untuk melakukannya." Ini akan menciptakan dua cara yang jelas untuk mengakses nilai dari kamus: obj['key']danobj.key .

Peringatan dan Kesalahan

Ini termasuk kemungkinan kurangnya kejelasan dan kebingungan dalam kode. yaitu, yang berikut ini dapat membingungkan orang lain yang akan mempertahankan kode Anda di kemudian hari, atau bahkan untuk Anda, jika Anda tidak akan kembali ke sana untuk sementara waktu. Lagi, dari Zen : "Keterbacaan penting!"

>>> KEY = 'spam'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1

Jika ddipakai atau KEY didefinisikan atau d[KEY] ditugaskan jauh dari tempat d.spamyang digunakan, itu dapat dengan mudah menyebabkan kebingungan tentang apa yang sedang dilakukan, karena ini bukan idiom yang umum digunakan. Saya tahu itu akan berpotensi membingungkan saya.

Selain itu, jika Anda mengubah nilai KEYsebagai berikut (tetapi tidak mengubah d.spam), Anda sekarang mendapatkan:

>>> KEY = 'foo'
>>> d[KEY] = 1
>>> # Several lines of miscellaneous code here...
... assert d.spam == 1
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AttributeError: 'C' object has no attribute 'spam'

IMO, tidak sepadan dengan usaha.

Barang-barang lainnya

Seperti yang telah dicatat orang lain, Anda dapat menggunakan objek hashable apa saja (bukan hanya string) sebagai kunci dict. Sebagai contoh,

>>> d = {(2, 3): True,}
>>> assert d[(2, 3)] is True
>>> 

legal, tapi

>>> C = type('C', (object,), {(2, 3): True})
>>> d = C()
>>> assert d.(2, 3) is True
  File "<stdin>", line 1
  d.(2, 3)
    ^
SyntaxError: invalid syntax
>>> getattr(d, (2, 3))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: getattr(): attribute name must be string
>>> 

tidak. Ini memberi Anda akses ke seluruh jajaran karakter yang dapat dicetak atau objek hashable lainnya untuk kunci kamus Anda, yang tidak Anda miliki ketika mengakses atribut objek. Hal ini memungkinkan keajaiban seperti metaclass objek yang di-cache, seperti resep dari Python Cookbook (Bab 9) .

Di mana saya Editorialize

Saya lebih suka estetika spam.eggslebih spam['eggs'](saya pikir itu terlihat lebih bersih), dan saya benar-benar mulai mendambakan fungsi ini ketika saya bertemu namedtuple. Tetapi kenyamanan untuk dapat melakukan hal-hal berikut ini.

>>> KEYS = 'spam eggs ham'
>>> VALS = [1, 2, 3]
>>> d = {k: v for k, v in zip(KEYS.split(' '), VALS)}
>>> assert d == {'spam': 1, 'eggs': 2, 'ham': 3}
>>>

Ini adalah contoh sederhana, tetapi saya sering menemukan diri saya menggunakan dicts dalam situasi yang berbeda dari yang saya gunakan obj.key notasi (yaitu, ketika saya perlu membaca prefs dari file XML). Dalam kasus lain, ketika saya tergoda untuk membuat kelas dinamis dan menampar beberapa atribut di atasnya untuk alasan estetika, saya terus menggunakan dikt untuk konsistensi untuk meningkatkan keterbacaan.

Saya yakin OP telah lama menyelesaikan ini untuk kepuasannya, tetapi jika dia masih menginginkan fungsi ini, maka saya sarankan dia mengunduh salah satu paket dari pypi yang menyediakannya:

  • Bunch adalah orang yang aku kenal. Subkelas daridict, jadi Anda memiliki semua fungsi itu.
  • AttrDict juga terlihat seperti itu juga cukup bagus, tapi saya tidak terbiasa dengannya dan belum melihat sumbernya sedetail Bunch .
  • Addict dirawat secara aktif dan menyediakan akses seperti attr dan lainnya.
  • Seperti disebutkan dalam komentar oleh Rotareti, Bunch telah ditinggalkan, tetapi ada garpu aktif yang disebut Munch .

Namun, untuk meningkatkan keterbacaan kodenya, saya sangat menyarankan agar dia tidak mencampur gaya notasinya. Jika dia lebih suka notasi ini maka dia hanya perlu instantiate objek dinamis, tambahkan atribut yang diinginkan untuk itu, dan menyebutnya sehari:

>>> C = type('C', (object,), {})
>>> d = C()
>>> d.spam = 1
>>> d.eggs = 2
>>> d.ham = 3
>>> assert d.__dict__ == {'spam': 1, 'eggs': 2, 'ham': 3}


Di mana Saya Memperbarui, untuk Menjawab Pertanyaan Tindak Lanjut dalam Komentar

Dalam komentar (di bawah), Elmo bertanya:

Bagaimana jika Anda ingin masuk lebih dalam? (mengacu pada tipe (...))

Meskipun saya belum pernah menggunakan use case ini (sekali lagi, saya cenderung menggunakan nested dict, untuk konsistensi), kode berikut berfungsi:

>>> C = type('C', (object,), {})
>>> d = C()
>>> for x in 'spam eggs ham'.split():
...     setattr(d, x, C())
...     i = 1
...     for y in 'one two three'.split():
...         setattr(getattr(d, x), y, i)
...         i += 1
...
>>> assert d.spam.__dict__ == {'one': 1, 'two': 2, 'three': 3}
Doug R.
sumber
1
Bunch sudah tidak digunakan lagi, tetapi ada garpu aktif: github.com/Infinidat/munch
Rotareti
@Rotareti - Terima kasih atas kesempatannya! Ini bukan fungsi yang saya gunakan, jadi saya tidak menyadarinya.
Doug R.
Bagaimana jika Anda ingin masuk lebih dalam? (Mengacu pada tipe (...))
Ole Aldric
6
Python seperti payung terbalik yang menjulang tinggi di tengah hujan lebat. Semuanya tampak cerdas dan funky untuk memulai, setelah beberapa waktu mulai menjadi berat, lalu tiba-tiba, Anda membaca beberapa hal bawaan guru di SE dan semuanya kembali dengan seluruh muatan di pundak Anda. Saat masih basah kuyup, kamu merasa lebih ringan dan semuanya begitu jernih dan segar.
Ole Aldric
19

Anda bisa menarik kelas kontainer yang nyaman dari perpustakaan standar:

from argparse import Namespace

untuk menghindari harus menyalin bit kode. Tidak ada akses kamus standar, tetapi mudah mendapatkannya kembali jika Anda benar-benar menginginkannya. Kode dalam argparse sederhana,

class Namespace(_AttributeHolder):
    """Simple object for storing attributes.

    Implements equality by attribute names and values, and provides a simple
    string representation.
    """

    def __init__(self, **kwargs):
        for name in kwargs:
            setattr(self, name, kwargs[name])

    __hash__ = None

    def __eq__(self, other):
        return vars(self) == vars(other)

    def __ne__(self, other):
        return not (self == other)

    def __contains__(self, key):
        return key in self.__dict__
lindyblackburn
sumber
2
PLUS 1 untuk referensi perpustakaan standar, yang membahas komentar pertama oleh OP.
Gordon Bean
4
Python menyertakan kelas yang lebih cepat (diimplementasikan dalam C) untuk kasus ini: types.SimpleNamespace docs.python.org/dev/library/types.html#types.SimpleNamespace
Nuno André
18

Bagaimana jika Anda menginginkan kunci yang merupakan metode, seperti __eq__atau __getattr__?

Dan Anda tidak akan dapat memiliki entri yang tidak dimulai dengan huruf, jadi gunakan 0343853sebagai kunci keluar.

Dan bagaimana jika Anda tidak ingin menggunakan string?

Bebek Komunis
sumber
Memang, atau misalnya benda lain sebagai kunci. Namun saya akan mengklasifikasikan kesalahan dari itu sebagai 'perilaku yang diharapkan' - dengan pertanyaan saya, saya lebih mengarah pada hal yang tidak terduga.
Izz ad-Din Ruhulessin
pickle.dumpmenggunakan__getstate__
Cees Timmerman
12

tuple dapat digunakan kunci dict. Bagaimana Anda mengakses tuple dalam konstruk Anda?

Juga, namedtuple adalah struktur yang nyaman yang dapat memberikan nilai melalui akses atribut.

Senthil Kumaran
sumber
7
Kekurangan dari namesuple adalah bahwa mereka tidak berubah.
Izz ad-Din Ruhulessin
10
Beberapa orang akan mengatakan bahwa menjadi tidak berubah bukanlah bug tetapi fitur tuple.
ben penulis
9

Bagaimana dengan Prodict , kelas Python kecil yang saya tulis untuk mengatur semuanya :)

Plus, Anda mendapatkan penyelesaian kode otomatis , instantiasi objek rekursif , dan konversi tipe otomatis !

Anda dapat melakukan persis apa yang Anda minta:

p = Prodict()
p.foo = 1
p.bar = "baz"

Contoh 1: Ketik mengisyaratkan

class Country(Prodict):
    name: str
    population: int

turkey = Country()
turkey.name = 'Turkey'
turkey.population = 79814871

kode otomatis selesai

Contoh 2: Konversi tipe otomatis

germany = Country(name='Germany', population='82175700', flag_colors=['black', 'red', 'yellow'])

print(germany.population)  # 82175700
print(type(germany.population))  # <class 'int'>

print(germany.flag_colors)  # ['black', 'red', 'yellow']
print(type(germany.flag_colors))  # <class 'list'>
Ramazan Polat
sumber
2
menginstal di python2 via pip, tetapi tidak bekerja di python2
Ant6n
2
@ Ant6n memerlukan python 3.6+ karena anotasi jenis
Ramazan Polat
8

Itu tidak bekerja secara umum. Tidak semua kunci dict yang valid membuat atribut yang dapat dialamatkan ("kunci"). Jadi, Anda harus berhati-hati.

Objek Python pada dasarnya adalah kamus. Jadi saya ragu ada banyak performa atau penalti lainnya.

tallseth
sumber
8

Ini tidak menjawab pertanyaan awal, tetapi harus bermanfaat bagi orang-orang yang, seperti saya, berakhir di sini ketika mencari lib yang menyediakan fungsi ini.

Kecanduan itu adalah lib yang bagus untuk ini: https://github.com/mewwts/addict menangani banyak masalah yang disebutkan dalam jawaban sebelumnya.

Contoh dari dokumen:

body = {
    'query': {
        'filtered': {
            'query': {
                'match': {'description': 'addictive'}
            },
            'filter': {
                'term': {'created_by': 'Mats'}
            }
        }
    }
}

Dengan pecandu:

from addict import Dict
body = Dict()
body.query.filtered.query.match.description = 'addictive'
body.query.filtered.filter.term.created_by = 'Mats'
gonz
sumber
8

Saya menemukan diri saya bertanya-tanya apa keadaan saat ini "kunci dict sebagai attr" di ekosistem python. Seperti yang ditunjukkan oleh beberapa komentator, ini mungkin bukan sesuatu yang ingin Anda buat sendiri dari awal , karena ada beberapa perangkap dan footgun, beberapa di antaranya sangat halus. Juga, saya tidak akan merekomendasikan menggunakan Namespacesebagai kelas dasar, saya sudah jalan itu, itu tidak cantik.

Untungnya, ada beberapa paket sumber terbuka yang menyediakan fungsi ini, siap untuk dipasang! Sayangnya, ada beberapa paket. Ini sinopsisnya, per Desember 2019.

Peserta (komitmen terkini untuk dikuasai | #commits | #contribs | coverage%):

Tidak lagi dipertahankan atau kurang terawat:

  • treedict (2014-03-28 | 95 | 2 |?%)
  • bunch (2012-03-12 | 20 | 2 |?%)
  • NeoBunch

Saat ini saya merekomendasikan munch atau pecandu . Mereka memiliki komitmen, kontributor, dan rilis terbanyak, menunjukkan basis kode sumber terbuka yang sehat untuk masing-masing. Mereka memiliki readme.md yang tampak bersih, cakupan 100%, dan serangkaian tes yang bagus.

Saya tidak punya anjing dalam perlombaan ini (untuk saat ini!), Selain telah memutar kode dict / attr saya sendiri dan membuang banyak waktu karena saya tidak mengetahui semua opsi ini :). Saya dapat berkontribusi untuk pecandu / mengunyah di masa depan karena saya lebih suka melihat satu paket yang solid daripada sekelompok yang terfragmentasi. Jika Anda menyukainya, berkontribusi! Secara khusus, sepertinya pengunyah dapat menggunakan lencana codecov dan pecandu dapat menggunakan lencana versi python.

pecandu pro:

  • inisialisasi rekursif (foo.abc = 'bar'), argumen seperti dict menjadi kecanduan.Dict

pecandu kontra:

  • membayangi typing.Dictjika Andafrom addict import Dict
  • Tidak ada pemeriksaan kunci. Karena memungkinkan init rekursif, jika Anda salah mengeja kunci, Anda hanya membuat atribut baru, bukan KeyError (terima kasih AljoSt)

pro mengunyah:

  • penamaan yang unik
  • fungsi ser / de bawaan untuk JSON dan YAML

kontra mengunyah:

  • tidak ada init rekursif / hanya dapat init satu attr pada suatu waktu

Di mana saya Editorialize

Beberapa bulan yang lalu, ketika saya menggunakan editor teks untuk menulis python, pada proyek dengan hanya saya atau pengembang lain, saya menyukai gaya dict-attrs, kemampuan untuk memasukkan kunci dengan hanya mendeklarasikan foo.bar.spam = eggs. Sekarang saya bekerja dalam tim, dan menggunakan IDE untuk semuanya, dan saya telah menjauh dari struktur data semacam ini dan pengetikan dinamis secara umum, lebih menyukai analisis statis, teknik fungsional, dan petunjuk tipe. Saya sudah mulai bereksperimen dengan teknik ini, subclassing Pstruct dengan objek-objek desain saya sendiri:

class  BasePstruct(dict):
    def __getattr__(self, name):
        if name in self.__slots__:
            return self[name]
        return self.__getattribute__(name)

    def __setattr__(self, key, value):
        if key in self.__slots__:
            self[key] = value
            return
        if key in type(self).__dict__:
            self[key] = value
            return
        raise AttributeError(
            "type object '{}' has no attribute '{}'".format(type(self).__name__, key))


class FooPstruct(BasePstruct):
    __slots__ = ['foo', 'bar']

Ini memberi Anda objek yang masih berperilaku seperti dict, tetapi juga memungkinkan Anda mengakses kunci seperti atribut, dengan cara yang jauh lebih kaku. Keuntungannya di sini adalah saya (atau konsumen kode Anda yang malang) tahu persis bidang apa yang bisa dan tidak bisa ada, dan IDE bisa mengisi bidang isian secara otomatis. Juga mengelompokkan vanilla dictberarti serialisasi json mudah. Saya pikir evolusi berikutnya dalam ide ini akan menjadi generator protobuf kustom yang memancarkan antarmuka ini, dan ketukan yang bagus adalah Anda mendapatkan struktur data lintas-bahasa dan IPC melalui gRPC untuk hampir gratis.

Jika Anda memutuskan untuk menggunakan attr-dicts, penting untuk mendokumentasikan bidang apa yang diharapkan, untuk kewarasan Anda sendiri (dan rekan tim Anda).

Jangan ragu untuk mengedit / memperbarui posting ini agar tetap terbaru!

DeusXMachina
sumber
2
con besar untuk itu addictadalah tidak akan meningkatkan pengecualian ketika Anda salah mengeja atribut, karena akan mengembalikan yang baru Dict(ini diperlukan untuk foo.abc = 'bar' untuk bekerja).
AljoSt
5

Berikut adalah contoh singkat dari catatan abadi menggunakan built-in collections.namedtuple:

def record(name, d):
    return namedtuple(name, d.keys())(**d)

dan contoh penggunaan:

rec = record('Model', {
    'train_op': train_op,
    'loss': loss,
})

print rec.loss(..)
Ben Usman
sumber
5

Hanya untuk menambahkan beberapa variasi pada jawabannya, sci-kit learn telah menerapkan ini sebagai Bunch:

class Bunch(dict):                                                              
    """ Scikit Learn's container object                                         

    Dictionary-like object that exposes its keys as attributes.                 
    >>> b = Bunch(a=1, b=2)                                                     
    >>> b['b']                                                                  
    2                                                                           
    >>> b.b                                                                     
    2                                                                           
    >>> b.c = 6                                                                 
    >>> b['c']                                                                  
    6                                                                           
    """                                                                         

    def __init__(self, **kwargs):                                               
        super(Bunch, self).__init__(kwargs)                                     

    def __setattr__(self, key, value):                                          
        self[key] = value                                                       

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

    def __getattr__(self, key):                                                 
        try:                                                                    
            return self[key]                                                    
        except KeyError:                                                        
            raise AttributeError(key)                                           

    def __setstate__(self, state):                                              
        pass                       

Yang Anda butuhkan adalah mendapatkan setattrdan getattrmetode - getattrpemeriksaan untuk kunci dict dan langkah-langkah untuk memeriksa atribut yang sebenarnya. Ini setstaetadalah perbaikan untuk perbaikan untuk pengawetan / pembongkaran "tandan" - jika dicabut periksa https://github.com/scikit-learn/scikit-learn/issues/6196

pengguna2804865
sumber
3

Tidak perlu menulis sendiri sebagai setattr () dan getattr () sudah ada.

Keuntungan dari objek kelas mungkin berperan dalam definisi dan pewarisan kelas.

David W
sumber
3

Saya membuat ini berdasarkan masukan dari utas ini. Saya perlu menggunakan odict, jadi saya harus mengganti get dan set attr. Saya pikir ini harus bekerja untuk sebagian besar kegunaan khusus.

Penggunaannya terlihat seperti ini:

# Create an ordered dict normally...
>>> od = OrderedAttrDict()
>>> od["a"] = 1
>>> od["b"] = 2
>>> od
OrderedAttrDict([('a', 1), ('b', 2)])

# Get and set data using attribute access...
>>> od.a
1
>>> od.b = 20
>>> od
OrderedAttrDict([('a', 1), ('b', 20)])

# Setting a NEW attribute only creates it on the instance, not the dict...
>>> od.c = 8
>>> od
OrderedAttrDict([('a', 1), ('b', 20)])
>>> od.c
8

Kelas:

class OrderedAttrDict(odict.OrderedDict):
    """
    Constructs an odict.OrderedDict with attribute access to data.

    Setting a NEW attribute only creates it on the instance, not the dict.
    Setting an attribute that is a key in the data will set the dict data but 
    will not create a new instance attribute
    """
    def __getattr__(self, attr):
        """
        Try to get the data. If attr is not a key, fall-back and get the attr
        """
        if self.has_key(attr):
            return super(OrderedAttrDict, self).__getitem__(attr)
        else:
            return super(OrderedAttrDict, self).__getattr__(attr)


    def __setattr__(self, attr, value):
        """
        Try to set the data. If attr is not a key, fall-back and set the attr
        """
        if self.has_key(attr):
            super(OrderedAttrDict, self).__setitem__(attr, value)
        else:
            super(OrderedAttrDict, self).__setattr__(attr, value)

Ini adalah pola yang cukup keren yang telah disebutkan di utas, tetapi jika Anda hanya ingin mengambil dikt dan mengubahnya menjadi objek yang berfungsi dengan lengkapi-otomatis dalam IDE, dll:

class ObjectFromDict(object):
    def __init__(self, d):
        self.__dict__ = d
Rafe
sumber
3

Rupanya sekarang ada perpustakaan untuk ini - https://pypi.python.org/pypi/attrdict - yang mengimplementasikan fungsionalitas yang tepat ini ditambah penggabungan rekursif dan pemuatan json. Mungkin patut dilihat.

Yurik
sumber
3

Inilah yang saya gunakan

args = {
        'batch_size': 32,
        'workers': 4,
        'train_dir': 'train',
        'val_dir': 'val',
        'lr': 1e-3,
        'momentum': 0.9,
        'weight_decay': 1e-4
    }
args = namedtuple('Args', ' '.join(list(args.keys())))(**args)

print (args.lr)
mujjiga
sumber
Ini adalah jawaban cepat dan kotor yang bagus. Satu-satunya pengamatan / komentar saya adalah bahwa saya pikir constructor namedtuple akan menerima daftar string, sehingga solusi Anda dapat disederhanakan (saya pikir) untuk:namedtuple('Args', list(args.keys()))(**args)
Dan Nguyen
2

Anda dapat melakukannya menggunakan kelas yang baru saja saya buat. Dengan kelas ini Anda dapat menggunakan Mapobjek seperti kamus lain (termasuk serialisasi json) atau dengan notasi titik. Saya harap membantu Anda:

class Map(dict):
    """
    Example:
    m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
    """
    def __init__(self, *args, **kwargs):
        super(Map, self).__init__(*args, **kwargs)
        for arg in args:
            if isinstance(arg, dict):
                for k, v in arg.iteritems():
                    self[k] = v

        if kwargs:
            for k, v in kwargs.iteritems():
                self[k] = v

    def __getattr__(self, attr):
        return self.get(attr)

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

    def __setitem__(self, key, value):
        super(Map, self).__setitem__(key, value)
        self.__dict__.update({key: value})

    def __delattr__(self, item):
        self.__delitem__(item)

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

Contoh penggunaan:

m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
# Add new key
m.new_key = 'Hello world!'
print m.new_key
print m['new_key']
# Update values
m.new_key = 'Yay!'
# Or
m['new_key'] = 'Yay!'
# Delete key
del m.new_key
# Or
del m['new_key']
epool
sumber
1
Perhatikan bahwa itu dapat membayangi dictmetode, misalnya: m=Map(); m["keys"] = 42; m.keys()memberi TypeError: 'int' object is not callable.
bfontaine
@ bfontaine Idenya adalah menjadi semacam field/attributedan bukan a method, tetapi jika Anda menetapkan suatu metode sebagai gantinya, Anda dapat mengakses metode itu m.method().
epool
2

Izinkan saya memposting implementasi lain, yang dibangun berdasarkan jawaban Kinvais, tetapi mengintegrasikan ide-ide dari AttributeDict yang diusulkan di http://databio.org/posts/python_AttributeDict.html .

Keuntungan dari versi ini adalah bahwa ia juga berfungsi untuk kamus bersarang:

class AttrDict(dict):
    """
    A class to convert a nested Dictionary into an object with key-values
    that are accessible using attribute notation (AttrDict.attribute) instead of
    key notation (Dict["key"]). This class recursively sets Dicts to objects,
    allowing you to recurse down nested dicts (like: AttrDict.attr.attr)
    """

    # Inspired by:
    # http://stackoverflow.com/a/14620633/1551810
    # http://databio.org/posts/python_AttributeDict.html

    def __init__(self, iterable, **kwargs):
        super(AttrDict, self).__init__(iterable, **kwargs)
        for key, value in iterable.items():
            if isinstance(value, dict):
                self.__dict__[key] = AttrDict(value)
            else:
                self.__dict__[key] = value
Kadee
sumber
1
class AttrDict(dict):

     def __init__(self):
           self.__dict__ = self

if __name__ == '____main__':

     d = AttrDict()
     d['ray'] = 'hope'
     d.sun = 'shine'  >>> Now we can use this . notation
     print d['ray']
     print d.sun
h_vm
sumber
1

Solusinya adalah:

DICT_RESERVED_KEYS = vars(dict).keys()


class SmartDict(dict):
    """
    A Dict which is accessible via attribute dot notation
    """
    def __init__(self, *args, **kwargs):
        """
        :param args: multiple dicts ({}, {}, ..)
        :param kwargs: arbitrary keys='value'

        If ``keyerror=False`` is passed then not found attributes will
        always return None.
        """
        super(SmartDict, self).__init__()
        self['__keyerror'] = kwargs.pop('keyerror', True)
        [self.update(arg) for arg in args if isinstance(arg, dict)]
        self.update(kwargs)

    def __getattr__(self, attr):
        if attr not in DICT_RESERVED_KEYS:
            if self['__keyerror']:
                return self[attr]
            else:
                return self.get(attr)
        return getattr(self, attr)

    def __setattr__(self, key, value):
        if key in DICT_RESERVED_KEYS:
            raise AttributeError("You cannot set a reserved name as attribute")
        self.__setitem__(key, value)

    def __copy__(self):
        return self.__class__(self)

    def copy(self):
        return self.__copy__()
Bruno Rocha - rochacbruno
sumber
1

Apa yang akan menjadi peringatan dan perangkap mengakses kunci dikt dengan cara ini?

Seperti yang disarankan @Henry, salah satu alasan titik-akses tidak dapat digunakan dalam dikte adalah bahwa ia membatasi nama kunci dict ke variabel python-valid, sehingga membatasi semua nama yang mungkin.

Berikut ini adalah contoh-contoh tentang mengapa titik-akses tidak akan membantu secara umum, mengingat dict, d:

Keabsahan

Atribut berikut tidak valid dalam Python:

d.1_foo                           # enumerated names
d./bar                            # path names
d.21.7, d.12:30                   # decimals, time
d.""                              # empty strings
d.john doe, d.denny's             # spaces, misc punctuation 
d.3 * x                           # expressions  

Gaya

Konvensi PEP8 akan memberikan batasan lunak pada penamaan atribut:

A. Nama kata kunci yang dicadangkan (atau fungsi builtin):

d.in
d.False, d.True
d.max, d.min
d.sum
d.id

Jika nama argumen fungsi bentrok dengan kata kunci yang dipesan, umumnya lebih baik untuk menambahkan satu garis bawah garis bawah ...

B. Aturan kasus tentang metode dan nama variabel :

Nama variabel mengikuti konvensi yang sama dengan nama fungsi.

d.Firstname
d.Country

Gunakan aturan penamaan fungsi: huruf kecil dengan kata-kata yang dipisahkan oleh garis bawah seperlunya untuk meningkatkan keterbacaan.


Terkadang kekhawatiran ini muncul di perpustakaan seperti panda , yang memungkinkan akses bertitik kolom DataFrame dengan nama. Mekanisme default untuk mengatasi batasan penamaan juga notasi array - string dalam tanda kurung.

Jika kendala ini tidak berlaku untuk kasus penggunaan Anda, ada beberapa opsi pada struktur data akses bertitik .

pylang
sumber
1

Anda dapat menggunakan dict_to_obj https://pypi.org/project/dict-to-obj/ Itu tidak persis seperti yang Anda minta

From dict_to_obj import DictToObj
a = {
'foo': True
}
b = DictToObj(a)
b.foo
True
Alon Barad
sumber
1
Ini adalah bentuk yang baik untuk diletakkan .ideadan file yang dibuat khusus pengguna atau IDE di file Anda .gitignore.
DeusXMachina
1

Ini bukan jawaban yang 'baik', tapi saya pikir ini bagus (tidak menangani dicts bersarang dalam bentuk saat ini). Cukup bungkus dict Anda dalam suatu fungsi:

def make_funcdict(d=None, **kwargs)
    def funcdict(d=None, **kwargs):
        if d is not None:
            funcdict.__dict__.update(d)
        funcdict.__dict__.update(kwargs)
        return funcdict.__dict__
    funcdict(d, **kwargs)
    return funcdict

Sekarang Anda memiliki sintaks yang sedikit berbeda. Untuk mengakses item dict seperti atribut lakukan f.key. Untuk mengakses item dict (dan metode dict lainnya) dengan cara yang biasa dilakukan f()['key']dan kami dapat dengan mudah memperbarui dict dengan memanggil f dengan argumen kata kunci dan / atau kamus

Contoh

d = {'name':'Henry', 'age':31}
d = make_funcdict(d)
>>> for key in d():
...     print key
... 
age
name
>>> print d.name
... Henry
>>> print d.age
... 31
>>> d({'Height':'5-11'}, Job='Carpenter')
... {'age': 31, 'name': 'Henry', 'Job': 'Carpenter', 'Height': '5-11'}

Dan itu dia. Saya akan senang jika ada yang menyarankan manfaat dan kelemahan dari metode ini.

DylanYoung
sumber
0

Seperti dicatat oleh Doug ada paket Bunch yang dapat Anda gunakan untuk mencapai obj.keyfungsionalitas. Sebenarnya ada versi yang lebih baru yang disebut

NeoBunch

Ini memiliki fitur hebat yang mengubah dikt Anda menjadi objek NeoBunch melalui fungsi neobunchify -nya . Saya menggunakan banyak template Mako dan mengirimkan data sebagai objek NeoBunch membuatnya jauh lebih mudah dibaca, jadi jika Anda akhirnya menggunakan dict normal dalam program Python Anda tetapi ingin notasi titik dalam template Mako Anda dapat menggunakannya seperti itu:

from mako.template import Template
from neobunch import neobunchify

mako_template = Template(filename='mako.tmpl', strict_undefined=True)
data = {'tmpl_data': [{'key1': 'value1', 'key2': 'value2'}]}
with open('out.txt', 'w') as out_file:
    out_file.write(mako_template.render(**neobunchify(data)))

Dan template Mako dapat terlihat seperti:

% for d in tmpl_data:
Column1     Column2
${d.key1}   ${d.key2}
% endfor
mizu
sumber
Tautan ke NeoBunch adalah 404
DeusXMachina
0

Cara termudah adalah mendefinisikan kelas, sebut saja Namespace. yang menggunakan objek dict .update () pada dict. Kemudian, dikt akan diperlakukan sebagai objek.

class Namespace(object):
    '''
    helps referencing object in a dictionary as dict.key instead of dict['key']
    '''
    def __init__(self, adict):
        self.__dict__.update(adict)



Person = Namespace({'name': 'ahmed',
                     'age': 30}) #--> added for edge_cls


print(Person.name)
Elmahy
sumber