Home > Back-end >  Dataclass and Callable Initialization Problem via Classmethods
Dataclass and Callable Initialization Problem via Classmethods

Time:01-08

I found this weird behaviour where I don't know if I am the problem or if this is a python / dataclass / callable bug.

Here is a minimal working example

from dataclasses import dataclass
from typing import Callable
import numpy as np


def my_dummy_callable(my_array, my_bool):
    return 1.0


@dataclass()
class MyDataClassDummy:

    my_data: int = 1
    my_callable: Callable[[np.ndarray, bool], float] = my_dummy_callable

    def __init__(self):
        print("I initialized my Class!")

    @classmethod
    def my_factory_with_callable_setting(cls):
        my_dummy = MyDataClassDummy()
        my_dummy.my_callable = my_dummy_callable
        return my_dummy

    @classmethod
    def my_factory_without_callable_setting(cls):
        my_dummy = MyDataClassDummy()
        return my_dummy

    def do_something(self):
        print("This is my data", self.my_data)
        print("This is the name of my callable", str(self.my_callable))
        return self.my_callable(np.empty(shape=(42, 42)), True)   self.my_data


@dataclass()
class MySecondDataClassDummy:
    my_data: int = 4
    my_callable: Callable[[np.ndarray, bool], float] = my_dummy_callable

    @classmethod
    def my_factory(cls):
        my_dummy = MySecondDataClassDummy()
        return my_dummy

    def do_something(self):
        print("This is my data", self.my_data)
        print("This is the name of my callable", str(self.my_callable))
        return self.my_callable(np.empty(shape=(42, 42)), True) - self.my_data


if __name__ == '__main__':
    # this works
    my_first_dummy = MyDataClassDummy.my_factory_with_callable_setting()
    my_first_dummy.do_something()

    # this also works
    my_second_dummy = MySecondDataClassDummy.my_factory()
    my_second_dummy.do_something()

    # this does not work
    my_other_dummy = MyDataClassDummy.my_factory_without_callable_setting()
    my_other_dummy.do_something()

case1: initialize with factory, initialize with my own init and then set the callable explicitly after initialization (allthough there is a default value) - works

case2: initialize with factory but don't explicitly code the init() myself - works

case3: initialize with factory, initialize with my own init and not set the callable explicitly after initialization (because this is why I have default values, isn't it?!) - doesn't work but throws the error:

Traceback (most recent call last):
  File "my_path/dataclass_dummy.py", line 63, in <module>
    my_other_dummy.do_something()
  File "my_path/dataclass_dummy.py", line 33, in do_something
    return self.my_callable(np.empty(shape=(42, 42)), True)   self.my_data
TypeError: my_dummy_callable() takes 2 positional arguments but 3 were given

So now I am wondering, what I am doing wrong in the third case.

I am using Python 3.8 and numpy 1.20.2

CodePudding user response:

The @dataclass decorator by default supplies an __init__() method to a class. This method turns type annotated class variables into attributes of instances of the class. This mechanism is used in the case of the class MySecondDataClassDummy. In effect, every instance of this class has an attribute my_callable. Since this attribute is a function, you can call it as you do in Case 2, and everything works.

The class MyDataClassDummy has its own __init__() method, which overrides __init__() provided by @dataclass. Instances of this class are then initialized more or less as they would be without the @dataclass decorator. In particular, class variables that are functions become bound methods of class instances. As a result, my_callable becomes such a bound method, and when in Case 3 you execute

self.my_callable(np.empty(shape=(42, 42)), True)  

then self is used as the first argument of my_callable. Since this function takes only two arguments, it generates an error.

The same problem does not occur in Case 1, since in this case you modify my_dummy.my_callable making it an attribute of my_dummy whose value is a function. After this modification, it is not a bound method anymore.

  •  Tags:  
  • Related