extend with list vs extend with iterator

84 views
Skip to first unread message

Martin R

unread,
Jun 10, 2021, 3:54:15 AM6/10/21
to sage-devel
While working on https://trac.sagemath.org/ticket/31897 Tejasvi (gsoc21) and I stumbled across the following python behaviour:

sage: def f(n):
....:     print(len(l))
....:     
sage: l = []; l.extend(f(n) for n in range(3))
0
1
2
sage: l = []; l.extend([f(n) for n in range(3)])
0
0
0

Is this behaviour we can rely on?  I could not find in the python doc, in any case.

Martin

Dima Pasechnik

unread,
Jun 10, 2021, 4:10:12 AM6/10/21
to sage-devel

Martin

--
You received this message because you are subscribed to the Google Groups "sage-devel" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sage-devel+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/sage-devel/53c97d28-fc5e-43ee-9371-bf8d2b646554n%40googlegroups.com.

Martin R

unread,
Jun 10, 2021, 4:20:23 AM6/10/21
to sage-devel
Thank you for the pointer, but of course I looked it up before posting.  It says

list.extend(iterable)

Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable.

However, this description is simply not true:

sage: l = []; l[len(l):] = (f(n) for n in range(3))
0
0
0

Jean-Florent Raymond

unread,
Jun 10, 2021, 4:42:38 AM6/10/21
to sage-...@googlegroups.com
Related:
https://stackoverflow.com/questions/57194152/is-python-list-extenditerator-guaranteed-to-be-lazy

It seems they are indeed not equivalent, despite what is claimed in the
documentation.

Le 10/06/2021 à 10:20, 'Martin R' via sage-devel a écrit :
> Thank you for the pointer, but of course I looked it up before posting. It
> says
>
> list.extend(*iterable*)
>>> <https://groups.google.com/d/msgid/sage-devel/53c97d28-fc5e-43ee-9371-bf8d2b646554n%40googlegroups.com?utm_medium=email&utm_source=footer>
>>> .
>>>
>>
>

Martin R

unread,
Jun 10, 2021, 4:44:14 AM6/10/21
to sage-devel

Martin R

unread,
Jun 10, 2021, 4:46:49 AM6/10/21
to sage-devel
Oh, sorry, emails crossed.  The problem is, that the python crew seems to refuse to clarify whether this is unspecified or intended.  Unless I find a completely different approach (which I hope), this is a shame, because extend is very likely faster than the loop equivalent.

Sébastien Labbé

unread,
Jun 10, 2021, 11:28:26 AM6/10/21
to sage-devel
In the second example, the list [f(n) for n in range(3)] is created before the method extend is called which explains why 0 is printed 3 times.

What is the question?

Sébastien

Martin R

unread,
Jun 10, 2021, 12:47:37 PM6/10/21
to sage-devel
The question is, whether we can rely on behaviour of the reference implementation of python which contradicts its documentation:

list.extend(iterable)

Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable.

sage: def f(n):

....:     print(len(l))
....:     
sage: l = []; l.extend(f(n) for n in range(3))
0
1
2
sage: l = []; l[len(l):] = (f(n) for n in range(3))
0
0
0

Martin

Michael Orlitzky

unread,
Jun 10, 2021, 2:01:18 PM6/10/21
to sage-...@googlegroups.com
On Thu, 2021-06-10 at 09:47 -0700, 'Martin R' via sage-devel wrote:
> The question is, whether we can rely on behaviour of the reference
> implementation of python which contradicts its documentation:
>

The cynic's take: you can't rely on anything in a language that has no
specification.

Can you write a doctest for the current behavior at the same time as
you write the code that relies upon it?


Sébastien Labbé

unread,
Jun 13, 2021, 2:42:47 AM6/13/21
to sage-devel
On Thursday, June 10, 2021 at 6:47:37 PM UTC+2 axio...@yahoo.de wrote:
The question is, whether we can rely on behaviour of the reference implementation of python which contradicts its documentation:

list.extend(iterable)

Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable.

I believe it is equivalent:

sage: def f(n):
....:     return None
....:                                                                    
sage: l = []; l.extend(f(n) for n in range(3)); l
[None, None, None]
sage: l = []; l[len(l):] = (f(n) for n in range(3)); l                                                                         
[None, None, None]

Sébastien

Martin R

unread,
Jun 13, 2021, 6:00:44 AM6/13/21
to sage-devel
So, my example is fake?

Vincent Delecroix

unread,
Jun 13, 2021, 9:46:42 AM6/13/21
to sage-...@googlegroups.com
It is not fake. If your iterator uses a reference to
the underlying list, then the behavior is different

sage: l = []
sage: iterator = (len(l) for i in range(3))
sage: l.extend(iterator)
sage: print(l)
[0, 1, 2]

sage: l = []
sage: iterator = (len(l) for i in range(3))
sage: l[:] = iterator
sage: print(l)
[0, 0, 0]


Le 13/06/2021 à 12:00, 'Martin R' via sage-devel a écrit :
> So, my example is fake?
>
> Sébastien Labbé schrieb am Sonntag, 13. Juni 2021 um 08:42:47 UTC+2:
>
>> On Thursday, June 10, 2021 at 6:47:37 PM UTC+2 axio...@yahoo.de wrote:
>>
>>> The question is, whether we can rely on behaviour of the reference
>>> implementation of python which contradicts its documentation:
>>>
>>> list.extend(*iterable*)

Sébastien Labbé

unread,
Jun 13, 2021, 10:33:43 AM6/13/21
to sage-devel
On Sunday, June 13, 2021 at 12:00:44 PM UTC+2 axio...@yahoo.de wrote:
So, my example is fake?

I think it has nothing to do with extend method:

sage: def f(n):
....:     print(len(l))
....:                                                                           
sage: l = []                                                                    
sage: for fn in [f(n) for n in range(3)]:
....:     l.append(fn)
....:                                                                           
0
0
0
sage: l                                                                         
[None, None, None]

sage: l = []                                                                    
sage: for fn in (f(n) for n in range(3)):
....:     l.append(fn)
....:                                                                           

0
1
2
sage: l                                                                         
[None, None, None]

It is more about the difference between [f(n) for n in range(3)] which is a list and (f(n) for n in range(3)) which is an iterator. The list [f(n) for n in range(3)] is computed completely before being used, whereas the iterator is lazy:

sage: l = []
sage: L = [f(n) for n in range(3)]                                              
0
0
0
sage: it = (f(n) for n in range(3)) 



Reply all
Reply to author
Forward
0 new messages