The Wayback Machine - https://web.archive.org/web/20230627230356/https://github.com/python/cpython/issues/91393
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

Inheritance from base class with property in class makes them non-instantiatable #91393

Open
Germandrummer92 mannequin opened this issue Apr 6, 2022 · 8 comments
Open
Labels
3.10 only security fixes stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error

Comments

@Germandrummer92
Copy link
Mannequin

Germandrummer92 mannequin commented Apr 6, 2022

BPO 47237
Nosy @gvanrossum, @ericvsmith, @JelleZijlstra, @Fidget-Spinner, @AlexWaygood, @Germandrummer92

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 = None
closed_at = None
created_at = <Date 2022-04-06.08:41:16.184>
labels = ['type-bug', 'library', '3.10']
title = 'Inheritance from base class with property in class makes them non-instantiatable'
updated_at = <Date 2022-04-06.15:54:34.248>
user = 'https://github.com/Germandrummer92'

bugs.python.org fields:

activity = <Date 2022-04-06.15:54:34.248>
actor = 'eric.smith'
assignee = 'none'
closed = False
closed_date = None
closer = None
components = ['Library (Lib)']
creation = <Date 2022-04-06.08:41:16.184>
creator = 'Germandrummer92'
dependencies = []
files = []
hgrepos = []
issue_num = 47237
keywords = []
message_count = 5.0
messages = ['416846', '416853', '416880', '416881', '416882']
nosy_count = 6.0
nosy_names = ['gvanrossum', 'eric.smith', 'JelleZijlstra', 'kj', 'AlexWaygood', 'Germandrummer92']
pr_nums = []
priority = 'normal'
resolution = None
stage = None
status = 'open'
superseder = None
type = 'behavior'
url = 'https://bugs.python.org/issue47237'
versions = ['Python 3.10']

@Germandrummer92
Copy link
Mannequin Author

Germandrummer92 mannequin commented Apr 6, 2022

Hi,

According to https://peps.python.org/pep-0544/#explicitly-declaring-implementation it should be possible to explicitly inherit from Protocols. This however breaks the dataclass constructor when using the @Property decorator in the protocol, see this example:

from typing import Protocol
from dataclasses import dataclass

class SomeProtocol(Protocol):
    @property
    def some_value(self) -> str: ...

@dataclass
class SomeDataclasss(SomeProtocol):
    some_value: str

if __name__ == '__main__':
    a = SomeDataclasss(some_value="value") # this crashes with AttributeError: can't set attribute 'some_value'

The pattern of @Property in the protocol is one taken from the mypy docs (see https://mypy.readthedocs.io/en/stable/protocols.html#recursive-protocols for example).

When removing the explicit inheritiance mypy also correctly typechecks the dataclass implementation when doing something like, only the explicit inheritance seems to fail in python

a: SomeProtocol = SomeDataclass()

@Germandrummer92 Germandrummer92 mannequin added 3.10 only security fixes stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error labels Apr 6, 2022
@ericvsmith
Copy link
Member

Here's the error without dataclasses:

------

from typing import Protocol

class SomeProtocol(Protocol):
    @property
    def some_value(self) -> str: ...

class SomeClass(SomeProtocol):
    def __init__(self, some_value):
        self.some_value = some_value

if __name__ == '__main__':
    a = SomeClass(some_value="value")

Traceback (most recent call last):
  File "foo.py", line 12, in <module>
    a = SomeClass(some_value="value")
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "foo.py", line 9, in __init__
    self.some_value = some_value
    ^^^^^^^^^^^^^^^
AttributeError: property 'some_value' of 'SomeClass' object has no setter

And here it is without Protocol:
--------

class SomeProperty:
    @property
    def some_value(self) -> str: ...

class SomeClass(SomeProperty):
    def __init__(self, some_value):
        self.some_value = some_value

if __name__ == '__main__':
    a = SomeClass(some_value="value")

Traceback (most recent call last):
  File "foo.py", line 10, in <module>
    a = SomeClass(some_value="value")
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "foo.py", line 7, in __init__
    self.some_value = some_value
    ^^^^^^^^^^^^^^^
AttributeError: property 'some_value' of 'SomeClass' object has no setter

@ericvsmith ericvsmith changed the title Inheritance from Protocol with property in dataclass makes them non-instantiatable Inheritance from Protocol with property in class makes them non-instantiatable Apr 6, 2022
@ericvsmith ericvsmith changed the title Inheritance from Protocol with property in dataclass makes them non-instantiatable Inheritance from Protocol with property in class makes them non-instantiatable Apr 6, 2022
@ericvsmith ericvsmith changed the title Inheritance from Protocol with property in class makes them non-instantiatable Inheritance from base class with property in class makes them non-instantiatable Apr 6, 2022
@ericvsmith ericvsmith changed the title Inheritance from Protocol with property in class makes them non-instantiatable Inheritance from base class with property in class makes them non-instantiatable Apr 6, 2022
@gvanrossum
Copy link
Member

So is the conclusion that this should be closed as "not a bug"?

@JelleZijlstra
Copy link
Member

I think the behavior with regular classes is expected (that's just how inheritance works), but a case could be made that dataclasses should handle this case specially.

@ericvsmith
Copy link
Member

What would dataclasses do that's different from a regular class?

@ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
@Kentzo
Copy link
Contributor

Kentzo commented Oct 22, 2022

With CPython 3.10.7 and mypy 0.982

import abc
import dataclasses as dc
from typing import Protocol


class Proto(Protocol):
    @property
    @abc.abstractmethod
    def prop(self) -> int:
        raise NotImplementedError


@dc.dataclass
class Implicit:
    prop: int


@dc.dataclass
class Explicit(Proto):
    prop: int


x: Proto = Implicit(42)
y: Proto = Explicit(42)

The results are:

$ python test.py
Traceback (most recent call last):
  File "/private/tmp/test.py", line 24, in <module>
    y: Proto = Explicit(42)
TypeError: Can't instantiate abstract class Explicit with abstract method prop     

$ mypy test.py 
Success: no issues found in 1 source file

@gvanrossum
Copy link
Member

I see this as a perhaps surprising result of how all these things work, not as a bug. Note that Protocol isn't needed, the same error is given if Proto doesn't inherit from Protocol. A workaround would be to set a default value in the Explicit subclass (prop: int = 0). But mostly my view is "don't do that".

@Kentzo
Copy link
Contributor

Kentzo commented Oct 22, 2022

Another workaround that removes inheritance but preserves explicit type checking at the place of definition:

if TYPE_CHECKING:
    _: type[Proto] = Explicit

@dc.dataclass
class Explicit:
    prop: int

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.10 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

4 participants