Why are there no ordered dictionaries?

  • Thread starter Christoph Zwerschke
  • Start date
C

Christoph Zwerschke

Christoph said:
I will assume that d has is a Foord/Larosa ordered dict with "sequence"
attribute in the following.

Then, with other words,

d.keys[:] = newkeyseq

should do the same as:

d.sequence = newkeyseq

At least in the case where newkeyseq is a permutation of d.sequence.

Otherwise, it should behave like the given implementation for setting
slices, i.e.

- if newkeyseq has duplicate elements, an exception should be raised.
- if newkeyseq has elements not in d.sequence, then the dictionary
should be updated with corresponding None values
- if d.sequence has elements not in newkeyseq then these elements should
be deleted from the dictionary

-- Christoph
 
C

Christoph Zwerschke

Bengt said:
OTOH,
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: unhashable type
I.e., slices are not valid keys for ordinary dicts, and slices tie in
very well with the ordered aspect of ordered dicts, so that's an
argument for permitting it via the indexing syntax, not just items[:]
or items()[:] which have related but not identical semantics.

I see it like that. BTW, the above error message is pretty bad.
> I wonder who is going to use it for what.

I think re-ordering will be a very rare use case anyway and slicing even
more. As a use case, I think of something like mixing different
configuration files and default configuration parameters, while trying
to keep a certain order of parameters and parameters blocks.

-- Christoph
 
F

Fuzzyman

Note that I've done two things with the Foord/Larosa dict. ;-)

I've implemented slicing, including slice assignment and deletion. I've
also 'hidden' ``sequence``, but you can pass arguments to keys, values
and items.

I've done a second (experimental) implementation of a custom keys
object. This is effectively the managed list - which you can call as a
method or mutate in place. You can't delete members from 'keys' but you
can do slice assignment so long as the sequence you're replacing is the
same length (and is a re -ordering of the set being replaced).

I'll post it on Monday, and if people like it I'll complete it.

All the best,


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

Bengt Richter

Bengt said:
d.keys[:] = newkeyseq

Do you really mean just re-ordering the keys without a corresponding reording of values??
That would be a weird renaming of all values. Or do you means that any key should still
retrieve the same value as before if used as d[key]? In which case the values must undergo
the same permutation as the keys. I.e., you are assuming key->value pairings remain stable
through any key reorderings?

Since it is considered as being a dictionary in the first place, the
key->value pairings should of course stay stable. In the usual
implementation based on an ordinary dictionary with an additional key
list ("sequence" in the Foord/Larosa and "_keys" in the Bejamin/Winter
implementation), you would only set the key list, since the value list
is generated dynamically. But if your implementation keeps internal
values or items lists, these need to be adjusted as well.

I will assume that d has is a Foord/Larosa ordered dict with "sequence"
attribute in the following.

Then, with other words,

d.keys[:] = newkeyseq

should do the same as:

d.sequence = newkeyseq
Exactly what, though? should e.g.
d.keys[3] = newk3
mean (not a suggested implementation, just to define semantics)
keys = d.keys()
if newk3 in keys and keys.index(newk3)!=3:
raise ValueError,'Attempt to introduce duplicate key'
items = d.items()
items[3] = (newk3, items[3][1])
d.clear()
d.update(items)

Yes, that would be the correct semantics. Of course this should not be
the real implementation and use KeyError instead of ValueError. With
other words,

d.keys = newkey

sould be the same as:

if d.sequence != newkey:
if newkey in d.sequence:
raise KeyError,'Attempt to introduce duplicate key'
else:
d.sequence = newkey
This would allow what you might call renaming in place.
Similarly
d.keys[i:j] = newkeysij
might have the semantics of
keys = d.keys()
outside = set(keys[:i])+set(keys[j:])
if outside & set(newkeysij) or len(newkeysij) != len(set(newkeysij)):
raise ValueError,'Attempt to introduce duplicate key(s)'
items = d.items()
items[i:j] = [(k, items[kx+i][1]) for kx,k in enumerate(newkeysij)]
d.clear()
d.update(items)

Is this what is desired?

Not quite, because it does not preserve the key->value pairings (see
above) and it would behave strangely or raise an exception if the new
slice is larger. The following code would do:

keys = d.keys()
outside = set(keys[:i])|set(keys[j:])
if outside & set(newkeysij) or len(newkeysij) != len(set(newkeysij)):
raise ValueError,'Attempt to introduce duplicate key(s)'
items = d.items()
items[i:j] = [(k, d.get(k, None)) for k in newkeysij]
d.clear()
d.update(items)

(Note that there was a bug in the second line. You cannot add sets.)
Oops, thinking about adding dicts ;-)
Again, this would be equivalent to:

seq = d.sequence
newseq = seq[:]
newseq[i:j] = newkeysij
if len(newseq) != len(set(newseq)):
raise KeyError,'Attempt to introduce duplicate key(s)'
for k in set(seq[i:j]) - set(newkeysij):
del d[k]
for k in set(newkeysij) - set(seq[i:j]):
d[k] = None
d.sequence = newseq
That depends on how you implement ;-)

Ok, I was thinking of the usual implementations.
Back from holiday, so maybe I'll hack something out.

Let us know when you have something to check out.

Maybe Fuzzyman can make some moderate improvements to the existing
odict.py, and you can do something more "radical". Then we have two
"reference implementations" and can compare how they prove regarding
performance and usability.
My code so far is a kludge to get functionality. Perhaps we can arrive at
a spec by doing some TDD. My current kludge passes these tests
(run by py.test which I downloaded from the pypy project site and made
work (apparanently ;-) with IIRC a .pth file to point to py where I
expanded the zip, and a cmd file to kick of python running py.test,
and I think that was all there was. As you can see, it is super easy to define
tests. If you want to try it, I think I can retrace my steps and describe
the way I set it up (for windows NT) in a few turgid run-on paragraphs ;-)
Maybe I'll get around to writing a downloading/checking/installing script
for it, with a few interactive are-you-sure?'s and file tree placement options etc.

I'm calling it a Creordict for now -- Created-order-dict.
Here are the tests so far. Do you want to add some to refine what's supposed to
happen. You may want to look at test_items, test_keys, test_values
as those are the ones that provide d.items(), d.items, d.items[i:j]
style accesses, with assign, eval, and del available for the latter two.
also test___getitem__ for d => value normal key access vs
d[i:j] => another Creordict instance. Let me know what's missing.
I'm not sure how this list-based dict object will work out, but it's
a subclass of list, not of dict. ;-)

Some def test_usecase_xx(): ... definitions might be nice.
I am running py.test on windows NT. I din't compile anything,
I just cobbled afew things together.

----< test_creordict.py >----------------------------------------------
# test_creordict.py
# XXX assoclist.py ?? cf. scheme assv using == (eq<->is, eqv<->==, equal<->deep== ???)
# Tests for creordict.py -- a dictionary object which keeps items in creation time order.
#
# XXX boilerplate bokr AT oz DOT net
from py.test import raises
import py

from creordict import Creordict

def test_sanity():
d = Creordict()
assert d.keys() == []
assert d.values() == []
assert d.items() == []
assert d == d
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
assert repr(d) == "Creordict([('a', 0), ('b', 100), ('c', 200), ('d', 300), ('e', 400)])"
assert d.keys() == ['a', 'b', 'c', 'd', 'e']
assert d.values() == [0, 100, 200, 300, 400]
assert d.items() == [('a', 0), ('b', 100), ('c', 200), ('d', 300), ('e', 400)]


def test___init__():
d = Creordict()
assert d.keys() == []
assert d.values() == []
assert d.items() == []
assert d == d
assert d._index == {}
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
assert repr(d) == "Creordict(%r)"% d.items()
assert d.keys() == ['a', 'b', 'c', 'd', 'e']
assert d.values() == [0, 100, 200, 300, 400]
assert d.items() == [('a', 0), ('b', 100), ('c', 200), ('d', 300), ('e', 400)]
assert d._index == {'a': 0, 'c': 2, 'b': 1, 'e': 4, 'd': 3}

def test___contains__():
d = Creordict([('a', 1)])
assert 'a' in d
assert (4 in d) == False

def test___eq__():
d = Creordict([('a', 1)])
assert d == d
raises(TypeError, "d == {}")
assert d != Creordict([('b', 1)])

def test___cmp__():
d = Creordict([('a', 1)])
assert cmp(d, d) == 0
raises(TypeError, "cmp(d, {})")

def mkresult(s, **kw):
return Creordict((k, kw.get(k, k in 'abcdef' and 'abcdef'.index(k)*100)) for k in s)

def test___delitem__():
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
print mkresult('abcde')
assert d == mkresult('abcde')
ditems = d.items()
print d['b']
print d.items()
print d._index
print d._index['b']
del d['b']
assert d == mkresult('acde')
del d['a']
assert d == mkresult('cde')
del d['e']
assert d == mkresult('cd')
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
del d[1:3]
assert d == mkresult('ade')
del d[:1]
assert d == mkresult('de')
del d[-1:]
assert d == mkresult('d')

def test___repr__():
assert repr(Creordict()) == 'Creordict([])'
raises(TypeError, "Creordict({1: 1})")
d = Creordict(((1, 3), (3, 2), (2, 1)))
assert repr(d) =='Creordict([(1, 3), (3, 2), (2, 1)])'

def test___setitem__():
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
print mkresult('abcde')
assert d == mkresult('abcde')
d['b'] = 101
assert d == mkresult('abcde', b=101)
d['e'] = 401
assert d == mkresult('abcde', b=101, e=401)

def test___getitem__():
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
assert d == mkresult('abcde')
items = d.items()
for k,v in items:
assert d[k]==v
raises(KeyError, "d['f']")

def test___getslice__():
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
assert d[2:4] == mkresult('cd')
assert d[ :4] == mkresult('abcd')
assert d[2: ] == mkresult('cde')
assert d[ : ] == mkresult('abcde')
assert d[0:0] == mkresult('')

def test___add__():
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
assert d+d == d
assert d + Creordict([('f', 500)]) == mkresult('abcdef')

def test_reverse():
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
d.reverse()
assert d == mkresult('edcba')

def test_insert():
d = Creordict([(k,i*100) for i,k in enumerate('abc')])
d.insert(1, ('x', 101))
assert d == mkresult('axbc', x=101, b=100, c=200)

def test_get():
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
assert d == mkresult('abcde')
items = d.items()
for k,v in items:
assert d.get(k)==v
assert d.get(k+'1') is None
assert d.get(k+'1', v) == v
raises(KeyError, "d['f']")

def test_copy():
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
assert d == d.copy()

def test_items():
assert Creordict().items() == []
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
assert d.items() == [(k,i*100) for i,k in enumerate('abcde')]
assert d.items[:] == [(k,i*100) for i,k in enumerate('abcde')]
assert d.items[2:4] == [(k,i*100) for i,k in enumerate('abcde')][2:4]
d.items[2:4] = []
assert d == mkresult('abe')
d.items[1] = ('x', 'replaces b')
assert d == mkresult('axe', x='replaces b')


def test_keys():
assert Creordict().keys() == []
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
assert d.keys() == list('abcde')
assert d.keys[:] == list('abcde')
assert d.keys[2:4] == list('cd')
assert d.keys[1] == 'b'
assert d.keys[-1] == 'e'
d.keys[2:4] = ['x', 'y']
assert d == mkresult('abxye', x=200, y=300)
del d.keys[-1]
assert d == mkresult('abxy', x=200, y=300)
keys = d.keys()
keys[0], keys[-1] = keys[-1], keys[0] # swap end keys
d.keys[:]=keys
assert d == mkresult('ybxa', x=200, y=0, a=300) # a and y value associations swapped ;-)

def test_values():
assert Creordict().values() == []
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
assert d.values() == [i*100 for i,k in enumerate('abcde')]
assert d.values[:] == [i*100 for i,k in enumerate('abcde')]
assert d.values[2:4] == d[2:4].values()
assert d.values[1] == d.values()[1]
assert d.values[-1] == d.values()[-1]
d.values[2:4] = ['v2', 'v3']
assert d == mkresult('abcde', c='v2', d='v3')
del d.values[-1]
assert d == mkresult('abcd', c='v2', d='v3')
values = d.values()
values[0], values[-1] = values[-1], values[0] # swap end values
d.values[:]=values
assert d == mkresult('abcd', c='v2', d=0, a='v3') # a and y value associations swapped ;-)

def test_iteritems():
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
itd = d.iteritems()
assert type(itd) == type(iter([]))
assert list(itd) == d.items()

def test_len():
d = Creordict()
assert len(d) == 0
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
assert len(d) == 5

def test_has_key():
d = Creordict(((1, 3), (3, 2), (2, 1)))
assert d.has_key(1) is True
assert d.has_key(4) is False

def test_iterkeys():
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
itd = d.iterkeys()
assert type(itd) == type(x for x in [])
assert list(itd) == d.keys()

def test_itervalues():
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
itd = d.itervalues()
assert type(itd) == type(x for x in [])
assert list(itd) == d.values()

def test_clear():
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
d.clear()
assert d.items() == []
assert d.keys() == []
assert d.values() == []

def test_pop():
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
assert d.pop('e') == 400
assert d.pop('b') == 100
assert d.pop('a') == 0
assert d == mkresult('cd')

def test_popitem():
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
assert d.popitem() == ('e', 400)
assert d.popitem() == ('d', 300)
assert d.popitem() == ('c', 200)
assert d == mkresult('ab')

def test_setdefault():
d = Creordict([(k,i*100) for i,k in enumerate('abcde')])
assert d.setdefault('c', 'cee') == 200
assert d.setdefault('g') is None
assert d == mkresult('abcdeg', g=None)
assert d.setdefault('h', 'eightch') == 'eightch'
assert d == mkresult('abcdegh', g=None, h='eightch')

def test_update():
d = Creordict()
assert d == mkresult('')
d.update([(k,i*100) for i,k in enumerate('abcde')])
assert d == mkresult('abcde')
raises(TypeError, "d.update({'f': 500})")
d.update([('f', 500), ('b',101)])
assert d == mkresult('abcdef', b=101)
raises(ValueError, "d.update([('broken',)])")
 
C

Christoph Zwerschke

I had the same idea to create a py.test to verify and compare various
implementations. The doctests in odict.py are nice, but you can't use
them for this purpose and they may not test enough. It would be also
good to have something for testing and comparing performance.

-- Christoph
 
B

Bengt Richter

I had the same idea to create a py.test to verify and compare various
implementations. The doctests in odict.py are nice, but you can't use
them for this purpose and they may not test enough. It would be also
good to have something for testing and comparing performance.
Well, the previous post test file just grew to make a simple check for various
aspects, so it's not super clean. I just grepped for defs in the implementation
and bulk appended "test_" to make an empty starting point. Then I just filled
in tests after a preliminary sanity check test. But there are some things
that could still accidentally be inherited from the base class when builtin
utility functions are called on the dict object. Also there's a lot of cut-paste
duplication an no full explorations of corner cases. There's neat generator-based
test driving with different parameters that I didn't take advantage of,
etc. etc.

I should really read the py test docs and learn to use it
better if I am going to use it. But anyway, it's a q&d hack to show and
sanity-check different usages. The semantics of assigning slices
to d.keys[i:j] and d.values[i:j] are kind of tricky when the size changes
and/or key names match or don't match in various ways, or the incoming
data represents collapsing redundant keys that are legal sequential assignment
overrides but change the size, etc.

One could add keyword args to the constructor to vary key eq and cmp used
on keys to determine "key collisions" between e.g. tuple keys and also for
sort. You could even allow partially non-hashable keys that way if you got tricky.
But maybe this is getting too tricky ;-)

I'll make some simple mods to the test to allow applying it to arbitrary
candidate implementations with different names and directory locations, so
I can try it on odict.py and other versions. But are the semantics right?

Doctest is handy, and in some ways I like examples showing up in the help docs,
but maybe help should take a keyword arg to select showing them (and some other
things too perhaps. A brief version excluding a lot of inheritance for classes
might be nice. Or a pattern for method and class var inclusion.

But I like the separation of the py.test tests with no need to mod the original.
Where next? This ordered dict thing is kind of a side-track from a side-track for me ;-)

Regards,
Bengt Richter
 
F

Fuzzyman

Hello Christoph,
I think re-ordering will be a very rare use case anyway and slicing even
more. As a use case, I think of something like mixing different
configuration files and default configuration parameters, while trying
to keep a certain order of parameters and parameters blocks.

In actual fact - being able to *reorder* the dictionary is the main way
I use this dictionary.

All the best,

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

Fuzzyman

The semantics of assigning slices
to d.keys[i:j] and d.values[i:j] are kind of tricky when the size changes
and/or key names match or don't match in various ways, or the incoming
data represents collapsing redundant keys that are legal sequential assignment
overrides but change the size, etc.

I have come against the same problem with slice assignment, when doing
odict. :)
Allowing the size to change prevents a useful optimisation - but I
dislike *preventing* programmers from doing things.

All the best,

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

Bengt Richter

Hmmm... it would be interesting to see if these tests can be used with
odict.
I assume you are referring to the pytest tests I posted, though I would
need some of the context you snipped to me more sure ;-)

Anyway, with some changes[1], they can.
The odict implementation now has full functionality by the way.
The one at
http://www.voidspace.org.uk/cgi-bin/voidspace/downman.py?file=odict.py
? It seems to be unchanged.

Optimisations to follow and maybe a few *minor* changes.

Anyway, test of the above results in:

C:\UTIL\\..\py.test test
============================= test process starts =============================
testing-mode: inprocess
executable: d:\python-2.4b1\mingw\python24.exe (2.4.0-beta-1)
using py lib: D:\bpy24\py-0.8.0-alpha2\py <rev unknown>

test\test_odicts.py[28] FF...FF..FFFF..FFF..........
test\test_odicts_candidate.py[0]

_______________________________________________________________________________
___________________________ entrypoint: test_sanity ___________________________

def test_sanity():
d = CandidateDict()
assert d.keys() == []
assert d.values() == []
assert d.items() == []
assert d == d
d = CandidateDict([(k,i*100) for i,k in enumerate('abcde')])
E assert repr(d) == "%s([('a', 0), ('b', 100), ('c', 200), ('d', 300), ('e', 400)])"%Can
dateDict.__name__
> assert "{'a': 0, 'b': 100, 'c': 200, 'd': 300, 'e': 400}" == ("%s([('a', 0), ('b', 100
('c', 200), ('d', 300), ('e', 400)])" % CandidateDict.__name__)
+ where "{'a': 0, 'b': 100, 'c': 200, 'd': 300, 'e': 400}" = repr({'a': 0, 'b': 100,
c': 200, 'd': 300, 'e': 400})

[C:\pywk\ut\test\test_odicts.py:19]
_______________________________________________________________________________
Apparently you are still using plain dict repr format, not a format that could (ideally) be exec'd
to reconstitute the thing repr'd.
'{}'

vs
'Creordict([])'

Since the test uses CandidateDict.__name__, it should work either way.

__________________________ entrypoint: test___init__ __________________________

def test___init__():
d = CandidateDict()
assert d.keys() == []
assert d.values() == []
assert d.items() == []
assert d == d
d = CandidateDict([(k,i*100) for i,k in enumerate('abcde')])
E assert repr(d) == "%s(%r)"% (CandidateDict.__name__, d.items())
> assert "{'a': 0, 'b': 100, 'c': 200, 'd': 300, 'e': 400}" == ('%s(%r)' % ('OrderedDict
[('a', 0), ('b', 100), ('c', 200), ('d', 300), ('e', 400)]))
+ where "{'a': 0, 'b': 100, 'c': 200, 'd': 300, 'e': 400}" = repr({'a': 0, 'b': 100,
c': 200, 'd': 300, 'e': 400})

[C:\pywk\ut\test\test_odicts.py:32]
_______________________________________________________________________________
(Duplicate from test_sanity)

________________________ entrypoint: test___delitem__ _________________________

def test___delitem__():
d = CandidateDict([(k,i*100) for i,k in enumerate('abcde')])
print mkresult('abcde')
assert d == mkresult('abcde')
ditems = d.items()
print d['b']
print d.items()
del d['b']
assert d == mkresult('acde')
del d['a']
assert d == mkresult('cde')
del d['e']
assert d == mkresult('cd')
d = CandidateDict([(k,i*100) for i,k in enumerate('abcde')])
> del d[1:3]

[C:\pywk\ut\test\test_odicts.py:70]
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

def __delitem__(self, key):
"""
>>> d = OrderedDict(((1, 3), (3, 2), (2, 1)))
>>> del d[3]
>>> d {1: 3, 2: 1}
>>> del d[3]
Traceback (most recent call last):
KeyError: 3
"""
# do the dict.__delitem__ *first* as it raises
# the more appropriate error
E dict.__delitem__(self, key)
> TypeError: unhashable type

[c:\pywk\clp\odict.py:137]
- - - - - - - - - - - test___delitem__: recorded stdout - - - - - - - - - - -
{'a': 0, 'b': 100, 'c': 200, 'd': 300, 'e': 400}
100
[('a', 0), ('b', 100), ('c', 200), ('d', 300), ('e', 400)]

_______________________________________________________________________________
You don't appear to accept slice operation syntax directly on the instance

__________________________ entrypoint: test___repr__ __________________________

def test___repr__():
E assert repr(CandidateDict()) == '%s([])'%CandidateDict.__name__
> assert '{}' == ('%s([])' % CandidateDict.__name__)
+ where '{}' = repr({})
+ where {} = CandidateDict()

[C:\pywk\ut\test\test_odicts.py:78]
_______________________________________________________________________________
________________________ entrypoint: test___getslice__ ________________________

def test___getslice__():
d = CandidateDict([(k,i*100) for i,k in enumerate('abcde')])
E assert d[2:4] == mkresult('cd')
> TypeError: unhashable type

[C:\pywk\ut\test\test_odicts.py:102]
_______________________________________________________________________________
Again, no slice op

__________________________ entrypoint: test___add__ ___________________________

def test___add__():
d = CandidateDict([(k,i*100) for i,k in enumerate('abcde')])
E assert d+d == d
> TypeError: unsupported operand type(s) for +: 'OrderedDict' and 'OrderedDict'

[C:\pywk\ut\test\test_odicts.py:110]
_______________________________________________________________________________
No addition defined on OrderedDicts?

__________________________ entrypoint: test_reverse ___________________________

def test_reverse():
d = CandidateDict([(k,i*100) for i,k in enumerate('abcde')])
E d.reverse()
> AttributeError: 'OrderedDict' object has no attribute 'reverse'

[C:\pywk\ut\test\test_odicts.py:115]
_______________________________________________________________________________
No reverse

___________________________ entrypoint: test_insert ___________________________

def test_insert():
d = CandidateDict([(k,i*100) for i,k in enumerate('abc')])
E d.insert(1, ('x', 101))
> AttributeError: 'OrderedDict' object has no attribute 'insert'

[C:\pywk\ut\test\test_odicts.py:120]
_______________________________________________________________________________
No insert

___________________________ entrypoint: test_items ____________________________

def test_items():
assert CandidateDict().items() == []
d = CandidateDict([(k,i*100) for i,k in enumerate('abcde')])
assert d.items() == [(k,i*100) for i,k in enumerate('abcde')]
E assert d.items[:] == [(k,i*100) for i,k in enumerate('abcde')]
> TypeError: unsubscriptable object

[C:\pywk\ut\test\test_odicts.py:141]
_______________________________________________________________________________
Only d.items() ?

____________________________ entrypoint: test_keys ____________________________

def test_keys():
assert CandidateDict().keys() == []
d = CandidateDict([(k,i*100) for i,k in enumerate('abcde')])
assert d.keys() == list('abcde')
E assert d.keys[:] == list('abcde')
> TypeError: unsubscriptable object

[C:\pywk\ut\test\test_odicts.py:153]
_______________________________________________________________________________
Only d.keys() ?

___________________________ entrypoint: test_values ___________________________

def test_values():
assert CandidateDict().values() == []
d = CandidateDict([(k,i*100) for i,k in enumerate('abcde')])
assert d.values() == [i*100 for i,k in enumerate('abcde')]
E assert d.values[:] == [i*100 for i,k in enumerate('abcde')]
> TypeError: unsubscriptable object

[C:\pywk\ut\test\test_odicts.py:170]
_______________________________________________________________________________
Only d.values() ?

============ tests finished: 17 passed, 11 failed in 1.36 seconds =============


[1] I changed the tests to use a CandidateDict alias, and eliminated a couple of checks
on internal state, so as to allow different implementations to be tested, using
CandidateDict.__name__ to synthesize alternate expected representation strings.

I made the test selectable if run interactively instead of directly via pytest:

[17:43] C:\pywk\ut>py24 test\test_odicts.py
Enter selection [1..2] (q to do nothing)
for importing candidate odicts
1: from ut.creordict import Creordict as CandidateDict
2: from clp.odict import OrderedDict as CandidateDict

"from ut.creordict import Creordict as CandidateDict"

is selected. Type "Yes" exactly (w/o quotes) to accept: Yes
============================= test process starts =============================
testing-mode: inprocess
executable: d:\python-2.4b1\mingw\python24.exe (2.4.0-beta-1)
using py lib: D:\bpy24\py-0.8.0-alpha2\py <rev unknown>

test\test_odicts.py[28] ............................

================== tests finished: 28 passed in 1.03 seconds ==================

Not sure they way I did it is a good idea, but anyway I can just add a little
boilerplate at the bottom of a test and a text file with the choices (which are
one-line sources that get written to test_... so the test can do from test_... import CandidateDict
and have the chosen class by the name it uses for both.

Regards,
Bengt Richter
 
B

Bengt Richter

Hello Christoph,


In actual fact - being able to *reorder* the dictionary is the main way
I use this dictionary.
Curious as to usage patterns
1) how many instances created, deleted
2) how many elements in an instance min, max?
3) how often updated?
3a) just ordering, no value or size change?
3b) just value change, no order or size change?
3c) changes involving size?
4) proportions of read vs write/delete/reorder accesses?

Regards,
Bengt Richter
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top