New Ordered Dictionery to Criticise

Fuzzyman fuzzyman at gmail.com
Fri Dec 2 09:03:43 EST 2005


Hello Bengt,

Bengt Richter wrote:
> On 1 Dec 2005 03:38:37 -0800, "Fuzzyman" <fuzzyman at gmail.com> wrote:
>
> >
> >Fuzzyman wrote:
> >> Sorry for this hurried message - I've done a new implementation of out
> >> ordered dict. This comes out of the discussion on this newsgroup (see
> >> blog entry for link to archive of discussion).
> >>
> >> See the latest blog entry to get at it :
> >> http://www.voidspace.org.uk/python/weblog/index.shtml
> >>
> >
> >Hello all,
> >
> >I've just done a new "beta 2" version. It has a full version of
> >FancyODict with the custome "callable sequence objects" for keys,
> >values and items. They are almost completely covered by tests.
> >
> >You can download the new(er) version from :
> >http://www.voidspace.org.uk/cgi-bin/voidspace/downman.py?file=odictbeta2.py
> >
> Ran my tests on it: this one may be of interest:
>  __________________________ entrypoint: test_popitem ___________________________
>
>      def test_popitem():
>          d = CandidateDict([(k,i*100) for i,k in enumerate('abcde')])
>  >       assert d.popitem() == ('e', 400)
>
>  [C:\pywk\ut\test\test_odicts.py:228]
>  _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
>  ...
>  ...
>  _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
>
>      def __getattr__(self, name):
>          """
>          Implemented so that access to ``sequence`` raises a warning.
>
>          >>> d = OrderedDict()
>          >>> d.sequence
>          []
>          """
>          if name == 'sequence':
>              warn('use of sequence attribute is deprecated. Use keys method '
>                  'instead.', DeprecationWarning)
>              # NOTE: Still (currently) returns a direct reference. Need to
>              #   because code that uses sequence will expect to be able to
>              #   mutate it in place.
>              return self._sequence
>          else:
>              # NOTE: strictly unnecessary, but it raises the appropriate error
>  >           getattr(self, name)
>
>  [c:\pywk\clp\odictbeta2.py:309]
>  _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
>  Recursion detected (same locals & position)
>  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
>  ============= tests finished: 19 passed, 9 failed in 1.57 seconds =============
>

Yep the line :

getattr(self, name)

is a bug.

I've replaced it now (not yet "published") with
object.__getattr__(self, name)
It could equally just be replaced with a raise AttributeError

You've picked up on another typo in the code, whichis odd because
``popitem`` is tested.

Oh well - works now.

[snip..]
> >    You can currently alter the order (of keys, values *and* items) by
> >passing an iterable into those methods.
> I'm playing this game, but I wonder how much of it really makes sense ;-)
>

Can you explain what you mean ?


> >
> >    Someone has suggested that this "smells bad" - and it ought to be
> >done through separate `setkeys``, ``setvalues``, and ``setitems``
> >methods.
> >
> >    I'm *inclined* to agree, but I don't feel strongly about it. Anyone
> >else got any opinions ?
> I don't see a big difference. What's important, if you ask me, is
> whether you get an idea of what will happen when you pass the iterable as
> an argument. OTTOMH that suggests that keys/values/items[slice] = iterable
> have more expectation-generating precedent, and in fact perhaps should define
> what is to happen if no other constraints are violated.

Don't completely follow.

Are you saying that

d = OrderedDict(some_iterable)
d.keys[slice] = iterable

is more *understandable*  ("expectation-generating precedent" WTF) ?

The alternative being : d.keys(iterable)

In which case I'm inclined to agree. *However* - having custom objects
for items/keys/values is undoubtably less efficient.

Therefore I'm supplying two classes OrderedDict and FancyODict (name
not yet fixed).

OrderedDict needs a way of changing the keys - so it needs either

d.keys(iterable)

*or*

d.setkeys(iterable)

Which is preferable, is my question.

I think you're saying you don't care :-)
In which case I will leave it as it presently is.

> >
> >* ``repr`` ought to return a value that ``eval`` could use to turn back
> >into an OrderedDict.
> >
> >    I have actually done an implementation of this, but it would mean
> >that *all* the doctests need to be changed. I *will* do this at some
> >point.
> Point in favor of py.test's separation of test/tested I guess.
>

Probably. :-)

doctest is part of the standard library - which is a point in it's
favour.


> >
> >* Slice assignment.
> >
> >    The semantics for slice assignment are fiddly.
> >
> >    For example, what do you do if in a slice assignment a key collides
> >with an existing key ?
> IMO, a slice defines the sliced-out as well as the untouched, and I think
> it is an error to collide with the unouched part, since it requires either
> modifying the "untouched" or ignoring incoming slice data.
> Incoming can go by the rules of a plain dict constuctor, except preserving order,
> and the rest should really be untouched IMO. BTW, I think new keys in the
> incoming slice data should NOT wind up at the end of the overall dict, since
> that violates the "untouched" part.
>

"new keys in the incoming slice data should NOT wind up at the end of
the overall dict"

I'm not a 100% certain I understand you, and I possibly didn't explain
myself properly.

If you assign to a slice, and there is a key collision. The old
(pre-existing) key is removed and the new inserted at the expected
index - so it doesn't end up at the "end".


> >
> >    My current implementation does what an ordinary dictionary does,
> >the new value overwrites the previous one. This means that the
> >dictionary can reduce in size as the assignment progresses. {sm;:?}
> That part is ok, though I wonder if some might want to have a strict-replacement
> mode for an odict, so slices can't change sizes.
> BTW  just thought of a name: itemdict -- to suggest the item-list-ordered semantics.
> >
> >    I think this is preferable to raising an error and preventing
> >assignment. It does prevent an optimisation whereby I calculate the
> >indexes of all the new items in advance.
> a strict_replacement=True kwyord arg to the descriptor could let you
> do both.
>

Then I have to implement *both* ways of doing it ! I'll consider it.

> >
> >    It also means you can't rely on the index of a key from a slice
> >assignment, unless you know that there will be no key collisions.
> Well, if you guarantee no name collisions in the source items by using
> an ordered dict as source, and make collisions with the "untouched"
> parts of a slice assignment an error, then you can rely on it.
> IMO that would be a useful middle way.
>

I'll consider that as well. As I said I don't like raising errors when
I don't have to. If the programmer wants to guarantee there are no
collisions then he can check himself. :-)

> >
> >     In general I'm *against* preventing programmers from doing things,
> >so long as the documentation carries an appropriate warning.
> Me too, in general ;-)
[snip..]
> >    # we can assign to every other key
> >    # using an ordered dict
> Why the restriction to ordered dict, other than as a convenient
> way to pre-normalize the item list? Doesn't update accept a plain item list?
>
Allow slice assignment of a list of tuples like update... hmmm...

Maybe if anyone asks for it "in the wild".


> >    d[::2] = OrderedDict([(2, 9), (4, 8)])
> i.e., why not accept plain [(2, 9), (4, 8)] here?
> >    len(d) == 4
> >    False
> BTW, this one I don't do yet. I reject aslice.step not in (None, 1)
> I seemed a bit too weird. But I guess I could just let the programmer decide ;-)

I didn't see any reason to restrict it, seeing as for my (unoptimised)
implementation it doesn't make any difference.

> >
> >    d
> >    {2: 9, 4: 8}
> >
> >    """
> >    Because of the key collisions the length of
> >    d has changed - it now only has two keys instead
> >    of four.
> >    """
> IMO the above collision is an error that should raise an exception,
> because the collision is with a key outside the (albeit interlaced)
> target slice, so you are effectively assigning outside the slice.
>

Yeah - maybe. Still not sure about this.

> BTW, what about slices of keys and values? E.g. does
>
>     d.keys[:] = ['prefix_'+ key for key in d.keys()]
>
> make sense? Note that it is different from d[:] = something.
>

Yep - it allows you to change the keys in place. (Retaining the
position in the sequence)

> Similarly d.values, thoug d.items[sliceinst] might be the same
> as d[sliceinst] for assignment. d[sliceinst] returns an ordered dict though,
> where as d.items[sliceinst] returns the same item list slice as
> d.items()[sliceinst].
>

Yep. Lots to consider. Only a few minor tweaks and my implementation is
basically working enough to release.

All the best,


Fuzzyman
http://www.voidspace.org.uk/python/index.shtml

> Regards,
> Bengt Richter




More information about the Python-list mailing list