Title: __function.__defaults__ breaks for __init__ of dataclasses with default factory
Type: behavior Stage:
Components: C API Versions:
Status: open Resolution:
Dependencies: Superseder:
Assigned To: eric.smith Nosy List: RunOrVeith, eric.smith
Priority: normal Keywords:

Created on 2019-12-17 17:51 by RunOrVeith, last changed 2019-12-17 21:08 by eric.smith.

Messages (3)
msg358562 - (view) Author: Veith Röthlingshöfer (RunOrVeith) Date: 2019-12-17 17:51
When creating a dataclass with a default that is a field with a default factory, the factory is not correctly resolved in cls.__init__.__defaults__. It evaluates to the __repr__ of dataclasses._HAS_DEFAULT_FACTORY_CLASS, which is "<factory>".

The expected behavior would be to have a value of whatever the default factory produces as a default.

This causes issues for example when using inspect.BoundParameters.apply_defaults() on the __init__ of such a dataclass.

Code to reproduce:

from dataclasses import dataclass, field
from typing import Any, Dict

class Test:
    a: int
    b: Dict[Any, Any] = field(default_factory=dict)

print(Test.__init__.__defaults__)  # <factory>

The affected packages are on a high-level dataclasses, on a lower level the issue is in the builtin __function.__defaults__.
msg358568 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2019-12-17 19:52
The problem is that __init__ has to have a sentinel to know whether or not to call the default value. If the default were just "dict", should it call it, or is the passed in value really dict, in which case it doesn't get called?

class Test:
    a: int
    b: Dict[Any, Any] = field(default_factory=dict)

The generated __init__ looks like:

def __init__(self, a, b=_HAS_DEFAULT_FACTORY):
   self.a = a
   self.b = dict() if b is _HAS_DEFAULT_FACTORY else b

If it were:

def __init__(self, a, b=dict):
   self.a = a

Then what would the assignment to self.b look like? What if you instantiated an object as Test(0, dict)? You wouldn't want dict to get called. You need to differentiate between Test(0, dict) and Test(0). The former does not call b, but the latter does call b.
msg358577 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2019-12-17 21:08
I guess I could make the default value something like _CALLABLE(dict), but I'm not sure that would do you any good. But at least you could tell from the repr what it is.
Date User Action Args
2019-12-17 21:08:15eric.smithsetmessages: + msg358577
2019-12-17 19:52:54eric.smithsetmessages: + msg358568
2019-12-17 17:54:02eric.smithsetassignee: eric.smith

nosy: + eric.smith
2019-12-17 17:51:54RunOrVeithcreate