Skip to content

Commit 9183af9

Browse files
List support: extend method (pyccel#1731)
Add support for the `extend` method of Python lists to the semantic stage. Add handling of that method to the Python printer. This fixes pyccel#1694. **Commit Summary** - Create a superclass `ListMethod` inheriting from `PyccelInternalFunction`, representing the structure for all list methods. - Create one dynamic printer `_print_ListMethod` for all list methods to prevent code duplication. - Add the `ListExtend` class only to document the behavior of `extend()` and its Pyccel implementation. - Create `_visit_ListExtend`, which, depending on the argument passed to `extend()`, tries to create semantic nodes of `append()` or a syntactic node of a `For` loop and then re-parse it. - Add tests for multiple list methods involving many scenarios where the functions could be used.
1 parent 11e2120 commit 9183af9

File tree

12 files changed

+658
-173
lines changed

12 files changed

+658
-173
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
55

66
### Added
77

8+
- #1694 : Add Python support for list method `extend()`.
89
- #1739 : Add Python support for set method `clear()`.
910
- #1739 : Add abstract class `SetMethod` to handle calls to various set methods.
1011
- #1740 : Add Python support for set method `copy()`.

pyccel/ast/builtin_methods/list_methods.py

Lines changed: 111 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,48 @@
1313
from pyccel.ast.datatypes import NativeVoid, NativeGeneric, NativeHomogeneousList
1414
from pyccel.ast.internals import PyccelInternalFunction
1515

16-
1716
__all__ = ('ListAppend',
1817
'ListClear',
18+
'ListExtend',
1919
'ListInsert',
20+
'ListMethod',
2021
'ListPop',
2122
)
2223

2324
#==============================================================================
24-
class ListAppend(PyccelInternalFunction):
25+
class ListMethod(PyccelInternalFunction):
26+
"""
27+
Abstract class for list method calls.
28+
29+
A subclass of this base class represents calls to a specific list
30+
method.
31+
32+
Parameters
33+
----------
34+
list_obj : TypedAstNode
35+
The object which the method is called from.
36+
37+
*args : TypedAstNode
38+
The arguments passed to list methods.
39+
"""
40+
__slots__ = ("_list_obj",)
41+
_attribute_nodes = ("_list_obj",)
42+
name = None
43+
def __init__(self, list_obj, *args):
44+
self._list_obj = list_obj
45+
super().__init__(*args)
46+
47+
@property
48+
def list_obj(self):
49+
"""
50+
Get the object representing the list.
51+
52+
Get the object representing the list.
53+
"""
54+
return self._list_obj
55+
56+
#==============================================================================
57+
class ListAppend(ListMethod):
2558
"""
2659
Represents a call to the .append() method.
2760
@@ -36,14 +69,13 @@ class ListAppend(PyccelInternalFunction):
3669
3770
Parameters
3871
----------
39-
list_variable : Variable
40-
The variable representing the list.
72+
list_obj : TypedAstNode
73+
The list object which the method is called from.
4174
4275
new_elem : TypedAstNode
4376
The argument passed to append() method.
4477
"""
45-
__slots__ = ("_list_variable", "_append_arg")
46-
_attribute_nodes = ("_list_variable", "_append_arg")
78+
__slots__ = ()
4779
_dtype = NativeVoid()
4880
_shape = None
4981
_order = None
@@ -52,104 +84,70 @@ class ListAppend(PyccelInternalFunction):
5284
_class_type = NativeVoid()
5385
name = 'append'
5486

55-
def __init__(self, list_variable, new_elem) -> None:
87+
def __init__(self, list_obj, new_elem) -> None:
5688
is_homogeneous = (
5789
new_elem.dtype is not NativeGeneric() and
58-
list_variable.dtype is not NativeGeneric() and
59-
list_variable.dtype == new_elem.dtype and
60-
list_variable.precision == new_elem.precision and
61-
list_variable.rank - 1 == new_elem.rank
62-
)
90+
list_obj.dtype is not NativeGeneric() and
91+
list_obj.dtype == new_elem.dtype and
92+
list_obj.precision == new_elem.precision and
93+
list_obj.rank - 1 == new_elem.rank
94+
)
6395
if not is_homogeneous:
6496
raise TypeError("Expecting an argument of the same type as the elements of the list")
65-
self._list_variable = list_variable
66-
self._append_arg = new_elem
67-
super().__init__()
68-
69-
@property
70-
def list_variable(self):
71-
"""
72-
Get the variable representing the list.
73-
74-
Get the variable representing the list.
75-
"""
76-
return self._list_variable
77-
78-
@property
79-
def append_argument(self):
80-
"""
81-
Get the argument which is passed to append().
82-
83-
Get the argument which is passed to append().
84-
"""
85-
return self._append_arg
97+
super().__init__(list_obj, new_elem)
8698

8799
#==============================================================================
88-
class ListPop(PyccelInternalFunction) :
100+
class ListPop(ListMethod) :
89101
"""
90102
Represents a call to the .pop() method.
91103
92104
Represents a call to the .pop() method which
93105
removes the item at the specified index.
94106
The method also returns the removed item.
95107
108+
>>> [1, 2].pop()
109+
2
110+
96111
Parameters
97112
----------
98-
list_variable : TypedAstNode
99-
The name of the list.
113+
list_obj : TypedAstNode
114+
The list object which the method is called from.
100115
101116
index_element : TypedAstNode
102117
The current index value for the element to be popped.
103118
"""
104-
__slots__ = ('_dtype','_precision', '_index','_list_variable')
105-
_attribute_nodes = ('_index','_list_variable')
106-
_rank = 0
107-
_order = None
108-
_shape = None
119+
__slots__ = ('_dtype','_precision', '_rank', '_shape', '_order')
109120
_class_type = NativeHomogeneousList()
110121
name = 'pop'
111122

112-
def __init__(self, list_variable, index_element=None):
113-
self._index = index_element
114-
self._list_variable = list_variable
115-
self._dtype = list_variable.dtype
116-
self._precision = list_variable.precision
117-
super().__init__()
118-
119-
@property
120-
def pop_index(self):
121-
"""
122-
The current index value for the element to be popped.
123-
124-
The current index value for the element to be popped.
125-
"""
126-
return self._index
127-
128-
@property
129-
def list_variable(self):
130-
"""
131-
Provide the name of the list as the return value.
132-
133-
Provide the name of the list as the return value.
134-
"""
135-
return self._list_variable
123+
def __init__(self, list_obj, index_element=None) -> None:
124+
self._rank = list_obj.rank - 1
125+
self._dtype = list_obj.dtype
126+
self._precision = list_obj.precision
127+
self._shape = (None if len(list_obj.shape) == 1 else tuple(list_obj.shape[1:]))
128+
self._order = (None if self._shape is None or len(self._shape) == 1 else list_obj.order)
129+
super().__init__(list_obj, index_element)
136130

137131
#==============================================================================
138-
class ListClear(PyccelInternalFunction) :
132+
class ListClear(ListMethod) :
139133
"""
140134
Represents a call to the .clear() method.
141135
142136
Represents a call to the .clear() method which deletes all elements from a list,
143137
effectively turning it into an empty list.
144138
Note that the .clear() method doesn't return any value.
145139
140+
>>> a = [1, 2]
141+
>>> a.clear()
142+
>>> print(a)
143+
[]
144+
146145
Parameters
147146
----------
148-
list_variable : TypedAstNode
149-
The name of the list.
147+
list_obj : TypedAstNode
148+
The list object which the method is called from.
150149
"""
151-
__slots__ = ('_list_variable',)
152-
_attribute_nodes = ('_list_variable',)
150+
__slots__ = ()
153151
_dtype = NativeVoid()
154152
_precision = -1
155153
_rank = 0
@@ -158,21 +156,11 @@ class ListClear(PyccelInternalFunction) :
158156
_class_type = NativeVoid()
159157
name = 'clear'
160158

161-
def __init__(self, list_variable):
162-
self._list_variable = list_variable
163-
super().__init__()
164-
165-
@property
166-
def list_variable(self):
167-
"""
168-
Provide the name of the list as the return value.
169-
170-
Provide the name of the list as the return value.
171-
"""
172-
return self._list_variable
159+
def __init__(self, list_obj) -> None:
160+
super().__init__(list_obj)
173161

174162
#==============================================================================
175-
class ListInsert(PyccelInternalFunction):
163+
class ListInsert(ListMethod):
176164
"""
177165
Represents a call to the .insert() method.
178166
@@ -188,17 +176,16 @@ class ListInsert(PyccelInternalFunction):
188176
189177
Parameters
190178
----------
191-
list_variable : Variable
192-
The variable representing the list.
179+
list_obj : TypedAstNode
180+
The list object which the method is called from.
193181
194182
index : TypedAstNode
195183
The index value for the element to be added.
196184
197185
new_elem : TypedAstNode
198186
The argument passed to insert() method.
199187
"""
200-
__slots__ = ("_index", "_list_variable", "_insert_arg")
201-
_attribute_nodes = ("_index", "_list_variable", "_insert_arg")
188+
__slots__ = ()
202189
_dtype = NativeVoid()
203190
_shape = None
204191
_order = None
@@ -207,44 +194,49 @@ class ListInsert(PyccelInternalFunction):
207194
_class_type = NativeVoid()
208195
name = 'insert'
209196

210-
def __init__(self, list_variable, index, new_elem) -> None:
197+
def __init__(self, list_obj, index, new_elem) -> None:
211198
is_homogeneous = (
212199
new_elem.dtype is not NativeGeneric() and
213-
list_variable.dtype is not NativeGeneric() and
214-
list_variable.dtype == new_elem.dtype and
215-
list_variable.precision == new_elem.precision and
216-
list_variable.rank - 1 == new_elem.rank
200+
list_obj.dtype is not NativeGeneric() and
201+
list_obj.dtype == new_elem.dtype and
202+
list_obj.precision == new_elem.precision and
203+
list_obj.rank - 1 == new_elem.rank
217204
)
218205
if not is_homogeneous:
219206
raise TypeError("Expecting an argument of the same type as the elements of the list")
220-
self._index = index
221-
self._list_variable = list_variable
222-
self._insert_arg = new_elem
223-
super().__init__()
224-
225-
@property
226-
def index(self):
227-
"""
228-
Index in which the element will be added.
229-
230-
Index in which the element will be added.
231-
"""
232-
return self._index
207+
super().__init__(list_obj, index, new_elem)
233208

234-
@property
235-
def list_variable(self):
236-
"""
237-
Get the variable representing the list.
209+
#==============================================================================
210+
class ListExtend(ListMethod):
211+
"""
212+
Represents a call to the .extend() method.
213+
214+
Represents a call to the .extend() method of an object with a list type,
215+
which adds items of an iterable (list, tuple, dictionary, etc) at the end
216+
of a list.
217+
This method is handled through the call to `_visit_ListExtend` in
218+
the semantic stage. It then attempts to construct a `For` loop node with
219+
a body that calls `append()`, or direct `append()` nodes depending on
220+
the type of the iterable passed to `extend()`.
221+
This class should never be instantiated; it's only purpose is to help
222+
construct the annotation_method `_visit_ListExtend`.
223+
The extend method is called as follows:
224+
225+
>>> a = [1, 2, 3]
226+
>>> a.extend(range(4, 8))
227+
>>> print(a)
228+
[1, 2, 3, 4, 5, 6, 7]
238229
239-
Get the variable representing the list.
240-
"""
241-
return self._list_variable
230+
Parameters
231+
----------
232+
list_obj : TypedAstNode
233+
The list object which the method is called from.
242234
243-
@property
244-
def insert_argument(self):
245-
"""
246-
Get the argument which is passed to insert().
235+
iterable : TypedAstNode
236+
The argument passed to extend() method.
237+
"""
238+
__slots__ = ()
239+
name = 'extend'
247240

248-
Get the argument which is passed to insert().
249-
"""
250-
return self._insert_arg
241+
def __init__(self, list_obj, iterable) -> None:
242+
super().__init__(list_obj, iterable)

0 commit comments

Comments
 (0)