4

I've been having an issue with dataclasses, despite using the decorator and passing the arguments to the dataclass, I will get a TypeError stating that the object does not take any arguments. This seems quite temperamental and doesn't seem to be triggered by code changes, something which is working for a colleague is not working for me (but sometimes does). We're both using Python 3.9.7 and we both code with PyCharm.

I made the switch from Windows to Ubuntu in an attempt to stop this issue but after a week or so, it is happening again. Here is my last stack trace error:

Traceback (most recent call last):
  File "/home/pedro/Documents/datacollect/Python-Shape-Game/main.py", line 188, in <module>
    shapes = load_shapes()
  File "/home/pedro/Documents/datacollect/Python-Shape-Game/main.py", line 139, in load_shapes
    return [factory.create(item) for item in data["shapes"]]
  File "/home/pedro/Documents/datacollect/Python-Shape-Game/main.py", line 139, in <listcomp>
    return [factory.create(item) for item in data["shapes"]]
  File "/home/pedro/Documents/datacollect/Python-Shape-Game/factory.py", line 28, in create
    return creation_function(**arguments)
TypeError: Rectangle() takes no arguments
pygame 2.1.0 (SDL 2.0.16, Python 3.9.7)

It is using the factory / plugin pattern to register shapes from JSON, the most notable lines in factory.py being:

shape_creation_functions: dict[str, Callable[..., Shape]] = {}
# ...
creation_function = shape_creation_functions[shape_type]
return creation_function(**arguments)

Here is the object which is failing:

class Shape(Protocol):
    """Represents a shape"""
    type: str
    rgb: list
    colour: tuple
    method: str
    positions: dict
    radius: int

    def map(self, position: str) -> Union[list[tuple[Any, Any]], tuple]:
        """Map the shape to the screen"""

@dataclass()
class Rectangle(Shape):
    """Represents a rectangle"""
    type: str
    rgb: list
    colour: tuple
    method: str
    positions: dict

def map(self, position: str) -> Union[list[tuple[Any, Any]], tuple]:
    """Draw the shape on the screen"""

    rect = (
        self.positions[position][0],
        self.positions[position][1],
        self.positions[position][2],
        self.positions[position][3]
    )

    return rect
martineau
  • 119,623
  • 25
  • 170
  • 301
  • 1
    Please share `Shape` code and that code that tries to create a `Rectangle ` – balderman Nov 14 '21 at 12:25
  • In general - what do you try to do here? create a dataclass from dict? – balderman Nov 14 '21 at 12:26
  • 1
    Add the code to the post please - not as a comment. – balderman Nov 14 '21 at 12:31
  • What is the point of the inheritance if both `Shape` and `Rectangle` come with the same attributes? – balderman Nov 14 '21 at 12:33
  • it is the last bit of code – Pedro Ribeiro Nov 14 '21 at 12:33
  • Now - "take one step back" and explain what do you try to do. – balderman Nov 14 '21 at 12:34
  • Do you understand that the Rectangle has a **radius** (coming from inheritance) ? – balderman Nov 14 '21 at 12:36
  • We are trying to load a dict with all of the arguments necessary to build the class, using factory (in the post), and the map function (just added it). It is a plugin architecture. – Pedro Ribeiro Nov 14 '21 at 12:37
  • Can you post the rest of `factory.py` – Iain Shelvington Nov 14 '21 at 12:38
  • 1
    Before we go on - can you explain the current inheritance? Why we find SAME attributes both in base class and in rect? – balderman Nov 14 '21 at 12:39
  • btw your docstring is a bit incorrect, the `map` function doesn't really draw anything to the screen but rather returns a part of what has to be drawn to the screen, also don't you inherit the docstring from the base class? – Matiiss Nov 14 '21 at 13:05
  • can you show the specific `creation_function` related to this issue? – Matiiss Nov 14 '21 at 13:13
  • @balderman I think it could be due to that in the base those are class attributes, but in the child those are instance attributes because of dataclass decorator. It seems that dataclasses behave a bit differently with inheritance perhaps – Matiiss Nov 14 '21 at 13:22
  • https://github.com/Gevie/Python-Shape-Game/ this is the repository for the project in case you want to have a look at the full code – Pedro Ribeiro Nov 14 '21 at 13:28
  • Hi guys, I'm the other contributor helping my friend out, one thing that wasn't mentioned is that this issue will happen for Pedro with a simple dataclass implementation on a fresh project. It's true that Rectangle doesn't need to have those arguments and also radius should have a default value. That's not really the issue here. The main issue is that he is unable to pass arguments to dataclasses at random intervals in time even if the code hasn't changed since his last build. – HelloSpeakman Nov 14 '21 at 13:30
  • To answer some more questions: Shape is a protocol for an unlimited number of shapes, by default four. Shape data is loaded via JSON and used to create instances. Attributes on the Rectangle class are deprecated, don't worry about it. I personally provide a doc string for every method, I have a PHP/Java background, it's for the developer at that point and could differ from an abstract class / inheritance. My contributions were in a very small window of time but none of this seems related to the issue pedro is experiencing (Ihis latest branch is fix_noob'sturn for that github link) – HelloSpeakman Nov 14 '21 at 13:47
  • latest branch is fix_noob'sturn – Pedro Ribeiro Nov 14 '21 at 13:50
  • @IainShelvington https://pastebin.com/q1eCHi9J This is the factory file. – HelloSpeakman Nov 14 '21 at 14:05
  • Final comment from me, I just checked out Pedro's branch and it ran perfectly for me (on the same versions). I haven't been involved in this project for the last couple weeks aside from feedback / PR... but it works for me but not for him. I don't think this is related to the Shape or Rectangle classes or the factory method. – HelloSpeakman Nov 14 '21 at 14:28
  • You are applying the `dataclasses` decorator incorrectly. It should be `@dataclass` *not* `@dataclass()` as shown in the [documentation](https://docs.python.org/3/library/dataclasses.html#module-dataclasses). – martineau Nov 14 '21 at 15:06
  • @martineau `@dataclass()` is the same as `@dataclass` as shown in the documentation you linked. It should be changed since we're not passing any arguments but it isn't the cause of the problem asked in the question. – HelloSpeakman Nov 15 '21 at 10:02
  • 1
    @HelloSpeakman: Yes, I see that now. Thanks and sorry for the distraction. – martineau Nov 15 '21 at 10:05

3 Answers3

5

I believe this is an issue arising from changes to the behavior of Protocol starting in Python 3.8. In particular, making a dataclass inherit from a protocol will cause problems.

The issue is that, with newer versions of Python, the inheritance from Protocol will result in Rectangle being assigned an __init__ method before being passed to dataclass. This is problematic because dataclass will not overwrite an existing method, and so your Rectangle class is never assigned an initializer that has rgb, colour, etc. as parameters.

One solution would be to make Rectangle not inherit from Shape. Since Shape does not provide any method implementations, inheritance is not needed here. (See the mypy docs for more details about structural subtyping.) Alternatively, if you would like to have Rectangle inherit from Shape, you could have Shape not inherit from Protocol.

Max Radin
  • 427
  • 1
  • 6
  • 11
4

It might not be the most relevant answer, but because it happened to me, I thought maybe it would be helpful for others.

if you're using the dataclass_json from dataclasses_json, the order in which you're annotating the class with, matters. So the order should be first @dataclass_json, and then comes @dataclass. Otherwise, you get this weird error which from the first look, it's completely unrelated. I hope it helps.

Zhivar Sourati
  • 503
  • 6
  • 9
-3

UPDATE: I could not reproduce the core of my results in new tests. I do not know what is going on here, below, see UPDATE: for the recent test. I used Python 3.8.10 in codium for both tests. Only the second example still throws at least a similar error.


The type error appears in this simple example as well:

from dataclasses import dataclass

@dataclass
class Point:
     x: int
     y: int

p = Point(10, 20)

Out:

TypeError: Point() takes no arguments

UPDATE: This runs through now.


If you add arguments, the error remains the same:

@dataclass
class Point(x=0,y=0):
     x: int
     y: int

p = Point(10, 20)

Out:

TypeError: Point() takes no arguments

This error is thrown since the attributes are not initialized (not in the constructor).

UPDATE: This throws now TypeError: __init_subclass__() takes no keyword arguments


Either, you fill the attributes step by step:

p.x = 10
p.y = 20

Or you use the following.

@dataclass
class Point:
    def __init__(self, x, y):
        x: int
        y: int

p = Point(10, 20)

UPDATE: This throws no error and gives Point() for p.


Same effect, but a bit more to write, see Data Classes:

@dataclass
class Point:
    def __init__(self, x:int, y:int):
        self.x = x
        self.y = y

p = Point(10, 20)

UPDATE: This runs through and gives Point() for p.


I guess you should just try an __init__ function like this:

class Rectangle():
    def __init__(self, Shape):
        Shape: object

And then work yourself further through the code.

questionto42
  • 7,175
  • 4
  • 57
  • 90
  • 1
    This example did not cause an error for me with Python 3.9.7. – Max Radin Jan 26 '22 at 23:44
  • @MaxRadin Good that you tested it. Might be a config issue on my side then. I do not think that the slightly different Python version will make the difference. I leave it for documentation. – questionto42 Jan 27 '22 at 00:22