Search code examples
pythonclassinstance

Missing callable argument even if provided


Given the following:

from dataclasses import dataclass
from typing import Callable

class Bar:
    def bar_meth():
        return [1, 2, 3]

class Foo(Bar):
    @staticmethod
    def foo_meth(a: int, b: int, base_func: Callable, *args):
        # sample code, it doesn't really matter as I'm sure this part works
        items = []
        for x in range(a, b):
            items.extend(base_func(args))

        return items

@dataclass
class Source:
    call: Callable = None
    args: tuple = None
    kwargs: dict = None

    def run(self):
        return self.call(self.args, self.kwargs)

class Binder:
    def __init__(self):
        self.source = Source()

    def bind(self, source: Callable, *args, **kwargs):
        self.source = Source(source, args, kwargs)

        return self.source

    def check(self):
        return self.source.run()
# main.py
a = Foo()
b = Binder()

b.bind(a.foo_meth, 0, 50, a.bar_meth)
b.check()

All of this mess raises TypeError: foo_meth() missing 1 required positional argument: 'base_func' at return self._call(self._args, self._kwargs). I checked with the debugger and b.args = (arg1, arg2, <bound method Bar.bar_meth of <object and 0xAddress>>), so bar_meth() should be passed correctly.

I know this looks quite convoluted but I don't know how I could improve it, so if you don't have a solution perhaps you could offer some rework suggestions.


Solution

  • You need to unpack self.args and self.kwargs when you do self.call() in Source.run().

    Change the function to:

    def run(self):
        return self.call(*self.args, **self.kwargs)
    

    Looks like you also forgot to unpack the arguments to base_func() inside Foo.foo_meth()

    def foo_meth(a: int, b: int, base_func: Callable, *args):
            # sample code, it doesn't really matter as I'm sure this part works
            items = []
            for x in range(a, b):
                items.extend(base_func(*args)) # <<------ Unpack here too
    
            return items
    

    Now your MRE gives a different error --

    TypeError: bar_meth() takes 0 positional arguments but 1 was given
    

    This is because you defined Bar.bar_meth() without any arguments, but you call it as a.bar_meth(args). You will need to redefine it as

    class Bar:
        def bar_meth(self):
            return [1, 2, 3]