Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dataclass defaults behave inconsistently for init=True/init=False when default is a descriptor #83128

Open
KevinShweh mannequin opened this issue Nov 30, 2019 · 3 comments
Assignees
Labels
3.7 (EOL) end of life 3.8 only security fixes stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error

Comments

@KevinShweh
Copy link
Mannequin

KevinShweh mannequin commented Nov 30, 2019

BPO 38947
Nosy @ericvsmith, @PCManticore, @ilevkivskyi, @iritkatriel

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = 'https://github.com/ericvsmith'
closed_at = None
created_at = <Date 2019-11-30.23:53:26.972>
labels = ['3.7', '3.8', 'type-bug', 'library']
title = 'dataclass defaults behave inconsistently for init=True/init=False when default is a descriptor'
updated_at = <Date 2020-10-18.23:48:06.146>
user = 'https://bugs.python.org/KevinShweh'

bugs.python.org fields:

activity = <Date 2020-10-18.23:48:06.146>
actor = 'iritkatriel'
assignee = 'eric.smith'
closed = False
closed_date = None
closer = None
components = ['Library (Lib)']
creation = <Date 2019-11-30.23:53:26.972>
creator = 'Kevin Shweh'
dependencies = []
files = []
hgrepos = []
issue_num = 38947
keywords = []
message_count = 3.0
messages = ['357669', '378913', '378914']
nosy_count = 6.0
nosy_names = ['falsetru', 'eric.smith', 'Claudiu.Popa', 'levkivskyi', 'Kevin Shweh', 'iritkatriel']
pr_nums = []
priority = 'normal'
resolution = None
stage = None
status = 'open'
superseder = None
type = 'behavior'
url = 'https://bugs.python.org/issue38947'
versions = ['Python 3.7', 'Python 3.8']

@KevinShweh
Copy link
Mannequin Author

KevinShweh mannequin commented Nov 30, 2019

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.

@KevinShweh KevinShweh mannequin added 3.7 (EOL) end of life 3.8 only security fixes stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error labels Nov 30, 2019
@ericvsmith ericvsmith self-assigned this Dec 1, 2019
@iritkatriel
Copy link
Member

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?

@iritkatriel
Copy link
Member

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

@ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.7 (EOL) end of life 3.8 only security fixes stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

2 participants