classification
Title: dataclasses.replace broken if a class has any ClassVars
Type: behavior Stage: resolved
Components: Library (Lib) Versions: Python 3.8, Python 3.7
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: eric.smith Nosy List: eric.smith, miss-islington, ned.deily, sigurd
Priority: normal Keywords: patch

Created on 2018-06-07 14:34 by sigurd, last changed 2018-06-07 20:30 by eric.smith. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 7488 merged eric.smith, 2018-06-07 17:58
PR 7493 merged miss-islington, 2018-06-07 19:57
Messages (9)
msg318942 - (view) Author: Sigurd Ljødal (sigurd) Date: 2018-06-07 14:34
The dataclasses.replace function does not work for classes that have class variables. See the console output below for an example.

$ python
Python 3.7.0b5+ (heads/3.7:3c417610ad, Jun  7 2018, 16:21:29) 
[Clang 9.1.0 (clang-902.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import typing
>>> import dataclasses
>>> @dataclasses.dataclass(frozen=True)
... class Test:
...   a: int
...   class_var: typing.ClassVar[str] = 'foo'
... 
>>> obj = Test(a=1)
>>> dataclasses.replace(obj, a=2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/sigurdljodal/.pyenv/versions/3.7-dev/lib/python3.7/dataclasses.py", line 1179, in replace
    return obj.__class__(**changes)
TypeError: __init__() got an unexpected keyword argument 'class_var'
msg318948 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2018-06-07 15:15
Thanks for the report. This is the same error you get when using any non-field:

>>> @dataclass
... class C:
...   i: int
...
>>> c = C(4)
>>> replace(c, i=3)
C(i=3)
>>> replace(c, j=3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\home\eric\local\python\cpython\lib\dataclasses.py", line 1179, in replace
    return obj.__class__(**changes)
TypeError: __init__() got an unexpected keyword argument 'j'

I think the TypeError is correct in both cases. The error message might not be the best.

Are you suggesting that this shouldn't raise a TypeError? Since a ClassVar is not an instance variable, I don't think it makes any sense to replace() it.
msg318949 - (view) Author: Sigurd Ljødal (sigurd) Date: 2018-06-07 15:40
I think you misunderstood the issue here. I'm not trying to replace the class variable itself, I'm changing another field on a class that has a class variable. The problem is that the replace method includes the class variable in the change dict when copying fields that are not included in the function call.

My call is: dataclasses.replace(obj, a=2) where I try to replace a, but it fails because class_var is included in the attributes to __init__ by the replace function.

The problem is on this line: https://github.com/python/cpython/blob/master/Lib/dataclasses.py#L1162
The _FIELDS attribute includes the class variable as a field, and it is therefore added to the changes dict.
msg318950 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2018-06-07 15:48
Ah, you're right. I did misunderstand. Thanks for correcting me. PR soon.
msg318955 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2018-06-07 16:39
Ned: How do you feel about backporting this to 3.7?

It's an unfortunate bug, but it's your call for 3.7.0 vs. 3.7.1.
msg318959 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2018-06-07 18:44
New changeset e7adf2ba41832404100313f9ac9d9f7fabedc1fd by Eric V. Smith in branch 'master':
bpo-33796: Ignore ClassVar for dataclasses.replace(). (GH-7488)
https://github.com/python/cpython/commit/e7adf2ba41832404100313f9ac9d9f7fabedc1fd
msg318966 - (view) Author: Ned Deily (ned.deily) * (Python committer) Date: 2018-06-07 19:32
Let's fix it now.
msg318970 - (view) Author: miss-islington (miss-islington) Date: 2018-06-07 20:15
New changeset 0aee3bea197af51de3a30e4665eaa2971a681fbb by Miss Islington (bot) in branch '3.7':
bpo-33796: Ignore ClassVar for dataclasses.replace(). (GH-7488)
https://github.com/python/cpython/commit/0aee3bea197af51de3a30e4665eaa2971a681fbb
msg318971 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2018-06-07 20:30
Thanks, Ned. I've backported it.
History
Date User Action Args
2018-06-07 20:30:17eric.smithsetstatus: open -> closed
priority: release blocker -> normal

components: + Library (Lib)
messages: + msg318971
type: behavior
resolution: fixed
stage: patch review -> resolved
2018-06-07 20:15:31miss-islingtonsetnosy: + miss-islington
messages: + msg318970
2018-06-07 19:57:48miss-islingtonsetpull_requests: + pull_request7118
2018-06-07 19:32:04ned.deilysetmessages: + msg318966
2018-06-07 18:44:09eric.smithsetmessages: + msg318959
2018-06-07 17:58:23eric.smithsetkeywords: + patch
stage: patch review
pull_requests: + pull_request7113
2018-06-07 16:39:20eric.smithsetmessages: + msg318955
2018-06-07 15:57:06eric.smithsettitle: dataclasses.replace broken with class variables -> dataclasses.replace broken if a class has any ClassVars
versions: + Python 3.8
2018-06-07 15:48:24eric.smithsetpriority: normal -> release blocker
nosy: + ned.deily
messages: + msg318950

2018-06-07 15:40:26sigurdsetmessages: + msg318949
2018-06-07 15:15:29eric.smithsetmessages: + msg318948
2018-06-07 14:55:09eric.smithsetassignee: eric.smith

nosy: + eric.smith
2018-06-07 14:34:49sigurdcreate