Làm thế nào để thêm một phần tử vào đầu một OrderDict?


93

Tôi có cái này:

d1 = OrderedDict([('a', '1'), ('b', '2')])

Nếu tôi làm điều này:

d1.update({'c':'3'})

Sau đó, tôi nhận được điều này:

OrderedDict([('a', '1'), ('b', '2'), ('c', '3')])

nhưng tôi muốn điều này:

[('c', '3'), ('a', '1'), ('b', '2')]

mà không cần tạo từ điển mới.


3
tôi nghĩ rằng bạn nên thiết kế lại programm của bạn
Dmitry Zagorulkin

2
"An OrderedDict là một câu lệnh ghi nhớ thứ tự các phím được đưa vào lần đầu tiên." @ZagorulkinDmitry nói đúng :) ( docs.python.org/2/library/… )
Markon

Câu trả lời:


86

Không có phương thức tích hợp nào để thực hiện việc này trong Python 2. Nếu bạn cần điều này, bạn cần viết một prepend()phương thức / hàm hoạt động trên OrderedDictnội bộ với độ phức tạp O (1).

Đối với Python 3.2 trở lên, bạn nên sử dụng move_to_endphương pháp này. Phương thức chấp nhận một lastđối số cho biết liệu phần tử sẽ được chuyển đến dưới cùng ( last=True) hay đầu ( last=False) của OrderedDict.

Cuối cùng, nếu bạn muốn một giải pháp nhanh, bẩn và chậm , bạn có thể tạo một giải pháp mới OrderedDicttừ đầu.

Chi tiết về bốn giải pháp khác nhau:


Mở rộng OrderedDictvà thêm một phương thức phiên bản mới

from collections import OrderedDict

class MyOrderedDict(OrderedDict):

    def prepend(self, key, value, dict_setitem=dict.__setitem__):

        root = self._OrderedDict__root
        first = root[1]

        if key in self:
            link = self._OrderedDict__map[key]
            link_prev, link_next, _ = link
            link_prev[1] = link_next
            link_next[0] = link_prev
            link[0] = root
            link[1] = first
            root[1] = first[0] = link
        else:
            root[1] = first[0] = self._OrderedDict__map[key] = [root, first, key]
            dict_setitem(self, key, value)

Bản giới thiệu:

>>> d = MyOrderedDict([('a', '1'), ('b', '2')])
>>> d
MyOrderedDict([('a', '1'), ('b', '2')])
>>> d.prepend('c', 100)
>>> d
MyOrderedDict([('c', 100), ('a', '1'), ('b', '2')])
>>> d.prepend('a', d['a'])
>>> d
MyOrderedDict([('a', '1'), ('c', 100), ('b', '2')])
>>> d.prepend('d', 200)
>>> d
MyOrderedDict([('d', 200), ('a', '1'), ('c', 100), ('b', '2')])

Chức năng độc lập điều khiển OrderedDictcác đối tượng

Hàm này thực hiện điều tương tự bằng cách chấp nhận đối tượng dict, khóa và giá trị. Cá nhân tôi thích lớp học hơn:

from collections import OrderedDict

def ordered_dict_prepend(dct, key, value, dict_setitem=dict.__setitem__):
    root = dct._OrderedDict__root
    first = root[1]

    if key in dct:
        link = dct._OrderedDict__map[key]
        link_prev, link_next, _ = link
        link_prev[1] = link_next
        link_next[0] = link_prev
        link[0] = root
        link[1] = first
        root[1] = first[0] = link
    else:
        root[1] = first[0] = dct._OrderedDict__map[key] = [root, first, key]
        dict_setitem(dct, key, value)

Bản giới thiệu:

>>> d = OrderedDict([('a', '1'), ('b', '2')])
>>> ordered_dict_prepend(d, 'c', 100)
>>> d
OrderedDict([('c', 100), ('a', '1'), ('b', '2')])
>>> ordered_dict_prepend(d, 'a', d['a'])
>>> d
OrderedDict([('a', '1'), ('c', 100), ('b', '2')])
>>> ordered_dict_prepend(d, 'd', 500)
>>> d
OrderedDict([('d', 500), ('a', '1'), ('c', 100), ('b', '2')])

Sử dụng OrderedDict.move_to_end()(Python> = 3.2)

Python 3.2 giới thiệu các OrderedDict.move_to_end()phương pháp. Sử dụng nó, chúng ta có thể di chuyển một khóa hiện có đến một trong hai đầu của từ điển trong thời gian O (1).

>>> d1 = OrderedDict([('a', '1'), ('b', '2')])
>>> d1.update({'c':'3'})
>>> d1.move_to_end('c', last=False)
>>> d1
OrderedDict([('c', '3'), ('a', '1'), ('b', '2')])

Nếu chúng ta cần chèn một phần tử và di chuyển nó lên trên cùng, tất cả chỉ trong một bước, chúng ta có thể trực tiếp sử dụng nó để tạo một prepend()trình bao bọc (không được trình bày ở đây).


Tạo mới OrderedDict- chậm !!!

Nếu bạn không muốn làm điều đó và hiệu suất không phải là vấn đề thì cách dễ nhất là tạo một mệnh đề mới:

from itertools import chain, ifilterfalse
from collections import OrderedDict


def unique_everseen(iterable, key=None):
    "List unique elements, preserving order. Remember all elements ever seen."
    # unique_everseen('AAAABBBCCDAABBB') --> A B C D
    # unique_everseen('ABBCcAD', str.lower) --> A B C D
    seen = set()
    seen_add = seen.add
    if key is None:
        for element in ifilterfalse(seen.__contains__, iterable):
            seen_add(element)
            yield element
    else:
        for element in iterable:
            k = key(element)
            if k not in seen:
                seen_add(k)
                yield element

d1 = OrderedDict([('a', '1'), ('b', '2'),('c', 4)])
d2 = OrderedDict([('c', 3), ('e', 5)])   #dict containing items to be added at the front
new_dic = OrderedDict((k, d2.get(k, d1.get(k))) for k in \
                                           unique_everseen(chain(d2, d1)))
print new_dic

đầu ra:

OrderedDict([('c', 3), ('e', 5), ('a', '1'), ('b', '2')])


Lưu ý nếu cđã tồn tại, điều này sẽ không cập nhật giá trị cũ
jamylak

1
@IARI Nếu bạn đang đề cập đến move_to_endthì không có thẻ Python 3 nào trong câu hỏi, move_to_endchỉ hoạt động trong Python 3.2+. Tôi sẽ cập nhật câu trả lời của mình để bao gồm giải pháp dựa trên Python 3. Rất cảm ơn vì đã cập nhật!
Ashwini Chaudhary

1
@AshwiniChaudhary Vì Python 3 đã được thêm vào move_to_front, có lẽ tốt hơn nên triển khai một move_to_frontphương thức thay vì một prependphương thức riêng biệt ? Điều này sẽ làm cho mã của bạn dễ di chuyển hơn nếu bạn cần hỗ trợ cả Python 2 và Python 3 từ cùng một cơ sở mã.
m000

1
Lý do đằng sau dict_setitem=dict.__setitem__như một tham số là prependgì? Tại sao một người sẽ / nên vượt qua một bộ định tuyến khác?
Jir

2
Phải có một lỗiordered_dict_prependtrên. Gọi ordered_dict_prepend(d, 'c', 100)hai lần và cố gắng in ra lệnh kết quả (bằng cách chỉ cần nhập vào dbảng điều khiển của Python), kết quả là quá trình Python tiếp tục chiếm bộ nhớ. Thử nghiệm với Python 2.7.10
Piotr Dobrogost

15

EDIT (2019-02-03) Lưu ý rằng câu trả lời sau chỉ hoạt động trên các phiên bản Python cũ hơn. Gần đây hơn, OrderedDictđã được viết lại bằng C. Ngoài ra, điều này không chạm vào các thuộc tính gạch dưới kép mà bạn không thích.

Tôi vừa viết một lớp con OrderedDicttrong một dự án của tôi cho một mục đích tương tự. Đây là ý chính .

Các hoạt động chèn cũng là thời gian không đổi O(1)(chúng không yêu cầu bạn xây dựng lại cấu trúc dữ liệu), không giống như hầu hết các giải pháp này.

>>> d1 = ListDict([('a', '1'), ('b', '2')])
>>> d1.insert_before('a', ('c', 3))
>>> d1
ListDict([('c', 3), ('a', '1'), ('b', '2')])

Tôi nhận được TypeError: '_Link' object does not support indexingkhi sử dụng điều này trên Python 3.4.
RickardSjogren,

1
Nó làm việc cũng doesnt với python 3.5: AttributeError: đối tượng 'ListDict' không có thuộc tính '_OrderedDict__map'
maggie

2
Điều này không còn hoạt động nữa vì OrderedDictđã được viết lại bằng C kể từ Python 3.5 và lớp con này đã phạm phải điều cấm kỵ đối với nội bộ (thực sự đảo ngược tên mangling để truy cập thuộc tính __).
Nhà giả kim hai bit vào

13

Bạn phải tạo một phiên bản mới của OrderedDict. Nếu khóa của bạn là duy nhất:

d1=OrderedDict([("a",1),("b",2)])
d2=OrderedDict([("c",3),("d",99)])
both=OrderedDict(list(d2.items()) + list(d1.items()))
print(both)

#OrderedDict([('c', 3), ('d', 99), ('a', 1), ('b', 2)])

Nhưng nếu không, hãy cẩn thận vì hành vi này có thể có hoặc có thể không mong muốn đối với bạn:

d1=OrderedDict([("a",1),("b",2)])
d2=OrderedDict([("c",3),("b",99)])
both=OrderedDict(list(d2.items()) + list(d1.items()))
print(both)

#OrderedDict([('c', 3), ('b', 2), ('a', 1)])

3
Trong python3, phương thức items không còn trả về danh sách mà là một dạng xem, hoạt động giống như một tập hợp. Trong trường hợp này, bạn sẽ cần phải sử dụng liên hợp đã đặt vì việc nối với + sẽ không hoạt động: dict (x.items () | y.items ())
The Demz 14/11/13

1
@TheDemz Tôi nghĩ rằng set union sẽ không bảo toàn thứ tự, do đó làm cho thứ tự cuối cùng của các mặt hàng OrderedDictkhông ổn định?
tối đa

@max Có nó không ổn định. Các đối tượng được trả về từ dict.keys (), dict.values ​​() và dict.items () được gọi là dạng xem từ điển. Chúng là chuỗi lười biếng sẽ thấy các thay đổi trong từ điển bên dưới. Để buộc chế độ xem từ điển trở thành một danh sách sử dụng danh sách đầy đủ (dictview). Xem Đối tượng xem từ điển. docs.python.org/3.4/library/...
Các Demz

6

Nếu bạn biết bạn sẽ muốn một khóa 'c', nhưng không biết giá trị, hãy chèn 'c' với một giá trị giả khi bạn tạo dict.

d1 = OrderedDict([('c', None), ('a', '1'), ('b', '2')])

và thay đổi giá trị sau đó.

d1['c'] = 3

Có cách nào để chèn vào một lệnh có thứ tự để các phần tử vẫn được sắp xếp (tăng / giảm).
Eswar

Một mệnh lệnh có thứ tự không được sắp xếp theo bất kỳ thuộc tính nào của các mục. Nó được sắp xếp theo thứ tự chèn, không liên quan gì đến bản thân các mặt hàng. Trong CPthon 3.6 và Python 3.7, tất cả các phần đều được sắp xếp theo thứ tự như vậy và có rất ít lý do để sử dụng OrderDict.
Terry Jan Reedy


3

FWIW Đây là một đoạn mã nhanh-n-dơ mà tôi đã viết để chèn vào một vị trí chỉ mục tùy ý. Không nhất thiết phải hiệu quả nhưng nó hoạt động tại chỗ.

class OrderedDictInsert(OrderedDict):
    def insert(self, index, key, value):
        self[key] = value
        for ii, k in enumerate(list(self.keys())):
            if ii >= index and k != key:
                self.move_to_end(k)

3

Bạn có thể muốn sử dụng một cấu trúc khác hoàn toàn, nhưng có nhiều cách để làm điều đó trong python 2.7 .

d1 = OrderedDict([('a', '1'), ('b', '2')])
d2 = OrderedDict(c='3')
d2.update(d1)

d2 sau đó sẽ chứa

>>> d2
OrderedDict([('c', '3'), ('a', '1'), ('b', '2')])

Như những người khác đã đề cập, trong python 3.2, bạn có thể sử dụng OrderedDict.move_to_end('c', last=False)để di chuyển một khóa nhất định sau khi chèn.

Lưu ý: Hãy lưu ý rằng tùy chọn đầu tiên chậm hơn đối với các bộ dữ liệu lớn do việc tạo một OrderedDict mới và sao chép các giá trị cũ.


2

Nếu bạn cần chức năng không có ở đó, chỉ cần mở rộng lớp với bất cứ thứ gì bạn muốn:

from collections import OrderedDict

class OrderedDictWithPrepend(OrderedDict):
    def prepend(self, other):
        ins = []
        if hasattr(other, 'viewitems'):
            other = other.viewitems()
        for key, val in other:
            if key in self:
                self[key] = val
            else:
                ins.append((key, val))
        if ins:
            items = self.items()
            self.clear()
            self.update(ins)
            self.update(items)

Không hiệu quả khủng khiếp, nhưng hoạt động:

o = OrderedDictWithPrepend()

o['a'] = 1
o['b'] = 2
print o
# OrderedDictWithPrepend([('a', 1), ('b', 2)])

o.prepend({'c': 3})
print o
# OrderedDictWithPrepend([('c', 3), ('a', 1), ('b', 2)])

o.prepend([('a',11),('d',55),('e',66)])
print o
# OrderedDictWithPrepend([('d', 55), ('e', 66), ('c', 3), ('a', 11), ('b', 2)])

1

Tôi sẽ đề xuất thêm một prepend()phương thức vào công thức ActiveState Python thuần túy này hoặc lấy một lớp con từ nó. Mã để làm như vậy có thể khá hiệu quả vì cấu trúc dữ liệu cơ bản để đặt hàng là một danh sách liên kết.

Cập nhật

Để chứng minh cách tiếp cận này là khả thi, dưới đây là mã thực hiện những gì được đề xuất. Như một phần thưởng, tôi cũng đã thực hiện một số thay đổi nhỏ bổ sung để hoạt động trong cả Python 2.7.15 và 3.7.1.

Một prepend()phương thức đã được thêm vào lớp trong công thức và đã được triển khai theo một phương thức khác đã được thêm tên move_to_end(), đã được thêm vào OrderedDicttrong Python 3.2.

prepend()cũng có thể được triển khai trực tiếp, gần như chính xác như được hiển thị ở đầu câu trả lời của @Ashwini Chaudhary —và làm như vậy có thể dẫn đến việc nó nhanh hơn một chút, nhưng đó chỉ là một bài tập cho người đọc có động lực ...

# Ordered Dictionary for Py2.4 from https://code.activestate.com/recipes/576693

# Backport of OrderedDict() class that runs on Python 2.4, 2.5, 2.6, 2.7 and pypy.
# Passes Python2.7's test suite and incorporates all the latest updates.

try:
    from thread import get_ident as _get_ident
except ImportError:  # Python 3
#    from dummy_thread import get_ident as _get_ident
    from _thread import get_ident as _get_ident  # Changed - martineau

try:
    from _abcoll import KeysView, ValuesView, ItemsView
except ImportError:
    pass

class MyOrderedDict(dict):
    'Dictionary that remembers insertion order'
    # An inherited dict maps keys to values.
    # The inherited dict provides __getitem__, __len__, __contains__, and get.
    # The remaining methods are order-aware.
    # Big-O running times for all methods are the same as for regular dictionaries.

    # The internal self.__map dictionary maps keys to links in a doubly linked list.
    # The circular doubly linked list starts and ends with a sentinel element.
    # The sentinel element never gets deleted (this simplifies the algorithm).
    # Each link is stored as a list of length three:  [PREV, NEXT, KEY].

    def __init__(self, *args, **kwds):
        '''Initialize an ordered dictionary.  Signature is the same as for
        regular dictionaries, but keyword arguments are not recommended
        because their insertion order is arbitrary.

        '''
        if len(args) > 1:
            raise TypeError('expected at most 1 arguments, got %d' % len(args))
        try:
            self.__root
        except AttributeError:
            self.__root = root = []  # sentinel node
            root[:] = [root, root, None]
            self.__map = {}
        self.__update(*args, **kwds)

    def prepend(self, key, value):  # Added to recipe.
        self.update({key: value})
        self.move_to_end(key, last=False)

    #### Derived from cpython 3.2 source code.
    def move_to_end(self, key, last=True):  # Added to recipe.
        '''Move an existing element to the end (or beginning if last==False).

        Raises KeyError if the element does not exist.
        When last=True, acts like a fast version of self[key]=self.pop(key).
        '''
        PREV, NEXT, KEY = 0, 1, 2

        link = self.__map[key]
        link_prev = link[PREV]
        link_next = link[NEXT]
        link_prev[NEXT] = link_next
        link_next[PREV] = link_prev
        root = self.__root

        if last:
            last = root[PREV]
            link[PREV] = last
            link[NEXT] = root
            last[NEXT] = root[PREV] = link
        else:
            first = root[NEXT]
            link[PREV] = root
            link[NEXT] = first
            root[NEXT] = first[PREV] = link
    ####

    def __setitem__(self, key, value, dict_setitem=dict.__setitem__):
        'od.__setitem__(i, y) <==> od[i]=y'
        # Setting a new item creates a new link which goes at the end of the linked
        # list, and the inherited dictionary is updated with the new key/value pair.
        if key not in self:
            root = self.__root
            last = root[0]
            last[1] = root[0] = self.__map[key] = [last, root, key]
        dict_setitem(self, key, value)

    def __delitem__(self, key, dict_delitem=dict.__delitem__):
        'od.__delitem__(y) <==> del od[y]'
        # Deleting an existing item uses self.__map to find the link which is
        # then removed by updating the links in the predecessor and successor nodes.
        dict_delitem(self, key)
        link_prev, link_next, key = self.__map.pop(key)
        link_prev[1] = link_next
        link_next[0] = link_prev

    def __iter__(self):
        'od.__iter__() <==> iter(od)'
        root = self.__root
        curr = root[1]
        while curr is not root:
            yield curr[2]
            curr = curr[1]

    def __reversed__(self):
        'od.__reversed__() <==> reversed(od)'
        root = self.__root
        curr = root[0]
        while curr is not root:
            yield curr[2]
            curr = curr[0]

    def clear(self):
        'od.clear() -> None.  Remove all items from od.'
        try:
            for node in self.__map.itervalues():
                del node[:]
            root = self.__root
            root[:] = [root, root, None]
            self.__map.clear()
        except AttributeError:
            pass
        dict.clear(self)

    def popitem(self, last=True):
        '''od.popitem() -> (k, v), return and remove a (key, value) pair.
        Pairs are returned in LIFO order if last is true or FIFO order if false.

        '''
        if not self:
            raise KeyError('dictionary is empty')
        root = self.__root
        if last:
            link = root[0]
            link_prev = link[0]
            link_prev[1] = root
            root[0] = link_prev
        else:
            link = root[1]
            link_next = link[1]
            root[1] = link_next
            link_next[0] = root
        key = link[2]
        del self.__map[key]
        value = dict.pop(self, key)
        return key, value

    # -- the following methods do not depend on the internal structure --

    def keys(self):
        'od.keys() -> list of keys in od'
        return list(self)

    def values(self):
        'od.values() -> list of values in od'
        return [self[key] for key in self]

    def items(self):
        'od.items() -> list of (key, value) pairs in od'
        return [(key, self[key]) for key in self]

    def iterkeys(self):
        'od.iterkeys() -> an iterator over the keys in od'
        return iter(self)

    def itervalues(self):
        'od.itervalues -> an iterator over the values in od'
        for k in self:
            yield self[k]

    def iteritems(self):
        'od.iteritems -> an iterator over the (key, value) items in od'
        for k in self:
            yield (k, self[k])

    def update(*args, **kwds):
        '''od.update(E, **F) -> None.  Update od from dict/iterable E and F.

        If E is a dict instance, does:           for k in E: od[k] = E[k]
        If E has a .keys() method, does:         for k in E.keys(): od[k] = E[k]
        Or if E is an iterable of items, does:   for k, v in E: od[k] = v
        In either case, this is followed by:     for k, v in F.items(): od[k] = v

        '''
        if len(args) > 2:
            raise TypeError('update() takes at most 2 positional '
                            'arguments (%d given)' % (len(args),))
        elif not args:
            raise TypeError('update() takes at least 1 argument (0 given)')
        self = args[0]
        # Make progressively weaker assumptions about "other"
        other = ()
        if len(args) == 2:
            other = args[1]
        if isinstance(other, dict):
            for key in other:
                self[key] = other[key]
        elif hasattr(other, 'keys'):
            for key in other.keys():
                self[key] = other[key]
        else:
            for key, value in other:
                self[key] = value
        for key, value in kwds.items():
            self[key] = value

    __update = update  # let subclasses override update without breaking __init__

    __marker = object()

    def pop(self, key, default=__marker):
        '''od.pop(k[,d]) -> v, remove specified key and return the corresponding value.
        If key is not found, d is returned if given, otherwise KeyError is raised.

        '''
        if key in self:
            result = self[key]
            del self[key]
            return result
        if default is self.__marker:
            raise KeyError(key)
        return default

    def setdefault(self, key, default=None):
        'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
        if key in self:
            return self[key]
        self[key] = default
        return default

    def __repr__(self, _repr_running={}):
        'od.__repr__() <==> repr(od)'
        call_key = id(self), _get_ident()
        if call_key in _repr_running:
            return '...'
        _repr_running[call_key] = 1
        try:
            if not self:
                return '%s()' % (self.__class__.__name__,)
            return '%s(%r)' % (self.__class__.__name__, self.items())
        finally:
            del _repr_running[call_key]

    def __reduce__(self):
        'Return state information for pickling'
        items = [[k, self[k]] for k in self]
        inst_dict = vars(self).copy()
        for k in vars(MyOrderedDict()):
            inst_dict.pop(k, None)
        if inst_dict:
            return (self.__class__, (items,), inst_dict)
        return self.__class__, (items,)

    def copy(self):
        'od.copy() -> a shallow copy of od'
        return self.__class__(self)

    @classmethod
    def fromkeys(cls, iterable, value=None):
        '''OD.fromkeys(S[, v]) -> New ordered dictionary with keys from S
        and values equal to v (which defaults to None).

        '''
        d = cls()
        for key in iterable:
            d[key] = value
        return d

    def __eq__(self, other):
        '''od.__eq__(y) <==> od==y.  Comparison to another OD is order-sensitive
        while comparison to a regular mapping is order-insensitive.

        '''
        if isinstance(other, MyOrderedDict):
            return len(self)==len(other) and self.items() == other.items()
        return dict.__eq__(self, other)

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

    # -- the following methods are only used in Python 2.7 --

    def viewkeys(self):
        "od.viewkeys() -> a set-like object providing a view on od's keys"
        return KeysView(self)

    def viewvalues(self):
        "od.viewvalues() -> an object providing a view on od's values"
        return ValuesView(self)

    def viewitems(self):
        "od.viewitems() -> a set-like object providing a view on od's items"
        return ItemsView(self)

if __name__ == '__main__':

    d1 = MyOrderedDict([('a', '1'), ('b', '2')])
    d1.update({'c':'3'})
    print(d1)  # -> MyOrderedDict([('a', '1'), ('b', '2'), ('c', '3')])

    d2 = MyOrderedDict([('a', '1'), ('b', '2')])
    d2.prepend('c', 100)
    print(d2)  # -> MyOrderedDict([('c', 100), ('a', '1'), ('b', '2')])


1

Tôi nhận được một vòng lặp vô cực trong khi cố gắng in hoặc lưu từ điển bằng câu trả lời @Ashwini Chaudhary với Python 2.7. Nhưng tôi đã cố gắng giảm mã của anh ấy một chút và làm cho nó hoạt động ở đây:

def move_to_dict_beginning(dictionary, key):
    """
        Move a OrderedDict item to its beginning, or add it to its beginning.
        Compatible with Python 2.7
    """

    if sys.version_info[0] < 3:
        value = dictionary[key]
        del dictionary[key]
        root = dictionary._OrderedDict__root

        first = root[1]
        root[1] = first[0] = dictionary._OrderedDict__map[key] = [root, first, key]
        dict.__setitem__(dictionary, key, value)

    else:
        dictionary.move_to_end( key, last=False )

điều này là tuyệt vời! Đã làm cho tôi!
Souvik Ray

0

Đây là một mệnh lệnh mặc định, có thứ tự cho phép chèn các mục vào bất kỳ vị trí nào và sử dụng. toán tử để tạo khóa:

from collections import OrderedDict

class defdict(OrderedDict):

    _protected = ["_OrderedDict__root", "_OrderedDict__map", "_cb"]    
    _cb = None

    def __init__(self, cb=None):
        super(defdict, self).__init__()
        self._cb = cb

    def __setattr__(self, name, value):
        # if the attr is not in self._protected set a key
        if name in self._protected:
            OrderedDict.__setattr__(self, name, value)
        else:
            OrderedDict.__setitem__(self, name, value)

    def __getattr__(self, name):
        if name in self._protected:
            return OrderedDict.__getattr__(self, name)
        else:
            # implements missing keys
            # if there is a callable _cb, create a key with its value
            try:
                return OrderedDict.__getitem__(self, name)
            except KeyError as e:
                if callable(self._cb):
                    value = self[name] = self._cb()
                    return value
                raise e

    def insert(self, index, name, value):
        items = [(k, v) for k, v in self.items()]
        items.insert(index, (name, value))
        self.clear()
        for k, v in items:
            self[k] = v


asd = defdict(lambda: 10)
asd.k1 = "Hey"
asd.k3 = "Bye"
asd.k4 = "Hello"
asd.insert(1, "k2", "New item")
print asd.k5 # access a missing key will create one when there is a callback
# 10
asd.k6 += 5  # adding to a missing key
print asd.k6
# 15
print asd.keys()
# ['k1', 'k2', 'k3', 'k4', 'k5', 'k6']
print asd.values()
# ['Hey', 'New item', 'Bye', 'Hello', 10, 15]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.