Multi-dimensional list initialization

Ian Kelly ian.g.kelly at gmail.com
Thu Nov 8 16:58:35 CET 2012


On Thu, Nov 8, 2012 at 1:26 AM, Andrew Robinson
<andrew3 at r3dsolutions.com> wrote:
> OK: Then copy by reference using map....:
>
> values = zip(   map( lambda:times, xrange(num_groups) )   )
> if len(values) < len(times) * num_groups ...
>
> Done.  It's clearer than a list comprehension and you still really don't
> need a list multiply.

That is not equivalent to the original.  Even had you not omitted some parts:

values = zip(samples, map(lambda i: times, range(num_groups)))

This still has the problem that map returns a list of num_groups
elements, each of which is times.  The desired value to be passed into
zip is a *single* sequence containing len(times) * num_groups
elements.  This is easily handled by list multiplication, but not so
easily by map or by a single list comprehension.  Looking back at the
'ini' solution you proposed before, I see that this also would be a
problem there.  Fixing the above, it would have to be something like:

values = zip(samples, reduce(operator.add, map(lambda i: times,
range(num_groups)), []))

Or from how I understand the 'ini' syntax to work:

values = zip(samples, reduce(operator.add, [lambda: times, ini
xrange(num_groups)], []))

Which brings to mind another point that I want to get to in a moment.
But when I said that I would use map instead, I meant that *if* the
body of the list comprehension is just a function application, then I
would prefer to use map over the list comprehension.  But in the above
I see no benefit in using a lambda in the first place.

Getting back to that other point, notice what we ended up doing in
both of those constructions above: repeated list concatenation as a
substitute for multiplication.  In fact, when we multiply (aList * 5),
this should be the equivalent of (aList + aList + aList + aList
+aList), should it not?  Clearly, however, there should be no implicit
copying involved in mere list concatenation.  For one thing, if the
user wants to concatenate copies, that is quite easily done
explicitly: (aList[:] + aList[:]) instead of (aList + aList).  For
another, list concatenation is less likely to be used for an
initialization process.  If list multiplication were to copy nested
lists, then, this would break the intuitive notion that list
multiplication is equivalent to repeated list concatenation.

> Yes, but you're very blind to history and code examples implementing the
> slice operation.
> slice usually depends on index; index does not depend on slice.
> Slice is suggested to be implemented by multiple calls to single indexes in
> traditional usage and documentation.

...and then by composing the elements located at those indexes into a
subsequence.

> The xrange(,,)[:] implementation breaks the tradition, because it doesn't
> call index multiple times; nor does it return a result equivalent identical
> to doing that.

Whether any given __getitem__ slicing implementation recursively calls
__getitem__ with a series of indexes or not is an implementation
detail.  If it were possible to index a range object multiple times
and then stuff the results into another range object, then the slicing
result would be equivalent.  The only reason it is not is that you
cannot construct a range object in that fashion.

I think that what you're expecting is that range(5)[:] should return a
list in Python 3 because it returns a list in Python 2.  This does not
represent a change in slicing behavior -- in fact, all you got by
slicing an xrange object in Python 2 was a TypeError.  This represents
an intentional break in backward compatibility between Python 2 and
Python 3, which was the purpose of Python 3 -- to fix a lot of
existing warts in Python by breaking them all at once, rather than
progressively over a long string of versions.  Users porting their
scripts from Python 2 to Python 3 are advised to replace "range(...)"
with "list(range(...))" if what they actually want is a list, and I
believe the 2to3 tool does this automatically.  Once the range object
is converted to a list, there is no further break with Python 2 --
slicing a list gives you a list, just as it always has.

In a nutshell, yes: range(...)[:] produces a different result in
Python 3 than in Python 2, just as it does without the slicing
operation tacked on.  It was never intended that scripts written for
Python 2 should be able to run in Python 3 unchanged without careful
attention to detail.


More information about the Python-list mailing list