Skip to content

Commit

Permalink
List support: append method (#1709)
Browse files Browse the repository at this point in the history
Add support for the append() method of Python lists to the semantic
stage. Add handling of that method to the Python printer. This fixes
#1689.

**Commit Summary**

- Add a class representation for the `append()` method, inheriting from
`PyccelInternalFunction`. The constructor ensures compatibility between
the datatypes of the `append()` argument and the list for homogenous
lists.
- Create `ClassDef` representing a `NativeHomogeneousList`. It includes
a method `append()` implemented by the `ListAppend` class.`
- Update condition in `get_cls_base()` function to properly check on the
classtype of the containers.
- Add method `_print_ListAppend()` to the Python printer for
generating the appropriate Python code for the `append()` method.
- Add check on the semantic to ensure that the correct class
definition (`PythonList`) is assigned to variables created via
expressions such as `a = [1]`
- Add tests for the `append()` method involving many scenarios where the
function could be used.

---------

Co-authored-by: EmilyBourne <louise.bourne@gmail.com>
Co-authored-by: Yaman Güçlü <yaman.guclu@gmail.com>
  • Loading branch information
3 people committed Feb 14, 2024
1 parent 2a3e16d commit f614845
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 8 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file.

### Added

- #1529 : Add Python support for list method `append()`

### Fixed

### Changed
Expand Down
81 changes: 81 additions & 0 deletions pyccel/ast/builtin_methods/list_methods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# coding: utf-8
#------------------------------------------------------------------------------------------#
# This file is part of Pyccel which is released under MIT License. See the LICENSE file or #
# go to https://github.com/pyccel/pyccel/blob/master/LICENSE for full license details. #
#------------------------------------------------------------------------------------------#
"""
The List container has a number of built-in methods that are
always available.
This module contains objects which describe these methods within Pyccel's AST.
"""

from pyccel.ast.datatypes import NativeVoid, NativeGeneric, NativeHomogeneousList
from pyccel.ast.internals import PyccelInternalFunction


__all__ = ('ListAppend',)


class ListAppend(PyccelInternalFunction):
"""
Represents a call to the .append() method.
Represents a call to the .append() method of an object with a list type,
which adds an element to the end of the list. This method returns `None`.
The append method is called as follows:
>>> a = [1]
>>> a.append(2)
>>> print(a)
[1, 2]
Parameters
----------
list_variable : Variable
The variable representing the list.
new_elem : Variable
The argument passed to append() method.
"""
__slots__ = ("_list_variable", "_append_arg")
_attribute_nodes = ("_list_variable", "_append_arg")
_dtype = NativeVoid()
_shape = None
_order = None
_rank = 0
_precision = -1
_class_type = NativeHomogeneousList()
name = 'append'

def __init__(self, list_variable, new_elem) -> None:
is_homogeneous = (
new_elem.dtype is not NativeGeneric() and
list_variable.dtype is not NativeGeneric() and
list_variable.dtype == new_elem.dtype and
list_variable.precision == new_elem.precision and
list_variable.rank - 1 == new_elem.rank
)
if not is_homogeneous:
raise TypeError("Expecting an argument of the same type as the elements of the list")
self._list_variable = list_variable
self._append_arg = new_elem
super().__init__()

@property
def list_variable(self):
"""
Get the variable representing the list.
Get the variable representing the list.
"""
return self._list_variable

@property
def append_argument(self):
"""
Get the argument which is passed to append().
Get the argument which is passed to append().
"""
return self._append_arg
21 changes: 18 additions & 3 deletions pyccel/ast/class_defs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
"""
This module contains all types which define a python class which is automatically recognised by pyccel
"""

from pyccel.ast.builtin_methods.list_methods import ListAppend


from .builtins import PythonImag, PythonReal, PythonConjugate
from .core import ClassDef, PyccelFunctionDef
from .c_concepts import CStackArray
from .datatypes import (NativeBool, NativeInteger, NativeFloat,
NativeComplex, NativeString, NativeNumeric,
NativeTuple, CustomDataType)
NativeComplex, NativeString, NativeNumericTypes,
NativeTuple, CustomDataType, NativeHomogeneousList)
from .numpyext import (NumpyShape, NumpySum, NumpyAmin, NumpyAmax,
NumpyImag, NumpyReal, NumpyTranspose,
NumpyConjugate, NumpySize, NumpyResultType,
Expand All @@ -23,6 +27,7 @@
'StringClass',
'NumpyArrayClass',
'TupleClass',
'ListClass',
'literal_classes',
'get_cls_base')

Expand Down Expand Up @@ -130,6 +135,14 @@

#=======================================================================================

ListClass = ClassDef('list', class_type = NativeHomogeneousList(),
methods=[
PyccelFunctionDef('append', func_class = ListAppend,
decorators = {}),
])

#=======================================================================================

TupleClass = ClassDef('tuple', class_type = NativeTuple(),
methods=[
#index
Expand Down Expand Up @@ -217,10 +230,12 @@ def get_cls_base(dtype, precision, container_type):
return None
if precision in (-1, 0, None) and container_type is dtype:
return literal_classes[dtype]
elif dtype in NativeNumeric or isinstance(container_type, NumpyNDArrayType):
elif isinstance(container_type, (*NativeNumericTypes, NumpyNDArrayType)):
return NumpyArrayClass
elif isinstance(container_type, NativeTuple):
return TupleClass
elif isinstance(container_type, NativeHomogeneousList):
return ListClass
else:
if container_type:
type_name = str(container_type)
Expand Down
7 changes: 7 additions & 0 deletions pyccel/codegen/printing/pycode.py
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,13 @@ def _print_NumpyCountNonZero(self, expr):

return "{}({})".format(name, arg)

def _print_ListAppend(self, expr):
method_name = expr.name
list_var = self._print(expr.list_variable)
append_arg = self._print(expr.append_argument)

return f"{list_var}.{method_name}({append_arg})\n"

def _print_Slice(self, expr):
start = self._print(expr.start) if expr.start else ''
stop = self._print(expr.stop) if expr.stop else ''
Expand Down
13 changes: 8 additions & 5 deletions pyccel/parser/semantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1094,7 +1094,7 @@ def _handle_function(self, expr, func, args, is_method = False):
func : FunctionDef instance, Interface instance or PyccelInternalFunction type
The function being called.
args : tuple
args : iterable
The arguments passed to the function.
is_method : bool
Expand All @@ -1118,10 +1118,13 @@ def _handle_function(self, expr, func, args, is_method = False):

try:
new_expr = func(*args, **kwargs)
except TypeError:
errors.report(UNRECOGNISED_FUNCTION_CALL,
symbol = expr,
severity = 'fatal')
except TypeError as e:
message = str(e)
if not message:
message = UNRECOGNISED_FUNCTION_CALL
errors.report(message,
symbol = expr,
severity = 'fatal')

return new_expr
else:
Expand Down
81 changes: 81 additions & 0 deletions tests/epyccel/test_epyccel_lists.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# pylint: disable=missing-function-docstring, missing-module-docstring, missing-class-docstring
# coding: utf-8
""" Tests for list methods.
"""

import pytest
from pyccel.epyccel import epyccel

@pytest.fixture( params=[
pytest.param("fortran", marks = [
pytest.mark.skip(reason="list methods not implemented in fortran"),
pytest.mark.fortran]),
pytest.param("c", marks = [
pytest.mark.skip(reason="list methods not implemented in c"),
pytest.mark.c]),
pytest.param("python", marks = pytest.mark.python)
],
scope = "module"
)
def language(request):
return request.param

def test_append_basic(language):
def f():
a = [1, 2, 3]
a.append(4)
return a

epyc_f = epyccel(f, language=language)
assert f() == epyc_f()

def test_append_multiple(language):
def f():
a = [1, 2, 3]
a.append(4)
a.append(5)
a.append(6)
return a

epyc_f = epyccel(f, language=language)
assert f() == epyc_f()

def test_append_list(language):
def f():
a = [[1, 2, 3]]
a.append([4, 5, 6])
return a

epyc_f = epyccel(f, language=language)
assert f() == epyc_f()

def test_append_range(language):
def f():
a = [1, 2, 3]
for i in range(0, 1000):
a.append(i)
a.append(1000)
return a

epyc_f = epyccel(f, language=language)
assert f() == epyc_f()

def test_append_range_list(language):
def f():
a = [[1, 2, 3]]
for i in range(0, 1000):
a.append([i, i + 1])
return a

epyc_f = epyccel(f, language=language)
assert f() == epyc_f()

def test_append_range_tuple(language):
def f():
a = [[1, 2, 3]]
for i in range(0, 1000):
a.append((i, i + 1))
return a

epyc_f = epyccel(f, language=language)
assert f() == epyc_f()
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# pylint: disable=missing-function-docstring, missing-module-docstring

import numpy as np
a = [1,2,3]
b = np.int32(4)
a.append(b)

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# pylint: disable=missing-function-docstring, missing-module-docstring


a = [1,2,3]
a.append([4])

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# pylint: disable=missing-function-docstring, missing-module-docstring

a = [1,2,3]
b = (4,5)
a.append(b)

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# pylint: disable=missing-function-docstring, missing-module-docstring

a = [[1, 2, 3]]
a.append((4.4, 8.4))

0 comments on commit f614845

Please sign in to comment.