[Tutor] FrozenDict

Albert-Jan Roskam sjeik_appie at hotmail.com
Sat Oct 10 13:39:53 CEST 2015



----------------------------------------
> Date: Fri, 9 Oct 2015 01:14:05 -0500
> From: eryksun at gmail.com
> To: tutor at python.org
> Subject: Re: [Tutor] FrozenDict
>
> On 10/8/15, Steven D'Aprano <steve at pearwood.info> wrote:
>>
>> That's one solution, but it is certainly possible for the class to be
>> its own iterator, in which case it needs to follow two rules:
>>
>> (1) self.__next__() needs to return the next value, or raise
>> StopIteration;
>>
>> (2) self.__iter__() needs to return self;
>>
>> and of course like all dunder methods __next__ and __iter__ need to be
>> defined on the class itself, not on the instance.
>
> Except this is generally a bad way to iterate a reiterable. Without a
> separate iterator, there's no simple way to maintain state for
> concurrent iterations.
>
> file types are an exception. A file is reiterable (i.e. by seeking
> back to 0), but the OS file pointer makes a file naturally an
> iterator. Thus getting concurrent iterations of a disk file requires
> separate file objects that have separate OS file pointers.
>
> FrozenDict.next is an example of what not to do:
>
> def next(self):
> try:
> value = self.__kwargs.items()[self.__counter][0]
> except IndexError:
> raise StopIteration
> self.__counter += 1
> return value
>
> In Python 2 this iterates the dict's keys by creating a list of (key,
> value) tuples -- every time next() is called. In Python 3, you'd have
> to explicitly create the list using list(self.__kwargs.items()). The
> design also lacks support for concurrent iterations, and not even
> multiple iterations since __counter isn't reset in __iter__.

Hi Erysun, Steven and others.

Eww, I see now that this indeed horribly inefficient.

>
> The simple approach is to have __iter__ return an instance of the
> wrapped dict's iterator. There's no reason to reinvent the wheel. Plus
> in the non-frozen case, the built-in dict iterators are smart enough
> to raise an error when the dict is modified, since it's assumed that
> any modification to the hash table invalidates the iteration.


I don't understand what you mean with this. It reads like super(FrozenDict, self).__iter__(), but that can't be correct because the supper class is an abstract class.
Anyway, I rewrote the code (__repr__ is omitted for brevity):

from collections import Mapping
import sys

class FrozenDict(Mapping):
    """A dictionary that does not support item assignment after initialization
   >>> fd = FrozenDict(a=1, b=2)
   >>> fd["a"]
    1
   >>> fd["a"] = 777  # doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ...
    TypeError: 'FrozenDict' object does not support item assignment
   >>> sorted(fd.items())
    [('a', 1), ('b', 2)]
   >>> sorted(fd.keys())
    ['a', 'b']
    """
    def __init__(self, **kwargs):
        self.__kwargs = kwargs
        self.__init__ = None
    def __getitem__(self, key):
        return self.__kwargs[key]
    def __iter__(self):
        return self.__kwargs.iterkeys()
    if sys.version_info.major> 2:
        __iter__ = lambda self: iter(self.__kwargs.keys())   # Python 3: I am assuming this approach is more efficient than using try-except. 
    def __len__(self):
        return len(self.__kwargs) 

Curious what you think about this code. By the way, why is a TyperError raised when one tries to set an item? An AttributeError seems more logical.

if __name__ == "__main__":
    import doctest
    doctest.testmod()


As always, thanks a lot!

Best wishes,
Albert-Jan
 
 		 	   		  


More information about the Tutor mailing list