Task Selection
As in the case of Java, a COMPSs Python application is a Python sequential program that contains calls to tasks. In particular, the user can select as a task:
Functions
Instance methods: methods invoked on objects.
Class methods: static methods belonging to a class.
The task definition in Python is done by means of Python decorators
instead of an annotated interface. In particular, the user needs to add
a @task
decorator that describes the task before the
definition of the function/method.
As an example (Code 19), let us assume that the application calls a function func, which receives a file path (string parameter) and an integer parameter. The code of func updates the file.
def func(file_path, value):
# update the file 'file_path'
def main():
my_file = '/tmp/sample_file.txt'
func(my_file, 1)
if __name__ == '__main__':
main()
In order to select func
as a task, the corresponding @task
decorator needs to be placed right before the definition of the
function, providing some metadata about the parameters of that function.
The @task
decorator has to be imported from the pycompss
library (Code 20).
from pycompss.api.task import task
@task()
def func():
...
Tip
The PyCOMPSs task api also provides the @task
decorator in camelcase
(@Task
) with the same functionality.
The rationale of providing both @task
and @Task
relies on following
the PEP8 naming convention. Decorators are usually defined using lowercase,
but since the task decorator is implemented following the class pattern,
its name is also available as camelcase.
Function parameters
The @task decorator does not interfere with the function parameters, Consequently, the user can define the function parameters as normal python functions (Code 21).
@task()
def func(param1, param2):
...
The use of *args and **kwargs as function parameters is supported (Code 22).
@task(returns=int)
def argkwarg_func(*args, **kwargs):
...
And even with other parameters, such as usual parameters and default defined arguments. Code 23 shows an example of a task with two three parameters (whose one of them (āsā) has a default value), *args and **kwargs.
@task(returns=int)
def multiarguments_func(v, w, s=2, *args, **kwargs):
...
Tasks within classes
Functions within classes can also be declared as tasks as normal functions.
The main difference is the existence of the self
parameter which enables
to modify the callee object.
For tasks corresponding to instance methods, by default the task is assumed to modify the callee object (the object on which the method is invoked). The programmer can tell otherwise by setting the target_direction argument of the @task decorator to IN (Code 24).
class MyClass(object):
...
@task(target_direction=IN)
def instance_method(self):
... # self is NOT modified here
Class methods and static methods can also be declared as tasks. The only
requirement is to place the @classmethod
or @staticmethod
over
the @task decorator (Code 25).
Note that there is no need to use the target_direction flag within the
@task decorator.
class MyClass(object):
...
@classmethod
@task()
def class_method(cls, a, b, c):
...
@staticmethod
@task(returns=int)
def static_method(a, b, c):
...
Tip
Tasks inheritance and overriding supported!!!
Caution
The objects used as task parameters MUST BE serializable:
Implement the
__getstate__
and__setstate__
functions in their classes for those objects that are not automatically serializable.The classes must not be declared in the same file that contains the main method (
if __name__=='__main__'
) (known pickle issue).
Important
For instances of user-defined classes, the classes of these objects should have an empty constructor, otherwise the programmer will not be able to invoke task instance methods on those objects (Code 26).
# In file utils.py
from pycompss.api.task import task
class MyClass(object):
def __init__(self): # empty constructor
...
@task()
def yet_another_task(self):
# do something with the self attributes
...
...
# In file main.py
from pycompss.api.task import task
from utils import MyClass
@task(returns=MyClass)
def ret_func():
...
myc = MyClass()
...
return myc
def main():
o = ret_func()
# invoking a task instance method on a future object can only
# be done when an empty constructor is defined in the object's
# class
o.yet_another_task()
if __name__=='__main__':
main()