3

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

1
  • It's just @dataclass and not @dataclass() if no arguments are passed to the decorator, as in your case you are not passing any arguments to it. It looks nicer without the parentheses. Just saying. Commented Jun 3, 2022 at 4:13

1 Answer 1

4

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.

Sign up to request clarification or add additional context in comments.

1 Comment

That makes sense and is somehow what I thought I would be missing, thanks!. So If I implement the __post_init__ function instead of the __init__ function in MyDataClassDummy to add my own initialization Code, then all three cases work. (I found the __post_init__ here)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.