Search code examples
pythonclasspartialfunctools

Surprising behaviour when using functools.partial in default argument inside a class


class A:

    def _add(a, b):
        return a + b
    
    def f_add():
        return _add
    
    def lambda_add():
        return lambda x, y: _add(x, y)
    
    def lambda_arg_add(l=lambda x, y: _add(x, y)):
        return l

    def partial_add():
        return partial(_add)
    
    def partail_arg_add(f=partial(_add)):
        return f


A.f_add()(1, 2)

A.lambda_add()(1, 2)

A.lambda_arg_add()(1, 2)

A.partial_add()(1, 2)

A.partail_arg_add()(1, 2)

All the function calls except the last one raise an error saying _add is not defined but the call to partail_arg_add executes successfully. What is the logic behind this? Why does the interpreter know where to look for _add when creating the partial function but only when it's done as a default argument?


Solution

  • functools.partial is not involved in this behavior.

    This is because the default arg is created at the function definition, during the class definition, which occur in the scope of the class, so you don't need any "self.".

    This is why this works :

    class Example:
        def A(self):
            pass
    
        def B(self, x=print(A, 1)):
            print(self.A, 2)
    
        print(A, 3)
    

    Output:

    <function Example.A at 0x7fcf8ebc5950> 1
    <function Example.A at 0x7fcf8ebc5950> 3
    

    Even without calling any Example.B(), it should print the <function Example.A at 0x7fcf8ebc5950> 1 and 3 when the class is defined.

    But you can't create a partial with a method of a non-yet created class

    Or, maybe you can, but I am not aware of any way to do it.

    I tried to fix you class:

    class A:
        @staticmethod
        def _add(a, b):
            return a + b
    
        @classmethod
        def f_add(cls):  # valid
            return cls._add
    
        @classmethod
        def lambda_add(cls):  # valid
            return lambda x, y: cls._add(x, y)
    
        @staticmethod
        def lambda_arg_add(l=lambda x, y: A._add(x, y)):  # valid
            return l
    
        @classmethod
        def partial_add(cls):  # valid
            return partial(cls._add)
    
        @staticmethod
        def partial_arg_add(f=partial(_add)):  # TypeError: 'staticmethod' object is not callable
            return f
    

    But partial_arg_add will fail because _add is not callable yet when the partial is created :

    class A:
        @staticmethod
        def _add(a, b):
            return a + b
    
        print(_add(1, 3))  # TypeError: 'staticmethod' object is not callable
    
    class A:
        @staticmethod
        def _add(a, b):
            return a + b
    
    
    print(A._add(1, 3))  # 4