Skip to content

Commit e5bc912

Browse files
authored
Add support for @Property (pyccel#2089)
The semantic handling and the printing of methods using the `@property` decorator was already working, however official support for the decorator was missing due to the missing wrapper support. This PR adds the wrapping and removes the warning about this decorator. Fixes pyccel#1834 **Commit Summary** - Add documentation - Prefer assertions for errors that only affect developers - Allow the setter of a property to be missing (it is then saved as `None`) - Add wrapping of a class property. The arguments of such functions are slightly different to those of a basic function. - Add `-Wno-incompatible-pointer-types` to Python flags to remove unavoidable warnings - Add tests
1 parent 9c23278 commit e5bc912

File tree

13 files changed

+393
-69
lines changed

13 files changed

+393
-69
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ All notable changes to this project will be documented in this file.
5656
- #1980 : Extend The C support for min and max to more than two variables
5757
- #2081 : Add support for multi operator expressions
5858
- Add support for inhomogeneous tuple annotations.
59+
- #1834 : Add support for `@property` decorator.
5960
- \[INTERNALS\] Add abstract class `SetMethod` to handle calls to various set methods.
6061
- \[INTERNALS\] Added `container_rank` property to `ast.datatypes.PyccelType` objects.
6162
- \[INTERNALS\] Add a `__call__` method to `FunctionDef` to create `FunctionCall` instances.

docs/classes.md

Lines changed: 228 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,233 @@ MyClass Object created!
188188
==158858== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
189189
```
190190
191+
## Class properties
192+
193+
Pyccel now supports class properties (to retrieve a constant value only).
194+
195+
### - Python Example
196+
197+
```python
198+
import numpy as np
199+
200+
class MyClass:
201+
def __init__(self, param1 : 'int', param2 : 'int'):
202+
self._param1 = param1
203+
self._param2 = np.ones(param2)
204+
print("MyClass Object created!")
205+
206+
@property
207+
def param1(self):
208+
return self._param1
209+
210+
@property
211+
def param2(self):
212+
return self._param2
213+
214+
if __name__ == '__main__':
215+
obj = MyClass1(2, 4)
216+
print(obj.param1)
217+
print(obj.param2)
218+
```
219+
220+
### PYTHON _OUTPUT_
221+
```Shell
222+
MyClass Object created!
223+
2
224+
[1. 1. 1. 1.]
225+
```
226+
227+
### - Header File Equivalent
228+
229+
```C
230+
struct MyClass {
231+
int64_t private_param1;
232+
t_ndarray private_param2;
233+
bool is_freed;
234+
};
235+
236+
void MyClass__init__(struct MyClass* self, int64_t param1, int64_t param2);
237+
int64_t MyClass__param1(struct MyClass* self);
238+
t_ndarray MyClass__param2(struct MyClass* self);
239+
void MyClass__del__(struct MyClass* self);
240+
```
241+
242+
### - C File Equivalent
243+
244+
```C
245+
/*........................................*/
246+
void MyClass__init__(struct MyClass* self, int64_t param1, int64_t param2)
247+
{
248+
self->is_freed = 0;
249+
self->private_param1 = param1;
250+
self->private_param2 = array_create(1, (int64_t[]){param2}, nd_double, false, order_c);
251+
array_fill((double)1.0, self->private_param2);
252+
printf("MyClass Object created!\n");
253+
}
254+
/*........................................*/
255+
/*........................................*/
256+
int64_t MyClass__param1(struct MyClass* self)
257+
{
258+
return self->private_param1;
259+
}
260+
/*........................................*/
261+
/*........................................*/
262+
t_ndarray MyClass__param2(struct MyClass* self)
263+
{
264+
t_ndarray Out_0001 = {.shape = NULL};
265+
alias_assign(&Out_0001, self->private_param2);
266+
return Out_0001;
267+
}
268+
/*........................................*/
269+
/*........................................*/
270+
void MyClass__del__(struct MyClass* self)
271+
{
272+
if (!self->is_freed)
273+
{
274+
// pass
275+
free_array(&self->private_param2);
276+
self->is_freed = 1;
277+
}
278+
}
279+
/*........................................*/
280+
```
281+
282+
### - C Program File Equivalent
283+
```C
284+
int main()
285+
{
286+
struct MyClass obj;
287+
t_ndarray Dummy_0000 = {.shape = NULL};
288+
int64_t i;
289+
MyClass__init__(&obj, INT64_C(2), INT64_C(4));
290+
printf("%"PRId64"\n", MyClass__param1(&obj));
291+
Dummy_0000 = MyClass__param2(&obj);
292+
printf("[");
293+
for (i = INT64_C(0); i < Dummy_0000.shape[INT64_C(0)] - INT64_C(1); i += INT64_C(1))
294+
{
295+
printf("%.15lf ", GET_ELEMENT(Dummy_0000, nd_double, i));
296+
}
297+
printf("%.15lf]\n", GET_ELEMENT(Dummy_0000, nd_double, Dummy_0000.shape[INT64_C(0)] - INT64_C(1)));
298+
MyClass__del__(&obj);
299+
free_pointer(&Dummy_0000);
300+
return 0;
301+
}
302+
```
303+
304+
### - Fortran File Equivalent
305+
306+
```fortran
307+
type, public :: MyClass
308+
integer(i64) :: private_param1
309+
real(f64), allocatable :: private_param2(:)
310+
logical(b1), private :: is_freed
311+
312+
contains
313+
procedure :: create => myclass_create
314+
procedure :: param1 => myclass_param1
315+
procedure :: param2 => myclass_param2
316+
procedure :: free => myclass_free
317+
end type MyClass
318+
319+
contains
320+
321+
322+
!........................................
323+
324+
subroutine myclass_create(self, param1, param2)
325+
326+
implicit none
327+
328+
class(MyClass), intent(inout) :: self
329+
integer(i64), value :: param1
330+
integer(i64), value :: param2
331+
332+
self%is_freed = .False._b1
333+
self%private_param1 = param1
334+
allocate(self%private_param2(0:param2 - 1_i64))
335+
self%private_param2 = 1.0_f64
336+
write(stdout, '(A)', advance="yes") 'MyClass Object created!'
337+
338+
end subroutine myclass_create
339+
340+
!........................................
341+
342+
343+
!........................................
344+
345+
function myclass_param1(self) result(Out_0001)
346+
347+
implicit none
348+
349+
integer(i64) :: Out_0001
350+
class(MyClass), intent(inout) :: self
351+
352+
Out_0001 = self%private_param1
353+
return
354+
355+
end function myclass_param1
356+
357+
!........................................
358+
359+
360+
!........................................
361+
362+
subroutine myclass_param2(self, Out_0001)
363+
364+
implicit none
365+
366+
real(f64), pointer, intent(out) :: Out_0001(:)
367+
class(MyClass), target, intent(inout) :: self
368+
369+
Out_0001(0:) => self%private_param2
370+
return
371+
372+
end subroutine myclass_param2
373+
374+
!........................................
375+
376+
377+
!........................................
378+
379+
subroutine myclass_free(self)
380+
381+
implicit none
382+
383+
class(MyClass), intent(inout) :: self
384+
385+
if (.not. self%is_freed) then
386+
! pass
387+
if (allocated(self%private_param2)) then
388+
deallocate(self%private_param2)
389+
end if
390+
self%is_freed = .True._b1
391+
end if
392+
393+
end subroutine myclass_free
394+
395+
!........................................
396+
```
397+
398+
### - Fortran Program File Equivalent
399+
400+
```fortran
401+
type(MyClass), target :: obj
402+
real(f64), pointer :: Dummy_0000(:)
403+
integer(i64) :: i
404+
405+
call obj % create(2_i64, 4_i64)
406+
write(stdout, '(I0)', advance="yes") obj % param1()
407+
call obj % param2(Out_0001 = Dummy_0000)
408+
write(stdout, '(A)', advance="no") '['
409+
do i = 0_i64, size(Dummy_0000, kind=i64) - 1_i64 - 1_i64
410+
write(stdout, '(F0.15, A)', advance="no") Dummy_0000(i) , ' '
411+
end do
412+
write(stdout, '(F0.15, A)', advance="no") Dummy_0000(size(Dummy_0000, &
413+
kind=i64) - 1_i64) , ']'
414+
write(stdout, '()', advance="yes")
415+
call obj % free()
416+
```
417+
191418
## Limitations
192419
193-
It's important to note that Pyccel does not support class inheritance, properties, magic methods or static class variables. For our first implementation, the focus of Pyccel is primarily on core class functionality and memory management.
420+
It's important to note that Pyccel does not support class inheritance, magic methods or static class variables. For our first implementation, the focus of Pyccel is primarily on core class functionality and memory management.

pyccel/ast/bind_c.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -608,10 +608,8 @@ class BindCClassProperty(PyccelAstNode):
608608
__slots__ = ('_getter', '_setter', '_python_name', '_docstring', '_class_type')
609609
_attribute_nodes = ('_getter', '_setter')
610610
def __init__(self, python_name, getter, setter, class_type, docstring = None):
611-
if not isinstance(getter, BindCFunctionDef):
612-
raise TypeError("Getter should be a BindCFunctionDef")
613-
if not isinstance(setter, BindCFunctionDef):
614-
raise TypeError("Setter should be a BindCFunctionDef")
611+
assert isinstance(getter, BindCFunctionDef)
612+
assert isinstance(setter, BindCFunctionDef) or setter is None
615613
self._python_name = python_name
616614
self._getter = getter
617615
self._setter = setter

pyccel/ast/cwrapper.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -843,10 +843,8 @@ class PyGetSetDefElement(PyccelAstNode):
843843
_attribute_nodes = ('_getter', '_setter', '_docstring')
844844
__slots__ = ('_python_name', '_getter', '_setter', '_docstring')
845845
def __init__(self, python_name, getter, setter, docstring):
846-
if not isinstance(getter, PyFunctionDef):
847-
raise TypeError("Getter should be a PyFunctionDef")
848-
if not isinstance(setter, PyFunctionDef):
849-
raise TypeError("Setter should be a PyFunctionDef")
846+
assert isinstance(getter, PyFunctionDef)
847+
assert isinstance(setter, PyFunctionDef) or setter is None
850848
self._python_name = python_name
851849
self._getter = getter
852850
self._setter = setter

pyccel/codegen/printing/cwrappercode.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ def _print_ModuleHeader(self, expr):
285285
"};\n")
286286
sig_methods = c.methods + (c.new_func,) + tuple(f for i in c.interfaces for f in i.functions) + \
287287
tuple(i.interface_func for i in c.interfaces) + \
288-
tuple(getset for p in c.properties for getset in (p.getter, p.setter))
288+
tuple(getset for p in c.properties for getset in (p.getter, p.setter) if getset)
289289
function_signatures += '\n'+''.join(self.function_signature(f)+';\n' for f in sig_methods)
290290
macro_defs += f'#define {type_name} (*(PyTypeObject*){API_var.name}[{i}])\n'
291291

@@ -393,7 +393,7 @@ def _print_PyClassDef(self, expr):
393393

394394
original_scope = expr.original_class.scope
395395
getters = tuple(p.getter for p in expr.properties)
396-
setters = tuple(p.setter for p in expr.properties)
396+
setters = tuple(p.setter for p in expr.properties if p.setter)
397397
print_methods = expr.methods + (expr.new_func,) + expr.interfaces + \
398398
getters + setters
399399
functions = '\n'.join(self._print(f) for f in print_methods)
@@ -417,13 +417,13 @@ def _print_PyClassDef(self, expr):
417417
if f.docstring else '""'
418418
funcs[py_name] = (f.name, docstring)
419419

420-
property_definitions = ''.join(('{\n'
421-
f'"{p.python_name}",\n'
422-
f'(getter) {p.getter.name},\n'
423-
f'(setter) {p.setter.name},\n'
424-
f'{self._print(p.docstring)},\n'
425-
'NULL\n'
426-
'},\n') for p in expr.properties)
420+
property_definitions = ''.join(''.join(('{\n',
421+
f'"{p.python_name}",\n',
422+
f'(getter) {p.getter.name},\n',
423+
f'(setter) {p.setter.name},\n' if p.setter else '(setter) NULL,\n',
424+
f'{self._print(p.docstring)},\n',
425+
'NULL\n',
426+
'},\n')) for p in expr.properties)
427427
property_definitions += '{ NULL }\n'
428428

429429
method_def_funcs = ''.join(('{\n'

pyccel/codegen/printing/fcode.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3735,7 +3735,7 @@ def _print_BindCArrayVariable(self, expr):
37353735

37363736
def _print_BindCClassDef(self, expr):
37373737
funcs = [expr.new_func, *expr.methods, *[f for i in expr.interfaces for f in i.functions],
3738-
*[a.getter for a in expr.attributes], *[a.setter for a in expr.attributes]]
3738+
*[a.getter for a in expr.attributes], *[a.setter for a in expr.attributes if a.setter]]
37393739
sep = f'\n{self._print(SeparatorComment(40))}\n'
37403740
return '', sep.join(self._print(f) for f in funcs)
37413741

0 commit comments

Comments
 (0)