103

I would like to pass default argument in my class, but somehow I am having problem:

from dataclasses import dataclass, field
from typing import List

@dataclass
class Pizza():
    ingredients: List = field(default_factory=['dow', 'tomatoes'])
    meat: str = field(default='chicken')

    def __repr__(self):
        return 'preparing_following_pizza {} {}'.format(self.ingredients, self.meat)

If I now try to instantiate Pizza, I get the following error:

>>> my_order = Pizza()
Traceback (most recent call last):
  File "pizza.py", line 13, in <module>
    Pizza()
  File "<string>", line 2, in __init__
TypeError: 'list' object is not callable

What am I doing wrong?

Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
H.Bukhari
  • 1,951
  • 3
  • 10
  • 15
  • I think the problem *may* be because you haven't created an instance of your class – N Chauhan Aug 28 '18 at 18:04
  • 1
    even with created class instance it does not work.. – H.Bukhari Aug 28 '18 at 18:08
  • No repro. It works perfectly fine with an instance. Please post a [mcve] so we can see what you're doing wrong. – Aran-Fey Aug 28 '18 at 18:11
  • 1
    @Aran-Fey did you try to write the code? As I said it doesn't work with the an instance, see my edit – H.Bukhari Aug 28 '18 at 18:14
  • Can you show use your `__init__` method? – N Chauhan Aug 28 '18 at 18:14
  • It *does* work with an instance, but you've failed to *create* an instance... – Aran-Fey Aug 28 '18 at 18:15
  • 4
    I dont want to use __init__, the whole point of @dataclass decorator is that you can skip init – H.Bukhari Aug 28 '18 at 18:16
  • @Aran-Fey Apparently, dataclasses work differently in 3.6 (external module) and 3.7 (standard module). Which version of Python do you use? I confirm that the OP's code does not work in 3.6. – DYZ Aug 28 '18 at 18:16
  • @DYZ I'm using 3.7, but I'm pretty sure it doesn't matter. You're right that the code doesn't work in the sense that you get an exception if you call the constructor without arguments; but there's no problem accessing the `ingredients` attribute of a successfully created `Pizza` instance. – Aran-Fey Aug 28 '18 at 18:19
  • @Aran-Fey But shouldn't the constructor use the default factory when called without the arguments? – DYZ Aug 28 '18 at 18:21
  • @DYZ Yes, of course, there's a bug in the code. I'm referring to [this comment](https://stackoverflow.com/questions/52063759/passing-default-argument-to-dataclasses#comment91078267_52063759) - it's simply not true. – Aran-Fey Aug 28 '18 at 18:22
  • @Aran-Fey When I attempt to create an instance with default parameters: `mypizza=Pizza()` - I get an error message, as described by the OP. Where is the bug? In the module implementation? – DYZ Aug 28 '18 at 18:25

2 Answers2

178

From the dataclasses.field docs:

The parameters to field() are:

  • default_factory: If provided, it must be a zero-argument callable that will be called when a default value is needed for this field. Among other purposes, this can be used to specify fields with mutable default values, as discussed below. It is an error to specify both default and default_factory.

Your default_factory is not a 0-argument callable but a list, which is the reason for the error:

from dataclasses import dataclass, field
from typing import List

@dataclass
class Pizza():
    ingredients: List = field(default_factory=['dow', 'tomatoes'])  # <- wrong!

Use a lambda function instead:

@dataclass
class Pizza():
    ingredients: List = field(default_factory=lambda: ['dow', 'tomatoes'])
LightCC
  • 9,804
  • 5
  • 52
  • 92
Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
22

For complex datatypes i tend to abbreviate like so:

import copy
from dataclasses import dataclass, field
from typing import Dict, Tuple

def default_field(obj):
    return field(default_factory=lambda: copy.copy(obj))

@dataclass
class C:
    complex_attribute: Dict[str, Tuple[int, str]] = default_field({"a": (1, "x"), "b": (1, "y")})
gessulat
  • 532
  • 4
  • 13
  • 10
    You should also make it `copy.copy(obj)`, otherwise it will be a shared mutable, and that's not a easy thing to debug. – HoverHell Aug 16 '19 at 14:23
  • Yes, this is really bad. Running `c1 = C(); c2 = C(); print("These are exactly the same object: ", id(c1.complex_attribute) == id(c2.complex_attribute))` will give `These are exactly the same object: True`. Please update this to use `copy.copy()`. – Harmon Nov 25 '20 at 16:06
  • Thanks for the comments. Is this update, what you mean? – gessulat Nov 26 '20 at 17:20
  • 6
    `copy.deepcopy(obj)` may be used if the values of the dictionary is mutable. In this case it's not since they are tuples. – Paaksing Feb 23 '21 at 19:18