This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: dataclass defaults behave inconsistently for init=True/init=False when default is a descriptor
Type: behavior Stage:
Components: Library (Lib) Versions: Python 3.8, Python 3.7
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: eric.smith Nosy List: Claudiu.Popa, Kevin Shweh, eric.smith, falsetru, iritkatriel, levkivskyi
Priority: normal Keywords:

Created on 2019-11-30 23:53 by Kevin Shweh, last changed 2022-04-11 14:59 by admin.

Messages (3)
msg357669 - (view) Author: Kevin Shweh (Kevin Shweh) Date: 2019-11-30 23:53
The following code:

    from dataclasses import dataclass, field
    from typing import Callable
     
    @dataclass
    class Foo:
    	callback: Callable[[int], int] = lambda x: x**2
     
    @dataclass
    class Bar:
    	callback: Callable[[int], int] = field(init=False, default=lambda x: x**2)
     
    print(Foo().callback(2))
    print(Bar().callback(2))

prints 4 for the first print, but throws a TypeError for the second. This is because Foo() stores the default callback in the instance dict, while Bar() only has it in the class dict. Bar().callback triggers the descriptor protocol and produces a method object instead of the original callback.

There does not seem to be any indication in the dataclasses documentation that these fields will behave differently. It seems like they should behave the same, and/or the documentation should be clearer about how the default value/non-init field interaction behaves.
msg378913 - (view) Author: Irit Katriel (iritkatriel) * (Python committer) Date: 2020-10-18 23:11
If I change Foo in your code to:

    @dataclass(init=False)
    class Foo:
    	callback: Callable[[int], int] = lambda x: x**2

Then the same TypeError exception is raised for Foo. 

If I then change it back (remove the init=False so that it picks up the default value of True), and then change init=True in field:

@dataclass
class Bar:
    callback: Callable[[int], int] = field(init=True, default=lambda x: x**2)

Then I get the expected output of 
4
4

What do you consider to be an inconsistency here?
msg378914 - (view) Author: Irit Katriel (iritkatriel) * (Python committer) Date: 2020-10-18 23:48
I think you may have meant this issue (which is not related to field()):

from dataclasses import dataclass
from typing import Callable

@dataclass(init=True)
class Foo:
    callback: Callable[[int], int] = lambda x: x**2
     
@dataclass(init=False)
class Bar:
    callback: Callable[[int], int] = lambda x: x**2

print('Foo().callback:', Foo().callback)
print('Foo().callback(2):', Foo().callback(2))

print('Bar().callback:', Bar().callback)
print('Bar().callback(3):', Bar().callback(3))

Output:
Foo().callback: <function Foo.<lambda> at 0x019592F8>
Foo().callback(2): 4
Bar().callback: <bound method Bar.<lambda> of Bar(callback=<bound method Bar.<lambda> of ...>)>
Traceback (most recent call last):
  File "C:\Users\User\src\cpython\x.py", line 17, in <module>
    print('Bar().callback(3):', Bar().callback(3))
TypeError: Bar.<lambda>() takes 1 positional argument but 2 were given
History
Date User Action Args
2022-04-11 14:59:23adminsetgithub: 83128
2020-10-18 23:48:06iritkatrielsetmessages: + msg378914
2020-10-18 23:11:28iritkatrielsetnosy: + iritkatriel
messages: + msg378913
2019-12-12 23:47:54falsetrusetnosy: + falsetru
2019-12-10 15:29:14Claudiu.Popasetnosy: + Claudiu.Popa
2019-12-06 22:47:09levkivskyisetnosy: + levkivskyi
2019-12-01 12:45:43eric.smithsetassignee: eric.smith
2019-12-01 03:40:07xtreaksetnosy: + eric.smith
2019-11-30 23:53:26Kevin Shwehcreate