Skip to content

Bug when using functools.partial with FunctionExecutor.map()Β #1428

@TomNicholas

Description

@TomNicholas

Lithops is incorrectly raising anytime I try to pass in a function decorated with functools.partial.

It complains that the function I decorated does not have a __call__ method

import functools
import lithops

fexec = lithops.FunctionExecutor()

iterable = ["foo", "bar"]
kwargs = {}

def f(s: str, **kwargs):
    return s.upper()

futures = fexec.map(f, iterable, **kwargs)  # this works fine
print(fexec.get_result(futures))

partial_f = functools.partial(f, **kwargs)
futures = fexec.map(partial_f, iterable)  # this fails
print(fexec.get_result(futures))
.venv/lib/python3.13/site-packages/lithops/executors.py:259: in map
    job = create_map_job(
.venv/lib/python3.13/site-packages/lithops/job/job.py:81: in create_map_job
    job = _create_job(
.venv/lib/python3.13/site-packages/lithops/job/job.py:245: in _create_job
    func_and_data_ser, mod_paths = serializer([func] + iterdata, inc_modules, exc_modules)
.venv/lib/python3.13/site-packages/lithops/job/serialize.py:73: in __call__
    ref_modules.update(self._module_inspect(obj))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <lithops.job.serialize.SerializeIndependent object at 0x12138c690>, obj = functools.partial(<function test_lithops_functools_partial_bug.<locals>.f at 0x1214ef7e0>)

    def _module_inspect(self, obj):
        """
        inspect objects for module dependencies
        """
        worklist = []
        seen = set()
        mods = set()
    
        if inspect.isfunction(obj) or (inspect.ismethod(obj) and inspect.isfunction(obj.__func__)):
            # The obj is the user's function
            worklist.append(obj)
    
        elif type(obj).__name__ == 'cython_function_or_method':
            for k, v in linspect.getmembers_static(obj):
                if k == '__globals__':
                    mods.add(v['__file__'])
    
        elif type(obj) is dict:
            # the obj is the user's iterdata
            for param in obj.values():
                if type(param).__module__ == "__builtin__":
                    continue
                elif inspect.isfunction(param):
                    # it is a user defined function
                    worklist.append(param)
                else:
                    # it is a user defined class
                    for k, v in linspect.getmembers_static(param):
                        if inspect.isfunction(v) or (inspect.ismethod(v) and inspect.isfunction(v.__func__)):
                            worklist.append(v)
        else:
            # The obj is the user's function but in form of a class
            found_methods = []
            for k, v in linspect.getmembers_static(obj):
                if inspect.isfunction(v) or (inspect.ismethod(v) and inspect.isfunction(v.__func__)):
                    found_methods.append(k)
                    worklist.append(v)
            if "__call__" not in found_methods:
>               raise Exception('The class you passed as the function to '
                                'run must contain the "__call__" method')
E               Exception: The class you passed as the function to run must contain the "__call__" method

But it definitely does have a __call__ method:

print(dir(partial_f))
['__call__', '__class__', '__class_getitem__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__vectorcalloffset__', 'args', 'func', 'keywords']

I think this indicates that this line is not general enough

if inspect.isfunction(v) or (inspect.ismethod(v) and inspect.isfunction(v.__func__)):

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions