Assigning generator expressions to ctype arrays

Terry Reedy tjreedy at udel.edu
Fri Oct 28 16:24:02 EDT 2011


On 10/28/2011 2:05 PM, Patrick Maupin wrote:

> On Oct 27, 10:23 pm, Terry Reedy<tjre... at udel.edu>  wrote:
>> I do not think everyone else should suffer substantial increase in space
>> and run time to avoid surprising you.
>
> What substantial increase?

of time and space, as I said, for the temporary array that I think would 
be needed and which I also described in the previous paragraph that you 
clipped

>  There's already a check that winds up
> raising an exception.  Just make it empty an iterator instead.

It? I have no idea what you intend that to refer to.


>>> It violates the principle of least surprise
>> for ctypes to do what is most efficient in 99.9% of uses?
>
> It doesn't work at all with an iterator, so it's most efficient 100%
> of the time now.  How do you know how many people would use iterators
> if it worked?

I doubt it would be very many because it is *impossible* to make it work 
in the way that I think people would want it to.

>> It could, but at some cost. Remember, people use ctypes for efficiency,

> yes, you just made my argument for me.  Thank you.  It is incredibly
> inefficient to have to create a temp array.

But necessary to work with blank box iterators. Now you are agreeing 
with my argument.

>> so the temp array path would have to be conditional.

> I don't understand this at all.  Right now, it just throws up its
> hands and says "I don't work with iterators."

If ctype_array slice assignment were to be augmented to work with 
iterators, that would, in my opinion (and see below), require use of 
temporary arrays. Since slice assignment does not use temporary arrays 
now (see below), that augmentation should be conditional on the source 
type being a non-sequence iterator.

> Why would it be a problem to change this?

CPython comes with immutable fixed-length arrays (tuples) that do not 
allow slice assignment and mutable variable-length arrays (lists) that 
do. The definition is 'replace the indicated slice with a new slice 
built from all values from an iterable'. Point 1: This works for any 
properly functioning iterable that produces any finite number of items. 
Iterators are always exhausted.

Replace can be thought of as delete follewed by add, but the 
implementation is not that naive. Point 2: If anything goes wrong and an 
exception is raised, the list is unchanged. This means that there must 
be temporary internal storage of either old or new references. An 
example that uses an improperly functioning generator.

 >>> a
[0, 1, 2, 3, 4, 5, 6, 7]
 >>> def g():
	yield None
	raise ValueError

 >>> a[3:6]=g()
Traceback (most recent call last):
   File "<pyshell#21>", line 1, in <module>
     a[3:6]=g()
   File "<pyshell#20>", line 3, in g
     raise ValueError
ValueError
 >>> a
[0, 1, 2, 3, 4, 5, 6, 7]

A c_uint array is a new kind of beast: a fixed-length mutable array. So 
it has to have a different definition of slice assignment than lists. 
Thomas Heller, the ctypes author, apparently chose 'replacement by a 
sequence with exactly the same number of items, else raise an 
exception'. though I do not know what the doc actually says.

An alternative definition would have been to replace as much of the 
slice as possible, from the beginning, while ignoring any items in 
excess of the slice length. This would work with any iterable. However, 
partial replacement of a slice would be a surprising innovation to most.

The current implementation assumes that the reported length of a 
sequence matches the valid indexes and dispenses with temporary storage. 
This is shown by the following:

from ctypes import c_uint
n = 20

class Liar:
     def __len__(self): return n
     def __getitem__(self, index):
         if index < 10:
             return 1
         else:
             raise ValueError()

x = (n * c_uint)()
print(list(x))
x[:] = range(n)
print(list(x))
try:
     x[:] = Liar()
except:
     pass
print(list(x))
 >>>
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

I consider such unintended partial replacement to be a glitch. An 
exception could be raised, but without adding temp storage, the array 
could not be restored. And making a change *and* raising an exception 
would be a different sort of glitch. (One possible with augmented 
assignment involving a mutable member of a tuple.) So I would leave this 
as undefined behavior for an input outside the proper domain of the 
function.

Anyway, as I said before, you are free to propose a specific change 
('work with iterators' is too vague) and provide a corresponding patch.

-- 
Terry Jan Reedy




More information about the Python-list mailing list