แปลง Python ที่ซ้อนกันเป็น Dict เป็นวัตถุหรือไม่


538

ฉันกำลังค้นหาวิธีที่สวยงามในการรับข้อมูลโดยใช้การเข้าถึงแอตทริบิวต์ใน dict ด้วย dicts และรายการที่ซ้อนกัน (เช่นไวยากรณ์ของวัตถุสไตล์จาวาสคริปต์)

ตัวอย่างเช่น:

>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}

ควรเข้าถึงได้ด้วยวิธีนี้:

>>> x = dict2obj(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
bar

ฉันคิดว่ามันเป็นไปไม่ได้หากไม่มีการเรียกซ้ำ แต่สิ่งที่จะเป็นวิธีที่ดีในการรับสไตล์วัตถุสำหรับ dicts คืออะไร


6
ฉันพยายามทำสิ่งที่คล้ายกันเมื่อเร็ว ๆ นี้ แต่คีย์พจนานุกรมที่เกิดซ้ำ ("จาก" - ซึ่งเป็นคำหลัก Python) ทำให้ฉันไม่สามารถผ่านได้ เพราะทันทีที่คุณลองใช้ "x.from" เพื่อเข้าถึงแอตทริบิวต์นั้นคุณจะได้รับข้อผิดพลาดทางไวยากรณ์
Dawie Strauss

3
นั่นเป็นปัญหาแน่นอน แต่ฉันสามารถละทิ้ง "จาก" เพื่อทำให้ชีวิตง่ายขึ้นในการเข้าถึงการสร้าง dict ขนาดใหญ่ :) การพิมพ์ x ['a'] ['d'] [1] ['foo'] น่ารำคาญจริง ๆ ดังนั้น xad [1] .foo กฎ หากคุณต้องการคุณสามารถเข้าถึงผ่านทาง getattr (x, 'from') หรือใช้ _from เป็นแอตทริบิวต์แทน
Marc

5
from_มากกว่า_fromตามPEP 8
Kos

1
คุณสามารถใช้getattr(x, 'from')แทนการเปลี่ยนชื่อแอตทริบิวต์
George V. Reilly

3
ดูเหมือนว่า "วิธีแก้ปัญหา" ส่วนใหญ่จะใช้งานไม่ได้ (แม้จะเป็นที่ยอมรับ แต่ไม่อนุญาตให้ซ้อนกันd1.b.c) ฉันคิดว่ามันชัดเจนว่าคุณควรใช้บางอย่างจากห้องสมุดเช่นตั้งชื่อจากคอลเลกชันตามคำแนะนำนี้ .. .
Andy Hayden

คำตอบ:


660

อัพเดท:ใน Python 2.6 ขึ้นไปพิจารณาว่าnamedtupleโครงสร้างข้อมูลเหมาะสมกับความต้องการของคุณหรือไม่:

>>> from collections import namedtuple
>>> MyStruct = namedtuple('MyStruct', 'a b d')
>>> s = MyStruct(a=1, b={'c': 2}, d=['hi'])
>>> s
MyStruct(a=1, b={'c': 2}, d=['hi'])
>>> s.a
1
>>> s.b
{'c': 2}
>>> s.c
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyStruct' object has no attribute 'c'
>>> s.d
['hi']

ทางเลือก (เนื้อหาคำตอบดั้งเดิม) คือ:

class Struct:
    def __init__(self, **entries):
        self.__dict__.update(entries)

จากนั้นคุณสามารถใช้:

>>> args = {'a': 1, 'b': 2}
>>> s = Struct(**args)
>>> s
<__main__.Struct instance at 0x01D6A738>
>>> s.a
1
>>> s.b
2

19
เหมือนกันที่นี่ - มีประโยชน์อย่างยิ่งสำหรับการสร้างวัตถุ Python ใหม่จากฐานข้อมูลเชิงเอกสารเช่น MongoDB
mikemaccana

13
ในการรับการพิมพ์ที่น่ารักเพิ่ม: def repr __ (self): return '<% s>'% str ('\ n' .join ('% s:% s:% s'% (k, repr (v)) สำหรับ (k, v ) ในตัวเอง. ict dict .iteritems ()))
six8

15
จะทำงานกับพจนานุกรมที่ซ้อนกันได้หรือไม่ และ dicts มีวัตถุและหรือรายการ ฯลฯ มีการจับใด ๆ ?
Sam Stoelinga

5
@Sam S: มันจะไม่สร้างStructs ที่ซ้อนกันจากพจนานุกรมที่ซ้อนกัน แต่โดยทั่วไปแล้วประเภทของค่าสามารถเป็นอะไรก็ได้ รหัสถูก จำกัด ให้มีความเหมาะสมกับช่องเสียบวัตถุ
Eli Bendersky

51
-1 เนื่องจากก) มันไม่ทำงานสำหรับ dicts ที่ซ้อนกันซึ่งคำถามถามอย่างชัดเจนและ b) ฟังก์ชันการทำงานเดียวกันนี้มีอยู่ในไลบรารีมาตรฐานในargparse.Namespace(ซึ่งได้กำหนดไว้__eq__ด้วย__ne__, __contains__)
Wim

110
class obj(object):
    def __init__(self, d):
        for a, b in d.items():
            if isinstance(b, (list, tuple)):
               setattr(self, a, [obj(x) if isinstance(x, dict) else x for x in b])
            else:
               setattr(self, a, obj(b) if isinstance(b, dict) else b)

>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
>>> x = obj(d)
>>> x.b.c
2
>>> x.d[1].foo
'bar'

7
ดี! ฉันจะแทนที่. items () โดย. ititems () แต่สำหรับหน่วยความจำขนาดเล็ก
Eric O Lebigot

7
หากไม่ใช่ข้อกำหนดของ OP นี่ไม่ใช่ปัญหา - แต่โปรดทราบว่าสิ่งนี้จะไม่ประมวลผลออบเจ็กต์ซ้ำในรายการภายในรายการ
Anon

3
ผมทราบดีว่านี่เป็นคำตอบที่เก่า แต่วันนี้มันจะดีกว่าที่จะใช้ชั้นฐานนามธรรมแทนว่าสายน่าเกลียดif isinstance(b, (list, tuple)):
Wim

4
เคล็ดลับ: ให้objชั้นเรียนรับช่วงต่อจากargparse.Namespaceคุณสมบัติเพิ่มเติมเช่นการแทนค่าสตริงที่อ่านได้
Serrano

2
ขึ้นอยู่กับกรณีการใช้งานโดยปกติเราจะตรวจสอบว่าไม่ใช่ประเภทสตริง แต่เป็นประเภท Sequence
wim

108

น่าแปลกที่ไม่มีใครได้กล่าวถึงพวง ไลบรารีนี้มีขึ้นเพื่อให้การเข้าถึงสไตล์แอ็ตทริบิวต์กับอ็อบเจ็กต์ dict และทำสิ่งที่ OP ต้องการ การสาธิต:

>>> from bunch import bunchify
>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
>>> x = bunchify(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
'bar'

มีห้องสมุด Python 3 ที่https://github.com/Infinidat/munch - เครดิตไปที่codyzu


16
และมีการรองรับ python 3 (ดูเหมือนจะเป็น 2.6-3.4) สาขาของ Bunch ชื่อ Munch: github.com/Infinidat/munch
codyzu

พวงเป็นทางออกที่ดีที่สุดของสิ่งเหล่านี้เพราะมันรองรับการทำให้เป็นอนุกรมหลายประเภทได้ดีและได้รับการบำรุงรักษา เยี่ยมมากขอบคุณ ฉันหวังว่าเวอร์ชั่น python3 นั้นมีชื่อเดียวกันเพราะเหตุใด
โจนาธาน

4
ดังนั้นการเตือนล่วงหน้า Bunch และ Attrdict เช่นกันสำหรับเรื่องนั้นช้ามาก พวกเขาใช้เวลาในการร้องขอของฉันประมาณ 1/3 และ 1/2 ตามลำดับเมื่อพวกเขาเห็นการใช้งานบ่อยครั้งในแอปพลิเคชันของเรา ไม่ใช่สิ่งที่แน่นอนที่จะไม่สนใจ พูดคุยเกี่ยวกับมันstackoverflow.com/a/31569634/364604
JayD3e

ขณะนี้ไม่อนุญาตให้วัตถุเช่นการเข้าถึง dicts มันไม่ให้ผ่านgetattr สิ่งนี้ทำให้วิปัสสนาใน REPLs อัจฉริยะเช่น IPython ทำได้ยาก
GDorn

อยากรู้ว่าสิ่งนี้เปรียบเทียบกับ JSONpath github.com/kennknowles/python-jsonpath-rw อย่างไร
MarkHu

64
x = type('new_dict', (object,), d)

จากนั้นเพิ่มการเรียกซ้ำไปยังสิ่งนี้และคุณทำเสร็จแล้ว

แก้ไขนี่คือวิธีที่ฉันใช้:

>>> d
{'a': 1, 'b': {'c': 2}, 'd': ['hi', {'foo': 'bar'}]}
>>> def obj_dic(d):
    top = type('new', (object,), d)
    seqs = tuple, list, set, frozenset
    for i, j in d.items():
        if isinstance(j, dict):
            setattr(top, i, obj_dic(j))
        elif isinstance(j, seqs):
            setattr(top, i, 
                type(j)(obj_dic(sj) if isinstance(sj, dict) else sj for sj in j))
        else:
            setattr(top, i, j)
    return top

>>> x = obj_dic(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
'bar'

1
ทำไมคุณสร้างวัตถุประเภทและไม่ยกตัวอย่างพวกเขา? นั่นจะไม่สมเหตุสมผลหรือไม่ ฉันหมายความว่าทำไมไม่ทำtop_instance = top()และส่งคืนสิ่งที่คุณกลับมาtop?
แพนเค้ก

6
ดีสำหรับข้อมูล "leaf" แต่ตัวอย่างออกจาก "กิ่งไม้" อย่างสะดวกxและชอบx.bซึ่งกลับมาน่าเกลียด<class '__main__.new'>
MarkHu

46

มีตัวช่วยเรียกเก็บเงินnamedtupleซึ่งสามารถทำสิ่งนี้เพื่อคุณ:

from collections import namedtuple

d_named = namedtuple('Struct', d.keys())(*d.values())

In [7]: d_named
Out[7]: Struct(a=1, b={'c': 2}, d=['hi', {'foo': 'bar'}])

In [8]: d_named.a
Out[8]: 1

28
สิ่งนี้ไม่ได้ตอบคำถามการเรียกซ้ำสำหรับ dicts ที่ซ้อนกัน
สิทธิ์

4
คุณไม่สามารถแก้ไขชื่อ tuples
Max

เราสามารถรับประกันได้หรือไม่ว่าคำสั่งของรายการที่ส่งคืนโดยคีย์ () และค่า () ตรงกันในการสั่งซื้อ ฉันหมายถึงถ้ามันเป็น OrderedDict ใช่ แต่ Dict มาตรฐาน? ฉันรู้ว่า CPython 3.6 ขึ้นไปและ PyPy "เล็ก" dicts จะได้รับคำสั่ง แต่อ้างเอกสาร "ด้านเพื่อรักษาของการดำเนินงานใหม่นี้ถือว่าเป็นรายละเอียดการดำเนินงานและไม่ควรพึ่งพา"
Havok

38
class Struct(object):
    """Comment removed"""
    def __init__(self, data):
        for name, value in data.iteritems():
            setattr(self, name, self._wrap(value))

    def _wrap(self, value):
        if isinstance(value, (tuple, list, set, frozenset)): 
            return type(value)([self._wrap(v) for v in value])
        else:
            return Struct(value) if isinstance(value, dict) else value

สามารถใช้กับโครงสร้างลำดับ / dict / value ของความลึกใด ๆ


4
นี่ควรเป็นคำตอบ มันทำงานได้ดีสำหรับการทำรัง คุณสามารถใช้สิ่งนี้เป็น object_hook สำหรับ json.load () เช่นกัน
010110110101

2
คล้ายกับคำตอบการทำงานของ SilentGhost ตั้งแต่ปี 2009 - สามารถเข้าถึงข้อมูลโหนดโหนดได้ แต่ผู้ปกครอง / กิ่งไม้แสดงเป็นข้อมูลอ้างอิงวัตถุ หากต้องการพิมพ์สวยdef __repr__(self): return '{%s}' % str(', '.join("'%s': %s" % (k, repr(v)) for (k, v) in self.__dict__.iteritems()))
MarkHu

5
ผู้ใช้ Python 3.x: มันเป็นเพียงแค่.items()แทนที่จะเป็น.iteritems()ในบรรทัดที่ 4 (ฟังก์ชั่นถูกเปลี่ยนชื่อ แต่ทำสิ่งเดียวกัน )
neo post modern

ทำงานได้สมบูรณ์แบบสำหรับวัตถุที่ซ้อนกันขอบคุณ! ในฐานะที่เป็นมือใหม่แสดงความคิดเห็นเป็นฉันสำหรับ python3 iteritems () จะต้องเปลี่ยนเป็นรายการ ()
Óscar Andreu

31

จากสิ่งที่ฉันรู้สึกว่าเป็นแง่มุมที่ดีที่สุดของตัวอย่างก่อนหน้านี้นี่คือสิ่งที่ฉันคิด

class Struct:
  '''The recursive class for building and representing objects with.'''
  def __init__(self, obj):
    for k, v in obj.iteritems():
      if isinstance(v, dict):
        setattr(self, k, Struct(v))
      else:
        setattr(self, k, v)
  def __getitem__(self, val):
    return self.__dict__[val]
  def __repr__(self):
    return '{%s}' % str(', '.join('%s : %s' % (k, repr(v)) for
      (k, v) in self.__dict__.iteritems()))

โปรดทราบว่านวกรรมิกสามารถย่อให้สั้นลงได้ def __init__(self, dct): for k, v in dct.iteritems(): setattr(self, k, isinstance(v, dict) and self.__class__(v) or v) ซึ่งจะลบการเรียกที่ชัดเจนไปยังStruct
George V. Reilly

2
ฉันไม่อยากลงคะแนนคำตอบของตัวเอง แต่เมื่อมองย้อนกลับไปฉันได้สังเกตเห็นว่ามันไม่ได้แบ่งเป็นประเภทลำดับ xd [1] .foo ล้มเหลวในกรณีนี้
andyvanee

2
isinstance(v, dict)ตรวจสอบจะดีกว่าที่เป็นisinstance(v, collections.Mapping)เพื่อที่จะสามารถจัดการกับอนาคต Dict เหมือนสิ่ง
เตาแก๊ส

29

หาก dict ของคุณมาจากjson.loads()คุณสามารถเปลี่ยนเป็นวัตถุแทน (แทน dict) ในหนึ่งบรรทัด:

import json
from collections import namedtuple

json.loads(data, object_hook=lambda d: namedtuple('X', d.keys())(*d.values()))

ดูเพิ่มเติมวิธีการแปลง JSON ข้อมูลเป็นวัตถุหลาม


ดูเหมือนว่าจะได้ช้ากว่าปกติ.loadsถ้าคุณมี JSON วัตถุขนาดใหญ่
michaelsnowden

16

หากคุณต้องการเข้าถึงคีย์ dict เป็นวัตถุ (หรือเป็น dict สำหรับคีย์ยาก) ให้ทำซ้ำ ๆ และสามารถอัปเดต dict ดั้งเดิมคุณสามารถทำได้:

class Dictate(object):
    """Object view of a dict, updating the passed in dict when values are set
    or deleted. "Dictate" the contents of a dict...: """

    def __init__(self, d):
        # since __setattr__ is overridden, self.__dict = d doesn't work
        object.__setattr__(self, '_Dictate__dict', d)

    # Dictionary-like access / updates
    def __getitem__(self, name):
        value = self.__dict[name]
        if isinstance(value, dict):  # recursively view sub-dicts as objects
            value = Dictate(value)
        return value

    def __setitem__(self, name, value):
        self.__dict[name] = value
    def __delitem__(self, name):
        del self.__dict[name]

    # Object-like access / updates
    def __getattr__(self, name):
        return self[name]

    def __setattr__(self, name, value):
        self[name] = value
    def __delattr__(self, name):
        del self[name]

    def __repr__(self):
        return "%s(%r)" % (type(self).__name__, self.__dict)
    def __str__(self):
        return str(self.__dict)

ตัวอย่างการใช้งาน:

d = {'a': 'b', 1: 2}
dd = Dictate(d)
assert dd.a == 'b'  # Access like an object
assert dd[1] == 2  # Access like a dict
# Updates affect d
dd.c = 'd'
assert d['c'] == 'd'
del dd.a
del dd[1]
# Inner dicts are mapped
dd.e = {}
dd.e.f = 'g'
assert dd['e'].f == 'g'
assert d == {'c': 'd', 'e': {'f': 'g'}}

13
>>> def dict2obj(d):
        if isinstance(d, list):
            d = [dict2obj(x) for x in d]
        if not isinstance(d, dict):
            return d
        class C(object):
            pass
        o = C()
        for k in d:
            o.__dict__[k] = dict2obj(d[k])
        return o


>>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
>>> x = dict2obj(d)
>>> x.a
1
>>> x.b.c
2
>>> x.d[1].foo
'bar'

12

ในที่สุดฉันก็ลองทั้งAttrDictและBunchห้องสมุดและพบว่ามันช้าเกินไปสำหรับการใช้งานของฉัน หลังจากเพื่อนและฉันดูเข้าไปแล้วเราพบว่าวิธีการหลักในการเขียนไลบรารีเหล่านี้ส่งผลให้ห้องสมุดซ้ำซากผ่านวัตถุที่ซ้อนกันและทำการคัดลอกวัตถุพจนานุกรมไปทั่ว ด้วยความคิดนี้เราจึงทำการเปลี่ยนแปลงที่สำคัญสองประการ 1) เราสร้างคุณลักษณะที่โหลดขี้เกียจ 2) แทนที่จะสร้างสำเนาของวัตถุพจนานุกรมเราสร้างสำเนาของวัตถุพร็อกซี่น้ำหนักเบา นี่คือการดำเนินการขั้นสุดท้าย การเพิ่มประสิทธิภาพของการใช้รหัสนี้ไม่น่าเชื่อ เมื่อใช้ AttrDict หรือ Bunch ห้องสมุดสองแห่งนี้ใช้เวลาเพียง 1/2 และ 1/3 ตามลำดับในการร้องขอของฉัน (what !?) รหัสนี้ลดเวลานั้นแทบจะไม่มีอะไรเลย (อยู่ในช่วง 0.5ms) หลักสูตรนี้ขึ้นอยู่กับความต้องการของคุณ

class DictProxy(object):
    def __init__(self, obj):
        self.obj = obj

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

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

    # you probably also want to proxy important list properties along like
    # items(), iteritems() and __len__

class ListProxy(object):
    def __init__(self, obj):
        self.obj = obj

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

    # you probably also want to proxy important list properties along like
    # __iter__ and __len__

def wrap(value):
    if isinstance(value, dict):
        return DictProxy(value)
    if isinstance(value, (tuple, list)):
        return ListProxy(value)
    return value

ดูการดำเนินงานเดิมที่นี่โดยhttps://stackoverflow.com/users/704327/michael-merickel

สิ่งอื่นที่ควรทราบคือการใช้งานนี้ค่อนข้างง่ายและไม่ใช้วิธีการทั้งหมดที่คุณอาจต้องการ คุณจะต้องเขียนสิ่งเหล่านี้ตามที่ต้องการในวัตถุ DictProxy หรือ ListProxy


9

x.__dict__.update(d) ควรทำดี


ขอบคุณสำหรับคำตอบของคุณ แต่ x คืออะไร? dict หรือวัตถุมาตรฐาน? คุณช่วยบอกฉันทีได้โปรด
Marc

x คือวัตถุของคุณ วัตถุทุกคนมีDict โดยการปรับปรุงdictของวัตถุคุณกำลังปรับปรุง vars สำคัญในนั้น
Alex Rodrigues

คำที่เป็นตัวหนาคือ _ _ dict _ _
Alex Rodrigues

15
สิ่งนี้จะไม่จัดการพจนานุกรมที่ซ้อนกัน
FogleBird

ไม่ใช่วัตถุทุกชิ้นที่มี__dict__: ลองobject().__dict__และคุณจะได้รับAttributeError: 'object' object has no attribute '__dict__'
Will Manley

8

คุณสามารถใช้ประโยชน์จากjsonโมดูลของไลบรารีมาตรฐานด้วยเบ็ดวัตถุที่กำหนดเอง :

import json

class obj(object):
    def __init__(self, dict_):
        self.__dict__.update(dict_)

def dict2obj(d):
    return json.loads(json.dumps(d), object_hook=obj)

ตัวอย่างการใช้งาน:

>>> d = {'a': 1, 'b': {'c': 2}, 'd': ['hi', {'foo': 'bar'}]}
>>> o = dict2obj(d)
>>> o.a
1
>>> o.b.c
2
>>> o.d[0]
u'hi'
>>> o.d[1].foo
u'bar'

และมันไม่ได้เป็นแบบอ่านอย่างเดียวอย่างที่มันเป็นnamedtupleเช่นคุณสามารถเปลี่ยนค่า - ไม่ใช่โครงสร้าง:

>>> o.b.c = 3
>>> o.b.c
3

1
คำตอบที่ดีที่สุดโดยไกล
คอนเนอร์

ฉันชอบความคิดที่จะใช้กลไกการโหลด json สำหรับองค์ประกอบที่ซ้อนกัน แต่เรามี dict อยู่แล้วและฉันไม่ชอบความจริงที่ว่าต้องสร้างสตริงจากมันเพื่อแมปมันเป็นวัตถุ ฉันอยากจะมีทางออกที่สร้างวัตถุโดยตรงจาก dict
Sandro

1
ความต้องการที่จะแปลงสายแรก แต่สวยทั่วไปเป็นหนึ่งสามารถขยายได้ผ่านทางและjson.JSONEncoder object_hook
Sandro

6

สิ่งนี้ควรเริ่มต้นของคุณ:

class dict2obj(object):
    def __init__(self, d):
        self.__dict__['d'] = d

    def __getattr__(self, key):
        value = self.__dict__['d'][key]
        if type(value) == type({}):
            return dict2obj(value)

        return value

d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}

x = dict2obj(d)
print x.a
print x.b.c
print x.d[1].foo

ยังใช้งานไม่ได้กับรายการ คุณจะต้องตัดรายการในรายการผู้ใช้และโอเวอร์โหลด__getitem__เพื่อตัดการตั้งค่า


2
ในการทำให้รายการใช้งานได้ให้ใช้if isinstance(d, list)คำสั่งย่อยจากAnonคำตอบ
Vinay Sajip

6

ฉันรู้ว่ามีคำตอบมากมายอยู่ที่นี่แล้วและฉันมาปาร์ตี้ไม่ทัน แต่วิธีนี้จะวนซ้ำและ 'เข้าแทนที่' แปลงพจนานุกรมเป็นโครงสร้างคล้ายวัตถุ ... ใช้งานได้ใน 3.xx

def dictToObject(d):
    for k,v in d.items():
        if isinstance(v, dict):
            d[k] = dictToObject(v)
    return namedtuple('object', d.keys())(*d.values())

# Dictionary created from JSON file
d = {
    'primaryKey': 'id', 
    'metadata': 
        {
            'rows': 0, 
            'lastID': 0
        }, 
    'columns': 
        {
            'col2': {
                'dataType': 'string', 
                'name': 'addressLine1'
            }, 
            'col1': {
                'datatype': 'string', 
                'name': 'postcode'
            }, 
            'col3': {
                'dataType': 'string', 
                'name': 'addressLine2'
            }, 
            'col0': {
                'datatype': 'integer', 
                'name': 'id'
            }, 
            'col4': {
                'dataType': 'string', 
                'name': 'contactNumber'
            }
        }, 
        'secondaryKeys': {}
}

d1 = dictToObject(d)
d1.columns.col1 # == object(datatype='string', name='postcode')
d1.metadata.rows # == 0

คุณหมายถึง "โครงสร้างเหมือนวัตถุ"?
MoralCode

1
เหมือนวัตถุเพราะหลักการพิมพ์เป็ด สิ่งที่ฉันหมายถึงคือคุณสามารถเข้าถึงคุณสมบัติได้เช่นเดียวกับในชั้นเรียน แต่มันไม่ใช่วัตถุในแง่นั้นมันเป็นสิ่งอันดับ
Aidan Haddon-Wright


4

ให้ฉันอธิบายวิธีแก้ปัญหาที่ฉันเกือบจะใช้เวลาก่อน แต่ก่อนอื่นเหตุผลที่ฉันไม่ได้แสดงโดยความจริงที่ว่ารหัสต่อไปนี้:

d = {'from': 1}
x = dict2obj(d)

print x.from

ให้ข้อผิดพลาดนี้:

  File "test.py", line 20
    print x.from == 1
                ^
SyntaxError: invalid syntax

เนื่องจาก "from" เป็นคำสำคัญ Python จึงมีคีย์พจนานุกรมบางตัวที่คุณไม่อนุญาต


ตอนนี้โซลูชันของฉันอนุญาตให้เข้าถึงรายการพจนานุกรมโดยใช้ชื่อโดยตรง แต่ยังช่วยให้คุณใช้ "ความหมายของพจนานุกรม" นี่คือรหัสพร้อมตัวอย่างการใช้งาน:

class dict2obj(dict):
    def __init__(self, dict_):
        super(dict2obj, self).__init__(dict_)
        for key in self:
            item = self[key]
            if isinstance(item, list):
                for idx, it in enumerate(item):
                    if isinstance(it, dict):
                        item[idx] = dict2obj(it)
            elif isinstance(item, dict):
                self[key] = dict2obj(item)

    def __getattr__(self, key):
        return self[key]

d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}

x = dict2obj(d)

assert x.a == x['a'] == 1
assert x.b.c == x['b']['c'] == 2
assert x.d[1].foo == x['d'][1]['foo'] == "bar"

1
โครงสร้างของคลาส: def init __ (self, ** entry): self .__ dict .update (entry)
Kenneth Reitz

4

คำถาม & คำตอบเก่า แต่ฉันมีอะไรจะพูดมากกว่านี้ ดูเหมือนว่าไม่มีใครพูดถึง dict ซ้ำ นี่คือรหัสของฉัน:

#!/usr/bin/env python

class Object( dict ):
    def __init__( self, data = None ):
        super( Object, self ).__init__()
        if data:
            self.__update( data, {} )

    def __update( self, data, did ):
        dataid = id(data)
        did[ dataid ] = self

        for k in data:
            dkid = id(data[k])
            if did.has_key(dkid):
                self[k] = did[dkid]
            elif isinstance( data[k], Object ):
                self[k] = data[k]
            elif isinstance( data[k], dict ):
                obj = Object()
                obj.__update( data[k], did )
                self[k] = obj
                obj = None
            else:
                self[k] = data[k]

    def __getattr__( self, key ):
        return self.get( key, None )

    def __setattr__( self, key, value ):
        if isinstance(value,dict):
            self[key] = Object( value )
        else:
            self[key] = value

    def update( self, *args ):
        for obj in args:
            for k in obj:
                if isinstance(obj[k],dict):
                    self[k] = Object( obj[k] )
                else:
                    self[k] = obj[k]
        return self

    def merge( self, *args ):
        for obj in args:
            for k in obj:
                if self.has_key(k):
                    if isinstance(self[k],list) and isinstance(obj[k],list):
                        self[k] += obj[k]
                    elif isinstance(self[k],list):
                        self[k].append( obj[k] )
                    elif isinstance(obj[k],list):
                        self[k] = [self[k]] + obj[k]
                    elif isinstance(self[k],Object) and isinstance(obj[k],Object):
                        self[k].merge( obj[k] )
                    elif isinstance(self[k],Object) and isinstance(obj[k],dict):
                        self[k].merge( obj[k] )
                    else:
                        self[k] = [ self[k], obj[k] ]
                else:
                    if isinstance(obj[k],dict):
                        self[k] = Object( obj[k] )
                    else:
                        self[k] = obj[k]
        return self

def test01():
    class UObject( Object ):
        pass
    obj = Object({1:2})
    d = {}
    d.update({
        "a": 1,
        "b": {
            "c": 2,
            "d": [ 3, 4, 5 ],
            "e": [ [6,7], (8,9) ],
            "self": d,
        },
        1: 10,
        "1": 11,
        "obj": obj,
    })
    x = UObject(d)


    assert x.a == x["a"] == 1
    assert x.b.c == x["b"]["c"] == 2
    assert x.b.d[0] == 3
    assert x.b.d[1] == 4
    assert x.b.e[0][0] == 6
    assert x.b.e[1][0] == 8
    assert x[1] == 10
    assert x["1"] == 11
    assert x[1] != x["1"]
    assert id(x) == id(x.b.self.b.self) == id(x.b.self)
    assert x.b.self.a == x.b.self.b.self.a == 1

    x.x = 12
    assert x.x == x["x"] == 12
    x.y = {"a":13,"b":[14,15]}
    assert x.y.a == 13
    assert x.y.b[0] == 14

def test02():
    x = Object({
        "a": {
            "b": 1,
            "c": [ 2, 3 ]
        },
        1: 6,
        2: [ 8, 9 ],
        3: 11,
    })
    y = Object({
        "a": {
            "b": 4,
            "c": [ 5 ]
        },
        1: 7,
        2: 10,
        3: [ 12 , 13 ],
    })
    z = {
        3: 14,
        2: 15,
        "a": {
            "b": 16,
            "c": 17,
        }
    }
    x.merge( y, z )
    assert 2 in x.a.c
    assert 3 in x.a.c
    assert 5 in x.a.c
    assert 1 in x.a.b
    assert 4 in x.a.b
    assert 8 in x[2]
    assert 9 in x[2]
    assert 10 in x[2]
    assert 11 in x[3]
    assert 12 in x[3]
    assert 13 in x[3]
    assert 14 in x[3]
    assert 15 in x[2]
    assert 16 in x.a.b
    assert 17 in x.a.c

if __name__ == '__main__':
    test01()
    test02()

4

ต้องการอัปโหลดกระบวนทัศน์น้อยรุ่นนี้ของฉัน

class Struct(dict):
  def __init__(self,data):
    for key, value in data.items():
      if isinstance(value, dict):
        setattr(self, key, Struct(value))
      else:   
        setattr(self, key, type(value).__init__(value))

      dict.__init__(self,data)

มันรักษาคุณสมบัติสำหรับประเภทที่นำเข้าสู่ชั้นเรียน ข้อกังวลเดียวของฉันคือการเขียนทับวิธีจากภายในพจนานุกรมของคุณ แต่อย่างอื่นดูเหมือนจะมั่นคง!


แม้ว่าจะเป็นความคิดที่ดี แต่ดูเหมือนว่ามันจะไม่เหมาะกับตัวอย่างของ OP แต่จริงๆแล้วมันดูเหมือนจะแก้ไขการส่งผ่านในพจนานุกรม! ในความเป็นจริงdf.aไม่ได้ทำงาน
Andy Hayden

4

มันก็ใช้ได้ดีเช่นกัน

class DObj(object):
    pass

dobj = Dobj()
dobj.__dict__ = {'a': 'aaa', 'b': 'bbb'}

print dobj.a
>>> aaa
print dobj.b
>>> bbb

3

นี่เป็นอีกวิธีในการใช้คำแนะนำดั้งเดิมของ SilentGhost:

def dict2obj(d):
  if isinstance(d, dict):
    n = {}
    for item in d:
      if isinstance(d[item], dict):
        n[item] = dict2obj(d[item])
      elif isinstance(d[item], (list, tuple)):
        n[item] = [dict2obj(elem) for elem in d[item]]
      else:
        n[item] = d[item]
    return type('obj_from_dict', (object,), n)
  else:
    return d

3

ฉันสะดุดในกรณีที่ฉันต้องการแปลงรายการ dicts ซ้ำเป็นรายการของวัตถุดังนั้นตามตัวอย่างของ Roberto ที่นี่สิ่งที่ได้ผลสำหรับฉัน:

def dict2obj(d):
    if isinstance(d, dict):
        n = {}
        for item in d:
            if isinstance(d[item], dict):
                n[item] = dict2obj(d[item])
            elif isinstance(d[item], (list, tuple)):
                n[item] = [dict2obj(elem) for elem in d[item]]
            else:
                n[item] = d[item]
        return type('obj_from_dict', (object,), n)
    elif isinstance(d, (list, tuple,)):
        l = []
        for item in d:
            l.append(dict2obj(item))
        return l
    else:
        return d

โปรดทราบว่า tuple ใด ๆ จะถูกแปลงเป็นรายการเทียบเท่าด้วยเหตุผลที่ชัดเจน

หวังว่านี่จะช่วยให้ใครบางคนได้มากเท่าที่คำตอบของคุณสำหรับฉันพวก


3

แล้วการกำหนดdictให้กับ__dict__วัตถุเปล่าล่ะ?

class Object:
    """If your dict is "flat", this is a simple way to create an object from a dict

    >>> obj = Object()
    >>> obj.__dict__ = d
    >>> d.a
    1
    """
    pass

แน่นอนว่าสิ่งนี้จะล้มเหลวในตัวอย่าง dict ที่ซ้อนกันของคุณเว้นแต่ว่าคุณจะใช้ dict ซ้ำ:

# For a nested dict, you need to recursively update __dict__
def dict2obj(d):
    """Convert a dict to an object

    >>> d = {'a': 1, 'b': {'c': 2}, 'd': ["hi", {'foo': "bar"}]}
    >>> obj = dict2obj(d)
    >>> obj.b.c
    2
    >>> obj.d
    ["hi", {'foo': "bar"}]
    """
    try:
        d = dict(d)
    except (TypeError, ValueError):
        return d
    obj = Object()
    for k, v in d.iteritems():
        obj.__dict__[k] = dict2obj(v)
    return obj

และองค์ประกอบรายการตัวอย่างของคุณอาจหมายถึงการเป็นMappingคู่ของรายการ (คีย์ค่า) เช่นนี้:

>>> d = {'a': 1, 'b': {'c': 2}, 'd': [("hi", {'foo': "bar"})]}
>>> obj = dict2obj(d)
>>> obj.d.hi.foo
"bar"

2

นี่คือการใช้งานอื่น:

class DictObj(object):
    def __init__(self, d):
        self.__dict__ = d

def dict_to_obj(d):
    if isinstance(d, (list, tuple)): return map(dict_to_obj, d)
    elif not isinstance(d, dict): return d
    return DictObj(dict((k, dict_to_obj(v)) for (k,v) in d.iteritems()))

[แก้ไข] บิตที่พลาดไปเกี่ยวกับการจัดการ dicts ภายในรายการไม่ใช่แค่ dicts อื่น ๆ เพิ่มการแก้ไข


ทราบว่าการตั้งDictพจนานุกรมหมายความว่าแหล่งที่มาของการเปลี่ยนแปลงใด ๆ แอตทริบิวต์บนวัตถุที่เกิดขึ้นจะมีผลต่อพจนานุกรมที่สร้างวัตถุและในทางกลับกัน สิ่งนี้อาจนำไปสู่ผลลัพธ์ที่ไม่คาดคิดหากใช้พจนานุกรมกับสิ่งอื่นที่ไม่ใช่การสร้างวัตถุ
Mark Roddy

@ Mark: ที่จริงแล้วพจนานุกรมใหม่กำลังถูกส่งไปยัง DictObj ทุกครั้งแทนที่จะส่งผ่านวัตถุ dict เดียวกันดังนั้นสิ่งนี้จะไม่เกิดขึ้นจริง มันจำเป็นที่จะต้องทำสิ่งนี้เพราะฉันจำเป็นต้องแปลค่าภายในพจนานุกรมด้วยดังนั้นมันจึงเป็นไปไม่ได้ที่จะผ่านวัตถุ dict ดั้งเดิมโดยไม่ต้องทำการผ่าเหล่าตัวเอง
Brian

มีคำตอบค่อนข้างน้อยสำหรับคำตอบนี้คำตอบที่ยอมรับไม่ได้ดูเหมือนว่าจะเป็นแบบเรียกซ้ำหรือจัดการกับรายการ ฉันอ่านพวกเขาทั้งหมดและอันนี้ดูง่ายที่สุดตั้งแต่แรกเห็นฉันทดสอบและใช้งานได้ คำตอบที่ดี
AlwaysTraining

2
class Struct(dict):
    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError(name)

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

    def copy(self):
        return Struct(dict.copy(self))

การใช้งาน:

points = Struct(x=1, y=2)
# Changing
points['x'] = 2
points.y = 1
# Accessing
points['x'], points.x, points.get('x') # 2 2 2
points['y'], points.y, points.get('y') # 1 1 1
# Accessing inexistent keys/attrs 
points['z'] # KeyError: z
points.z # AttributeError: z
# Copying
points_copy = points.copy()
points.x = 2
points_copy.x # 1

2

เกี่ยวกับสิ่งนี้:

from functools import partial
d2o=partial(type, "d2o", ())

สิ่งนี้สามารถใช้ได้ดังนี้:

>>> o=d2o({"a" : 5, "b" : 3})
>>> print o.a
5
>>> print o.b
3

2

ฉันคิดว่า dict ประกอบด้วยตัวเลขสตริงและ dict ก็เพียงพอแล้ว ดังนั้นฉันจึงเพิกเฉยต่อสถานการณ์ที่สิ่งอันดับ tuples รายการและประเภทอื่น ๆ ที่ไม่ปรากฏในมิติสุดท้ายของ dict

เมื่อพิจารณาการสืบทอดรวมกับการเรียกซ้ำมันช่วยแก้ปัญหาการพิมพ์ได้อย่างสะดวกและยังมีวิธีสอบถามข้อมูลสองวิธีวิธีหนึ่งในการแก้ไขข้อมูล

ดูตัวอย่างด้านล่างคำสั่งที่อธิบายข้อมูลบางอย่างเกี่ยวกับนักเรียน:

group=["class1","class2","class3","class4",]
rank=["rank1","rank2","rank3","rank4","rank5",]
data=["name","sex","height","weight","score"]

#build a dict based on the lists above
student_dic=dict([(g,dict([(r,dict([(d,'') for d in data])) for r in rank ]))for g in group])

#this is the solution
class dic2class(dict):
    def __init__(self, dic):
        for key,val in dic.items():
            self.__dict__[key]=self[key]=dic2class(val) if isinstance(val,dict) else val


student_class=dic2class(student_dic)

#one way to edit:
student_class.class1.rank1['sex']='male'
student_class.class1.rank1['name']='Nan Xiang'

#two ways to query:
print student_class.class1.rank1
print student_class.class1['rank1']
print '-'*50
for rank in student_class.class1:
    print getattr(student_class.class1,rank)

ผล:

{'score': '', 'sex': 'male', 'name': 'Nan Xiang', 'weight': '', 'height': ''}
{'score': '', 'sex': 'male', 'name': 'Nan Xiang', 'weight': '', 'height': ''}
--------------------------------------------------
{'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''}
{'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''}
{'score': '', 'sex': 'male', 'name': 'Nan Xiang', 'weight': '', 'height': ''}
{'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''}
{'score': '', 'sex': '', 'name': '', 'weight': '', 'height': ''}

2

โดยทั่วไปแล้วคุณต้องการสะท้อนลำดับชั้น dict ลงในวัตถุของคุณ แต่ไม่แสดงรายการหรือสิ่งอันดับซึ่งโดยทั่วไปจะอยู่ในระดับต่ำสุด ดังนั้นนี่คือวิธีที่ฉันทำ:

class defDictToObject(object):

    def __init__(self, myDict):
        for key, value in myDict.items():
            if type(value) == dict:
                setattr(self, key, defDictToObject(value))
            else:
                setattr(self, key, value)

ดังนั้นเราจึง:

myDict = { 'a': 1,
           'b': { 
              'b1': {'x': 1,
                    'y': 2} },
           'c': ['hi', 'bar'] 
         }

และรับ:

x.b.b1.x 1

x.c ['สวัสดี', 'บาร์']


1

พจนานุกรมของฉันอยู่ในรูปแบบนี้:

addr_bk = {
    'person': [
        {'name': 'Andrew', 'id': 123, 'email': 'andrew@mailserver.com',
         'phone': [{'type': 2, 'number': '633311122'},
                   {'type': 0, 'number': '97788665'}]
        },
        {'name': 'Tom', 'id': 456,
         'phone': [{'type': 0, 'number': '91122334'}]}, 
        {'name': 'Jack', 'id': 7788, 'email': 'jack@gmail.com'}
    ]
}

ที่สามารถเห็นฉันมีพจนานุกรมที่ซ้อนกันและรายการ dicts นี่เป็นเพราะ addr_bk ถูกถอดรหัสจากข้อมูลบัฟเฟอร์โปรโตคอลที่แปลงเป็น python dict โดยใช้ lwpb.codec มีฟิลด์ตัวเลือก (เช่น email => ซึ่งอาจไม่มีคีย์) และฟิลด์ซ้ำ (เช่น phone => แปลงเป็นรายการ dict)

ฉันลองแก้ไขปัญหาที่เสนอทั้งหมดข้างต้น บางคนไม่จัดการพจนานุกรมที่ซ้อนกันได้ดี คนอื่นไม่สามารถพิมพ์รายละเอียดวัตถุได้อย่างง่ายดาย

เฉพาะทางออก dict2obj (dict) โดย Dawie Strauss นั้นทำงานได้ดีที่สุด

ฉันปรับปรุงมันเล็กน้อยเพื่อจัดการเมื่อไม่สามารถหากุญแจ:

# Work the best, with nested dictionaries & lists! :)
# Able to print out all items.
class dict2obj_new(dict):
    def __init__(self, dict_):
        super(dict2obj_new, self).__init__(dict_)
        for key in self:
            item = self[key]
            if isinstance(item, list):
                for idx, it in enumerate(item):
                    if isinstance(it, dict):
                        item[idx] = dict2obj_new(it)
            elif isinstance(item, dict):
                self[key] = dict2obj_new(item)

    def __getattr__(self, key):
        # Enhanced to handle key not found.
        if self.has_key(key):
            return self[key]
        else:
            return None

จากนั้นฉันทดสอบด้วย:

# Testing...
ab = dict2obj_new(addr_bk)

for person in ab.person:
  print "Person ID:", person.id
  print "  Name:", person.name
  # Check if optional field is available before printing.
  if person.email:
    print "  E-mail address:", person.email

  # Check if optional field is available before printing.
  if person.phone:
    for phone_number in person.phone:
      if phone_number.type == codec.enums.PhoneType.MOBILE:
        print "  Mobile phone #:",
      elif phone_number.type == codec.enums.PhoneType.HOME:
        print "  Home phone #:",
      else:
        print "  Work phone #:",
      print phone_number.number

1

สร้างคำตอบของฉันให้เป็น " python: วิธีเพิ่มคุณสมบัติให้คลาสแบบไดนามิกได้อย่างไร ":

class data(object):
    def __init__(self,*args,**argd):
        self.__dict__.update(dict(*args,**argd))

def makedata(d):
    d2 = {}
    for n in d:
        d2[n] = trydata(d[n])
    return data(d2)

def trydata(o):
    if isinstance(o,dict):
        return makedata(o)
    elif isinstance(o,list):
        return [trydata(i) for i in o]
    else:
        return o

คุณโทรหาmakedataพจนานุกรมที่คุณต้องการแปลงหรืออาจtrydataขึ้นอยู่กับสิ่งที่คุณคาดหวังว่าเป็นอินพุตและมันจะแยกวัตถุข้อมูลออก

หมายเหตุ:

  • คุณสามารถเพิ่ม elifs trydataหากคุณต้องการฟังก์ชั่นเพิ่มเติม
  • เห็นได้ชัดว่าสิ่งนี้จะไม่ทำงานหากคุณต้องการx.a = {}หรือคล้ายกัน
  • หากคุณต้องการเป็นรุ่นที่อ่านได้อย่างเดียวใช้ข้อมูลระดับจากคำตอบเดิม
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.