"dict ที่แช่แข็ง" จะเป็นอย่างไร?


158
  • ชุดที่แช่แข็งเป็น frozenet
  • รายการแช่แข็งอาจเป็น tuple
  • คำสั่งแช่แข็งจะเป็นอย่างไร? dict ที่เปลี่ยนแปลงไม่ได้ hashable

ฉันคิดว่ามันอาจจะเป็นอะไรที่คล้ายcollections.namedtupleๆ กัน แต่นั่นก็เหมือนทict-แช่แข็งคีย์ (dict แบบครึ่งแข็ง) ไม่ใช่เหรอ

A "frozendict" ควรพจนานุกรมแช่แข็งมันควรจะมีkeys, values, getฯลฯ และการสนับสนุนin, forฯลฯ

อัปเดต:
* มีอยู่: https://www.python.org/dev/peps/pep-0603

คำตอบ:


120

Python ไม่มีประเภท Frozendict ในตัว ปรากฎว่าสิ่งนี้จะไม่มีประโยชน์บ่อยเกินไป (แม้ว่ามันจะยังคงมีประโยชน์มากกว่าที่frozensetเป็นอยู่)

เหตุผลที่พบบ่อยที่สุดในการต้องการประเภทดังกล่าวคือเมื่อฟังก์ชั่น memoizing เรียกฟังก์ชั่นที่มีข้อโต้แย้งที่ไม่รู้จัก วิธีการแก้ปัญหาที่พบมากที่สุดในการจัดเก็บเทียบเท่า hashable ของ Dict (ที่ค่าที่มี hashable) tuple(sorted(kwargs.iteritems()))เป็นสิ่งที่ต้องการ

ขึ้นอยู่กับการเรียงลำดับที่ไม่บ้า Python ไม่สามารถรับประกันได้ว่าการเรียงลำดับจะส่งผลให้เกิดสิ่งที่สมเหตุสมผล (แต่มันไม่สามารถสัญญาได้มากนักดังนั้นอย่าเหงื่อมากเกินไป)


คุณสามารถทำเสื้อคลุมบางอย่างที่ใช้งานได้ง่ายเหมือนกับ dict มันอาจมีลักษณะเหมือน

import collections

class FrozenDict(collections.Mapping):
    """Don't forget the docstrings!!"""

    def __init__(self, *args, **kwargs):
        self._d = dict(*args, **kwargs)
        self._hash = None

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

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

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

    def __hash__(self):
        # It would have been simpler and maybe more obvious to 
        # use hash(tuple(sorted(self._d.iteritems()))) from this discussion
        # so far, but this solution is O(n). I don't know what kind of 
        # n we are going to run into, but sometimes it's hard to resist the 
        # urge to optimize when it will gain improved algorithmic performance.
        if self._hash is None:
            hash_ = 0
            for pair in self.items():
                hash_ ^= hash(pair)
            self._hash = hash_
        return self._hash

มันควรจะใช้งานได้ดี:

>>> x = FrozenDict(a=1, b=2)
>>> y = FrozenDict(a=1, b=2)
>>> x is y
False
>>> x == y
True
>>> x == {'a': 1, 'b': 2}
True
>>> d = {x: 'foo'}
>>> d[y]
'foo'

7
ฉันไม่ทราบว่าผู้คนที่มีความปลอดภัยในระดับใดจะกังวลกับเรื่องนี้ แต่ในแง่ของ__hash__วิธีการของคุณอาจได้รับการปรับปรุงเล็กน้อย เพียงใช้ตัวแปรชั่วคราวเมื่อคำนวณแฮชและตั้งค่าself._hashเมื่อคุณมีค่าสุดท้ายเท่านั้น ด้วยวิธีนี้เธรดอื่นจะได้รับแฮชในขณะที่การคำนวณแรกจะทำการคำนวณซ้ำซ้อนแทนที่จะได้ค่าที่ไม่ถูกต้อง
Jeff DQ

22
@Jeff ตามกฎแล้วรหัสทั้งหมดในทุกที่ไม่ปลอดภัยต่อเธรดและคุณควรล้อมรอบโครงสร้างการซิงโครไนซ์บางส่วนเพื่อใช้รหัสนั้นอย่างปลอดภัย นอกจากนี้แนวคิดเรื่องความปลอดภัยของเธรดโดยเฉพาะของคุณขึ้นอยู่กับอะตอมมิกซิตี้ของการกำหนดคุณลักษณะของวัตถุซึ่งอยู่ไกลจากการรับประกัน
Devin Jeanpierre

9
@ Anentropic ไม่เป็นความจริง แต่อย่างใด
Mike Graham

17
ถูกเตือน: "FrozenDict" นี้ไม่จำเป็นต้องหยุด ไม่มีอะไรจะหยุดคุณจากการใส่รายการที่ไม่แน่นอนเป็นค่าซึ่งในกรณีที่การแฮ็ชจะทำให้เกิดข้อผิดพลาด ไม่มีสิ่งใดที่ผิดปกติ แต่ผู้ใช้ควรระวัง อีกสิ่งหนึ่ง: อัลกอริทึมการแฮชนี้ได้รับการคัดเลือกมาไม่ดีมีแนวโน้มที่จะเกิดการชนกันของแฮช ตัวอย่างเช่น {'a': 'b'} แฮชเหมือนกับ {'b': 'a'} และ {'a': 1, 'b': 2} แฮชเหมือนกับ {'a': 2, ' b ': 1} ทางเลือกที่ดีกว่าก็คือ self._hash ^ = hash ((คีย์, ค่า))
Steve Byrnes

6
หากคุณเพิ่มรายการที่ไม่แน่นอนในวัตถุที่ไม่เปลี่ยนรูปแบบพฤติกรรมที่เป็นไปได้สองอย่างคือการโยนข้อผิดพลาดในการสร้างวัตถุหรือโยนข้อผิดพลาดในการแฮชวัตถุ สิ่งอันดับทำในสิ่งที่ไม่เคยทำมาก่อน ฉันคิดว่าคุณตัดสินใจอย่างดีที่จะใช้แนวทางหลังทุกสิ่งที่ได้รับการพิจารณา อย่างไรก็ตามฉันคิดว่าผู้คนอาจเห็นว่า FrozenDict และ frozenet มีชื่อที่คล้ายกันและข้ามไปยังข้อสรุปที่พวกเขาควรทำตัวเหมือนกัน ดังนั้นฉันคิดว่ามันคุ้มค่าที่จะเตือนผู้คนเกี่ยวกับความแตกต่างนี้ :-)
Steve Byrnes

63

อยากรู้อยากเห็นแม้ว่าเราจะไม่ค่อยมีประโยชน์frozensetในงูหลาม แต่ก็ยังไม่มีการแมปแบบแช่แข็ง ความคิดที่ถูกปฏิเสธในPEP 416 - เพิ่ม frozendict builtin ประเภท ความคิดที่อาจจะมาเยือนในหลาม 3.9 ดูPEP 603 - การเพิ่มประเภท frozenmap คอลเลกชัน

ดังนั้น python 2 แก้ปัญหานี้:

def foo(config={'a': 1}):
    ...

ยังดูเหมือนว่าจะค่อนข้างอ่อนแอ:

def foo(config=None):
    if config is None:
        config = default_config = {'a': 1}
    ...

ใน python3 คุณมีตัวเลือกนี้ :

from types import MappingProxyType

default_config = {'a': 1}
DEFAULTS = MappingProxyType(default_config)

def foo(config=DEFAULTS):
    ...

ตอนนี้การกำหนดค่าเริ่มต้นสามารถอัปเดตแบบไดนามิก แต่ยังคงไม่เปลี่ยนรูปแบบที่คุณต้องการให้ไม่เปลี่ยนรูปได้โดยผ่านพร็อกซีแทน

ดังนั้นการเปลี่ยนแปลงในdefault_configจะอัปเดตDEFAULTSตามที่คาดไว้ แต่คุณไม่สามารถเขียนลงในวัตถุพร็อกซีการแมปเองได้

เป็นที่ยอมรับว่าไม่ใช่สิ่งเดียวกันกับ "dict ที่เปลี่ยนไม่ได้และไม่สามารถแฮชได้" - แต่มันเป็นสิ่งทดแทนที่ดีเนื่องจากมีกรณีการใช้งานชนิดเดียวกันซึ่งเราอาจต้องการ Frozendict


2
มีเหตุผลใดที่จะเก็บพร็อกซีไว้ในตัวแปรโมดูลหรือไม่? ทำไมไม่เพียงdef foo(config=MappingProxyType({'a': 1})):? ตัวอย่างของคุณยังช่วยให้การแก้ไขทั่วโลกผ่านdefault_configเช่นกัน
jpmc26

นอกจากนี้ฉันสงสัยว่าการมอบหมายสองครั้งในconfig = default_config = {'a': 1}นั้นเป็นตัวพิมพ์ผิด
jpmc26

21

การสมมติว่าคีย์และค่าของพจนานุกรมนั้นไม่เปลี่ยนแปลง (เช่นสตริง) แล้ว:

>>> d
{'forever': 'atones', 'minks': 'cards', 'overhands': 'warranted', 
 'hardhearted': 'tartly', 'gradations': 'snorkeled'}
>>> t = tuple((k, d[k]) for k in sorted(d.keys()))
>>> hash(t)
1524953596

นี่เป็นสิ่งที่ดีเป็นที่ยอมรับและเป็นตัวแทนที่ไม่เปลี่ยนแปลงของ dict (ยกเว้นการเปรียบเทียบพฤติกรรมบ้า ๆ บอ ๆ เรียงลำดับ)
Mike Graham

6
@devin: ตกลงเต็ม แต่ฉันจะให้โพสต์ของฉันเป็นตัวอย่างที่มักจะมีวิธีที่ดีกว่า
msw

14
ยิ่งไปกว่านั้นก็คือการวางไว้ใน frozenet ซึ่งไม่ต้องการคีย์หรือค่าที่จะมีการกำหนดลำดับที่สอดคล้องกัน
asmeurer

8
ปัญหาเดียวกับสิ่งนี้: คุณไม่มีการแมปอีกต่อไป นั่นจะเป็นจุดรวมของการมี dict ที่แข็งในตอนแรก
นักฟิสิกส์บ้า

2
วิธีนี้เป็นวิธีที่ดีมากเมื่อกลับไปที่ dict เรียบง่ายdict(t)
codythecoder

12

ไม่มีfronzedictแต่คุณสามารถใช้MappingProxyTypeที่ถูกเพิ่มไปยังไลบรารีมาตรฐานด้วย Python 3.3:

>>> from types import MappingProxyType
>>> foo = MappingProxyType({'a': 1})
>>> foo
mappingproxy({'a': 1})
>>> foo['a'] = 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'mappingproxy' object does not support item assignment
>>> foo
mappingproxy({'a': 1})

กับข้อแม้:TypeError: can't pickle mappingproxy objects
Radu

ฉันชอบความคิดของสิ่งนี้ ฉันจะลองดู
Doug

ปัญหานี้MappingProxyTypeยังคงเป็น unhashable
Jab

10

นี่คือรหัสที่ฉันใช้อยู่ ฉันจัดระดับชุดย่อยเป็นโหล ข้อดีของการนี้มีดังนี้

  1. นี่เป็นวัตถุที่ไม่เปลี่ยนรูปอย่างแท้จริง ไม่ต้องพึ่งพาพฤติกรรมที่ดีของผู้ใช้และนักพัฒนาในอนาคต
  2. ง่ายต่อการแปลงไปมาระหว่างพจนานุกรมปกติและพจนานุกรมที่ถูกตรึง FrozenDict (orig_dict) -> พจนานุกรมแช่แข็ง dict (Frozen_dict) -> dict ปกติ

อัปเดต 21 มกราคม 2558: รหัสต้นฉบับที่ฉันโพสต์ในปี 2014 ใช้ for-loop เพื่อค้นหาคีย์ที่ตรงกับ มันช้าอย่างไม่น่าเชื่อ ตอนนี้ฉันได้รวบรวมการใช้งานซึ่งใช้ประโยชน์จากคุณลักษณะการแฮ็กจำนวนมาก คู่ของคีย์ - ค่าจะถูกเก็บไว้ในภาชนะพิเศษโดยที่__hash__และ__eq__ฟังก์ชั่นนั้นจะยึดตามคีย์เท่านั้น รหัสนี้ได้รับการทดสอบโดยหน่วยอย่างเป็นทางการซึ่งแตกต่างจากสิ่งที่ฉันโพสต์ที่นี่ในเดือนสิงหาคม 2014

ใบอนุญาตแบบ MIT

if 3 / 2 == 1:
    version = 2
elif 3 / 2 == 1.5:
    version = 3

def col(i):
    ''' For binding named attributes to spots inside subclasses of tuple.'''
    g = tuple.__getitem__
    @property
    def _col(self):
        return g(self,i)
    return _col

class Item(tuple):
    ''' Designed for storing key-value pairs inside
        a FrozenDict, which itself is a subclass of frozenset.
        The __hash__ is overloaded to return the hash of only the key.
        __eq__ is overloaded so that normally it only checks whether the Item's
        key is equal to the other object, HOWEVER, if the other object itself
        is an instance of Item, it checks BOTH the key and value for equality.

        WARNING: Do not use this class for any purpose other than to contain
        key value pairs inside FrozenDict!!!!

        The __eq__ operator is overloaded in such a way that it violates a
        fundamental property of mathematics. That property, which says that
        a == b and b == c implies a == c, does not hold for this object.
        Here's a demonstration:
            [in]  >>> x = Item(('a',4))
            [in]  >>> y = Item(('a',5))
            [in]  >>> hash('a')
            [out] >>> 194817700
            [in]  >>> hash(x)
            [out] >>> 194817700
            [in]  >>> hash(y)
            [out] >>> 194817700
            [in]  >>> 'a' == x
            [out] >>> True
            [in]  >>> 'a' == y
            [out] >>> True
            [in]  >>> x == y
            [out] >>> False
    '''

    __slots__ = ()
    key, value = col(0), col(1)
    def __hash__(self):
        return hash(self.key)
    def __eq__(self, other):
        if isinstance(other, Item):
            return tuple.__eq__(self, other)
        return self.key == other
    def __ne__(self, other):
        return not self.__eq__(other)
    def __str__(self):
        return '%r: %r' % self
    def __repr__(self):
        return 'Item((%r, %r))' % self

class FrozenDict(frozenset):
    ''' Behaves in most ways like a regular dictionary, except that it's immutable.
        It differs from other implementations because it doesn't subclass "dict".
        Instead it subclasses "frozenset" which guarantees immutability.
        FrozenDict instances are created with the same arguments used to initialize
        regular dictionaries, and has all the same methods.
            [in]  >>> f = FrozenDict(x=3,y=4,z=5)
            [in]  >>> f['x']
            [out] >>> 3
            [in]  >>> f['a'] = 0
            [out] >>> TypeError: 'FrozenDict' object does not support item assignment

        FrozenDict can accept un-hashable values, but FrozenDict is only hashable if its values are hashable.
            [in]  >>> f = FrozenDict(x=3,y=4,z=5)
            [in]  >>> hash(f)
            [out] >>> 646626455
            [in]  >>> g = FrozenDict(x=3,y=4,z=[])
            [in]  >>> hash(g)
            [out] >>> TypeError: unhashable type: 'list'

        FrozenDict interacts with dictionary objects as though it were a dict itself.
            [in]  >>> original = dict(x=3,y=4,z=5)
            [in]  >>> frozen = FrozenDict(x=3,y=4,z=5)
            [in]  >>> original == frozen
            [out] >>> True

        FrozenDict supports bi-directional conversions with regular dictionaries.
            [in]  >>> original = {'x': 3, 'y': 4, 'z': 5}
            [in]  >>> FrozenDict(original)
            [out] >>> FrozenDict({'x': 3, 'y': 4, 'z': 5})
            [in]  >>> dict(FrozenDict(original))
            [out] >>> {'x': 3, 'y': 4, 'z': 5}   '''

    __slots__ = ()
    def __new__(cls, orig={}, **kw):
        if kw:
            d = dict(orig, **kw)
            items = map(Item, d.items())
        else:
            try:
                items = map(Item, orig.items())
            except AttributeError:
                items = map(Item, orig)
        return frozenset.__new__(cls, items)

    def __repr__(self):
        cls = self.__class__.__name__
        items = frozenset.__iter__(self)
        _repr = ', '.join(map(str,items))
        return '%s({%s})' % (cls, _repr)

    def __getitem__(self, key):
        if key not in self:
            raise KeyError(key)
        diff = self.difference
        item = diff(diff({key}))
        key, value = set(item).pop()
        return value

    def get(self, key, default=None):
        if key not in self:
            return default
        return self[key]

    def __iter__(self):
        items = frozenset.__iter__(self)
        return map(lambda i: i.key, items)

    def keys(self):
        items = frozenset.__iter__(self)
        return map(lambda i: i.key, items)

    def values(self):
        items = frozenset.__iter__(self)
        return map(lambda i: i.value, items)

    def items(self):
        items = frozenset.__iter__(self)
        return map(tuple, items)

    def copy(self):
        cls = self.__class__
        items = frozenset.copy(self)
        dupl = frozenset.__new__(cls, items)
        return dupl

    @classmethod
    def fromkeys(cls, keys, value):
        d = dict.fromkeys(keys,value)
        return cls(d)

    def __hash__(self):
        kv = tuple.__hash__
        items = frozenset.__iter__(self)
        return hash(frozenset(map(kv, items)))

    def __eq__(self, other):
        if not isinstance(other, FrozenDict):
            try:
                other = FrozenDict(other)
            except Exception:
                return False
        return frozenset.__eq__(self, other)

    def __ne__(self, other):
        return not self.__eq__(other)


if version == 2:
    #Here are the Python2 modifications
    class Python2(FrozenDict):
        def __iter__(self):
            items = frozenset.__iter__(self)
            for i in items:
                yield i.key

        def iterkeys(self):
            items = frozenset.__iter__(self)
            for i in items:
                yield i.key

        def itervalues(self):
            items = frozenset.__iter__(self)
            for i in items:
                yield i.value

        def iteritems(self):
            items = frozenset.__iter__(self)
            for i in items:
                yield (i.key, i.value)

        def has_key(self, key):
            return key in self

        def viewkeys(self):
            return dict(self).viewkeys()

        def viewvalues(self):
            return dict(self).viewvalues()

        def viewitems(self):
            return dict(self).viewitems()

    #If this is Python2, rebuild the class
    #from scratch rather than use a subclass
    py3 = FrozenDict.__dict__
    py3 = {k: py3[k] for k in py3}
    py2 = {}
    py2.update(py3)
    dct = Python2.__dict__
    py2.update({k: dct[k] for k in dct})

    FrozenDict = type('FrozenDict', (frozenset,), py2)

1
โปรดทราบว่าคุณได้รับอนุญาตภายใต้ CC BY-SA 3.0 โดยโพสต์ไว้ที่นี่ อย่างน้อยที่เป็นมุมมองที่แพร่หลาย ฉันเดาว่ากฎหมายพื้นฐานสำหรับสิ่งที่ยอมรับข้อตกลงและเงื่อนไขบางอย่างเมื่อคุณลงทะเบียนครั้งแรก
Evgeni Sergeev

1
ฉันทำสมองของฉันพยายามคิดหาวิธีแฮชกุญแจโดยไม่ต้องสั่ง การกำหนดแฮชใหม่Itemให้เป็นแฮชของกุญแจคือการแฮ็กอย่างเป็นระเบียบ!
clacke

น่าเสียดายที่เวลาการทำงานของdiff(diff({key}))ยังคงเป็นเส้นตรงในขนาดของ FrozenDict ในขณะที่เวลาในการเข้าถึง dict ปกติคงที่ในกรณีทั่วไป
เดนนิส

6

ฉันคิดถึง frozendict ทุกครั้งที่ฉันเขียนฟังก์ชั่นเช่นนี้:

def do_something(blah, optional_dict_parm=None):
    if optional_dict_parm is None:
        optional_dict_parm = {}

6
ทุกครั้งที่ฉันเห็นความคิดเห็นเช่นนี้ฉันแน่ใจว่าฉันทำให้เกิดความผิดพลาดและวาง {} เป็นค่าเริ่มต้นและกลับไปดูรหัสที่เพิ่งเขียนของฉัน
Ryan Hiebert

1
ใช่มันเป็น gotcha ที่น่ารังเกียจที่ทุกคนพบไม่ช้าก็เร็ว
Mark Visser

8
สูตรที่ง่ายขึ้น:optional_dict_parm = optional_dict_parm or {}
Emmanuel

2
ในกรณีนี้คุณสามารถใช้เป็นค่าเริ่มต้นสำหรับการโต้แย้ง types.MappingProxyType({})
GingerPlusPlus

@GingerPlusPlus คุณสามารถเขียนคำตอบนั้นได้หรือไม่?
jonrsharpe

5

คุณสามารถใช้frozendictจากutilspieแพ็คเกจเป็น:

>>> from utilspie.collectionsutils import frozendict

>>> my_dict = frozendict({1: 3, 4: 5})
>>> my_dict  # object of `frozendict` type
frozendict({1: 3, 4: 5})

# Hashable
>>> {my_dict: 4}
{frozendict({1: 3, 4: 5}): 4}

# Immutable
>>> my_dict[1] = 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/mquadri/workspace/utilspie/utilspie/collectionsutils/collections_utils.py", line 44, in __setitem__
    self.__setitem__.__name__, type(self).__name__))
AttributeError: You can not call '__setitem__()' for 'frozendict' object

ตามเอกสาร :

frozendict (dict_obj) : ยอมรับ obj ของ dict type และส่งคืน dict ที่แฮชและไม่เปลี่ยนรูป



3

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

นี่คือไฟล์ "frozen_dict.pyx"

import cython
from collections import Mapping

cdef class dict_wrapper:
    cdef object d
    cdef int h

    def __init__(self, *args, **kw):
        self.d = dict(*args, **kw)
        self.h = -1

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

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

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

    def __hash__(self):
        if self.h == -1:
            self.h = hash(frozenset(self.d.iteritems()))
        return self.h

class FrozenDict(dict_wrapper, Mapping):
    def __repr__(self):
        c = type(self).__name__
        r = ', '.join('%r: %r' % (k,self[k]) for k in self)
        return '%s({%s})' % (c, r)

__all__ = ['FrozenDict']

นี่คือไฟล์ "setup.py"

from distutils.core import setup
from Cython.Build import cythonize

setup(
    ext_modules = cythonize('frozen_dict.pyx')
)

หากคุณติดตั้ง Cython แล้วให้บันทึกสองไฟล์ด้านบนลงในไดเรกทอรีเดียวกัน ย้ายไปยังไดเรกทอรีนั้นในบรรทัดคำสั่ง

python setup.py build_ext --inplace
python setup.py install

และคุณควรจะทำ


3

ข้อเสียเปรียบหลักของnamedtupleมันคือต้องระบุก่อนใช้งานดังนั้นจึงสะดวกน้อยลงสำหรับเคสแบบใช้ครั้งเดียว

อย่างไรก็ตามมีวิธีแก้ปัญหาในทางปฏิบัติที่สามารถใช้เพื่อจัดการกรณีดังกล่าวจำนวนมาก สมมติว่าคุณต้องการมีค่า dict ที่ไม่เปลี่ยนรูปแบบต่อไปนี้:

MY_CONSTANT = {
    'something': 123,
    'something_else': 456
}

สามารถเลียนแบบได้ดังนี้:

from collections import namedtuple

MY_CONSTANT = namedtuple('MyConstant', 'something something_else')(123, 456)

เป็นไปได้ที่จะเขียนฟังก์ชันเสริมเพื่อทำให้สิ่งนี้เป็นไปโดยอัตโนมัติ:

def freeze_dict(data):
    from collections import namedtuple
    keys = sorted(data.keys())
    frozen_type = namedtuple(''.join(keys), keys)
    return frozen_type(**data)

a = {'foo':'bar', 'x':'y'}
fa = freeze_dict(data)
assert a['foo'] == fa.foo

แน่นอนว่าวิธีนี้ใช้ได้เฉพาะกับ dicts แบบเรียบ แต่ไม่ควรยากเกินไปที่จะใช้เวอร์ชันแบบเรียกซ้ำ


1
ปัญหาเช่นเดียวกับคำตอบ tuple อื่น ๆ : คุณต้องทำgetattr(fa, x)แทนfa[x]ไม่มีkeysวิธีที่ปลายนิ้วของคุณและเหตุผลอื่น ๆ ทั้งหมดที่การทำแผนที่สามารถเป็นที่ต้องการ
นักฟิสิกส์บ้า

1

subclassing dict

ฉันเห็นรูปแบบนี้ในป่า (github) และต้องการพูดถึงมัน:

class FrozenDict(dict):
    def __init__(self, *args, **kwargs):
        self._hash = None
        super(FrozenDict, self).__init__(*args, **kwargs)

    def __hash__(self):
        if self._hash is None:
            self._hash = hash(tuple(sorted(self.items())))  # iteritems() on py2
        return self._hash

    def _immutable(self, *args, **kws):
        raise TypeError('cannot change object - object is immutable')

    __setitem__ = _immutable
    __delitem__ = _immutable
    pop = _immutable
    popitem = _immutable
    clear = _immutable
    update = _immutable
    setdefault = _immutable

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

d1 = FrozenDict({'a': 1, 'b': 2})
d2 = FrozenDict({'a': 1, 'b': 2})
d1.keys() 
assert isinstance(d1, dict)
assert len(set([d1, d2])) == 1  # hashable

ข้อดี

  • การสนับสนุนสำหรับget(), keys(), items()( iteritems()บน py2) และสินค้าทั้งหมดจากdictออกจากกล่องโดยไม่ต้องใช้พวกเขาอย่างชัดเจน
  • ใช้ภายในdictซึ่งหมายถึงประสิทธิภาพ ( dictเขียนเป็น c ใน CPython)
  • สง่างามเรียบง่ายและไม่มีเวทมนตร์ดำ
  • isinstance(my_frozen_dict, dict)ผลตอบแทนจริง - แม้ว่าหลามสนับสนุนให้เป็ดพิมพ์หลายแพคเกจใช้isinstance()นี้สามารถบันทึก tweaks และปรับแต่งมากมาย

จุดด้อย

  • คลาสย่อยใด ๆ สามารถแทนที่สิ่งนี้หรือเข้าถึงได้ภายใน (คุณไม่สามารถปกป้องบางสิ่งได้ในหลาม 100% คุณควรเชื่อถือผู้ใช้ของคุณและให้เอกสารที่ดี)
  • ถ้าคุณสนใจความเร็วคุณอาจต้องการทำให้__hash__เร็วขึ้นเล็กน้อย

ฉันทำการเปรียบเทียบความเร็วในเธรดอื่นและปรากฎว่าการเอาชนะ__setitem__และการสืบทอดdictนั้นเร็วมากเมื่อเทียบกับทางเลือกอื่น ๆ
Torxed


0

ฉันต้องการเข้าถึงกุญแจคงที่สำหรับบางสิ่ง ณ จุดหนึ่งสำหรับบางสิ่งที่เป็นประเภทของสิ่งที่ทั่วโลกเป็นกลุ่มและฉันก็นั่งลงบนสิ่งนี้:

class MyFrozenDict:
    def __getitem__(self, key):
        if key == 'mykey1':
            return 0
        if key == 'mykey2':
            return "another value"
        raise KeyError(key)

ใช้มันเหมือน

a = MyFrozenDict()
print(a['mykey1'])

คำเตือน: ฉันไม่แนะนำสิ่งนี้สำหรับกรณีการใช้งานส่วนใหญ่เพราะมันทำให้การแลกเปลี่ยนค่อนข้างรุนแรง


ต่อไปนี้จะเท่ากันในอำนาจโดยไม่ทำให้ประสิทธิภาพแย่ลง อย่างไรก็ตามนี่เป็นเพียงการทำให้เข้าใจง่ายของคำตอบที่ได้รับการยอมรับ ... `` `คลาส FrozenDict: def __init __ (ตัวเอง, ข้อมูล): self._data = data def __getitem __ (ตัวเอง, คีย์): return self._data [key]` ` `
Yuval

@Yuval คำตอบนั้นไม่เทียบเท่า สำหรับ starters api นั้นแตกต่างกันเนื่องจากต้องการข้อมูลเพื่อเริ่มต้น นี่ก็หมายความว่ามันไม่สามารถเข้าถึงได้ทั่วโลกอีกต่อไป นอกจากนี้หาก _data ถูกเปลี่ยนแปลงค่าส่งคืนของคุณจะเปลี่ยนไป ฉันรู้ว่ามีการแลกเปลี่ยนที่สำคัญเช่นที่ฉันพูดฉันไม่แนะนำสิ่งนี้สำหรับกรณีการใช้งานส่วนใหญ่
Adverbly

-1

ในกรณีที่ไม่มีการสนับสนุนภาษาพื้นเมืองคุณสามารถทำเองหรือใช้วิธีแก้ไขปัญหาที่มีอยู่ โชคดีที่ Python ทำให้มันง่ายที่จะขยายการใช้งานฐาน

class frozen_dict(dict):
    def __setitem__(self, key, value):
        raise Exception('Frozen dictionaries cannot be mutated')

frozen_dict = frozen_dict({'foo': 'FOO' })
print(frozen['foo']) # FOO
frozen['foo'] = 'NEWFOO' # Exception: Frozen dictionaries cannot be mutated

# OR

from types import MappingProxyType

frozen_dict = MappingProxyType({'foo': 'FOO'})
print(frozen_dict['foo']) # FOO
frozen_dict['foo'] = 'NEWFOO' # TypeError: 'mappingproxy' object does not support item assignment

คลาส Frozen_dict ของคุณไม่สามารถแฮชได้
miracle173
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.