-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
abstract dataclass inheritance gives Only concrete class can be given where "Type[Abstract]" is expected
#5374
Comments
What if you remove the meta class?
On Wed, Jul 18, 2018 at 9:01 PM Jonas Obrist ***@***.***> wrote:
mypy 0.620, --strict.
Sample code:
import abcfrom dataclasses import dataclass
@DataClass # error: Only concrete class can be given where "Type[Abstract]" is expectedclass Abstract(metaclass=abc.ABCMeta):
a: str
@abc.abstractmethod
def c(self) -> None:
pass
@dataclassclass Concrete(Abstract):
b: str
def c(self) -> None:
pass
instance = Concrete(a='hello', b='world')
Note that this error *only* occurs if at least one abstract method is
defined on the abstract class. Removing the abstract method c (or making
it non-abstract) makes the error go away.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#5374>, or mute the thread
<https://github.com/notifications/unsubscribe-auth/ACwrMmnqdQS-PT-Ppma35aXOsPpD6X7vks5uIASAgaJpZM4VVt0v>
.
--
--Guido (mobile)
|
Removing the metaclass still triggers it. Minimal code to get that error: import abc
from dataclasses import dataclass
@dataclass
class Abstract:
@abc.abstractmethod
def c(self) -> None:
pass |
The underlying cause is this: from typing import Type, TypeVar
from abc import abstractmethod
T = TypeVar('T')
class C:
@abstractmethod
def f(self) -> None: ...
def fun(x: Type[T]) -> Type[T]: ...
fun(C) A possible simple solution is to type |
Lifting this constraint would be great for fetching implementations to interfaces (abstracts/protocols). T_co = TypeVar('T_co', covariant=True)
def register(interface: Type[T_co], implementation: Type[T_co]) -> Type[T_co]: ...
def inject(interface: Type[T_co]) -> T_co: ... |
Just a bit of context why this is prohibited: mypy always allows instantiation of something typed as from typing import Type, TypeVar
from abc import abstractmethod
T = TypeVar('T')
class C:
@abstractmethod
def f(self) -> None: ...
def fun(x: Type[T]) -> T: ...
return x() # mypy allows this, since it is a very common pattern
fun(C) # this fails at runtime, and it would be good to catch it statically,
# what we do currently I am still not sure what is the best way to proceed here. Potentially, we can only prohibit this if original function argument (or its upper bound) is abstract (so function actually expects implementations to be passed). This will be a bit unsafe, but more practical, as all examples provided so far would pass. |
I'm leaning towards allowing abstract classes to be used without restriction. |
This will be false negative, so probably OK. |
Sorry to bother, but is there some ETA on this issue? |
There is no concrete work planned for this issue yet, we are focusing on high-priority issues. |
I'm having a similar error and I don't know if it's the same problem or a different, related problem (or perhaps I'm doing something wrong), related to abstract classes and Attrs. I have this abstract class: https://github.com/eventbrite/pymetrics/blob/ab608978/pymetrics/recorders/base.py#L38-L39 Then I have this constant: _DEFAULT_METRICS_RECORDER = NoOpMetricsRecorder() # type: MetricsRecorder And this Attrs class: @attr.s
@six.add_metaclass(abc.ABCMeta)
class RedisTransportCore(object):
...
metrics = attr.ib(
default=_DEFAULT_METRICS_RECORDER,
validator=attr.validators.instance_of(MetricsRecorder),
) # type: MetricsRecorder On the line with
What's interesting is that this only happens with today's released Attrs 19.2.0 and last week's released MyPy 0.730. If I downgrade to Attrs 19.1.0 and keep the latest MyPy, the error goes away. If I downgrade to MyPy 0.720 and keep the latest Attrs, the error goes away. I'm able to get around the problem by adding |
This is really sad, given that dataclasses are being pushed as a go-to class container in 3.7+, having abstract dataclasses is not such a rare occasion. But mypy in the current state basically prohibits using it. Given that this issue is 1.5 years old, wonder if there's any known workarounds? |
Marking as high priority, since there seems to be quite a lot of interest in getting this fixed. |
Depending on your needs, for a temporary workaround, you can do the following, which type checks with import abc
import dataclasses
class AbstractClass(abc.ABC):
"""An abstract class."""
@abc.abstractmethod
def method(self) -> str:
"""Do something."""
@dataclasses.dataclass
class DataClassMixin:
"""A dataclass mixin."""
spam: str
class AbstractDataClass(DataClassMixin, AbstractClass):
"""An abstract data class."""
@dataclasses.dataclass
class ConcreteDataClass(AbstractDataClass):
"""A concrete data class."""
eggs: str
def method(self) -> str:
"""Do something."""
return "{} tastes worse than {}".format(self.spam, self.eggs)
print(ConcreteDataClass("spam", "eggs").method())
# -> "spam tastes worse than eggs" |
Similar to @cybojenix, we would be very happy if from typing import Any, Dict, TypeVar, Type
from typing_extensions import Protocol
ServiceType = TypeVar("ServiceType")
registrations: Dict[Any, Any] = {}
def register(t: Type[ServiceType], impl: ServiceType) -> None:
registrations[t] = impl
def locate(t: Type[ServiceType]) -> ServiceType:
return registrations[t]
class ThingService(Protocol):
def serve(str) -> str:
pass
class ThingImpl(ThingService):
def serve(str) -> str:
return "fish"
register(ThingService, ThingImpl())
thing: ThingService = locate(ThingService)
print(thing.serve()) |
Interestingly, the given rationale of not allowing abstract classes ("mypy always allows instantiation of something typed as Type[...], in particular of function arguments"), does not seem to match its implementation, as classes based on To elaborate (and give another example to possibly test a fix against later), the following code uses only from abc import ABC, abstractmethod
from typing import List, Type, TypeVar
AB = TypeVar("AB", "A", "B") # Could be A or B (Union)
class A:
@abstractmethod
def snork(self):
pass
class B:
@abstractmethod
def snork(self):
pass
class oneA(A):
pass
class anotherA(A):
pass
class someB(B):
pass
def make_objects(of_type: Type[AB]) -> List[AB]:
return [cls() for cls in of_type.__subclasses__()]
if __name__ == "__main__":
print(make_objects(A))
print(make_objects(B)) Removing the two |
Running into the same issue. |
For anyone reading this, I've solved it by getting rid of @property
def prop(self) -> Any:
raise NotImplemented We lose IDE check here, because we can instantiate a class, but calling the property at runtime will throw the exception, which should be fine in most of cases. |
@AlexanderPodorov It kind of defeats the purpose of type hinting and static checks if you rely on runtime errors. The point of |
Formerly abstract, `Spectrum` has to be made concrete due to [mypy issue#5374](python/mypy#5374) `Only concrete class can be given where "Type[Abstract]" is expected`
I'm seeing the same issue with an abstract class wrapped by @functools.total_ordering
class Student(abc.ABC):
<< cutting total_ordering implementation >>
@abc.abstract_method
def get_record(self):
pass
class NewStudent(Student):
def get_record(self) -> str:
return f"{self.lastname}, {self.firstname}" it fails mypy with |
Hi! Does anyone have an update or simple workaround for this? |
Fixes #5374 As discussed in the issue, we should allow this common use case, although it may be technically unsafe (e.g. if one would instantiate a class in a class decorator body).
Is there any further discussions about this use case?
|
This error assumes a specific usage of the passed in type, which may be common, but still not the only way a passed in class can be used, so it is an incorrect assumption. The actual error, which is trying to instantiate an abstract class is not reported in this case. The example in this comment highlights this assumption (#5374 (comment)):
I may have a method that reads YAML configuration that checks the expected types for returned values or the code may call static/class methods for passed in types, etc. Here's a simplified example. In actual code, from typing import TypeVar, Type
from abc import abstractmethod
class B:
@classmethod
def cm(cls, i: int) -> str:
return str(i) + "_ABC"
@abstractmethod
def am(self) -> int: ...
class X(B):
@classmethod
def cm(cls, i: int) -> str:
return str(i) + "_XYZ"
def am(self) -> int:
return 123
T = TypeVar("T", bound=B)
def fn(c: Type[T]) -> int|str:
print(c.cm(222))
return 1 if c == B else "ABC"
assert(fn(B) == 1) # error: Only concrete class can be given where
# "Type[B]" is expected [type-abstract]
assert(fn(X) == "ABC") A useful error would be if the code within the flagged function actually tries to call a constructor for an abstract type, not that an abstract type was passed in. It is unfortunate that this issue was closed. |
@NeilGirdhar Thanks for pointing out the new issue. Will keep an eye on it. |
In order to work around <python/mypy#5374>
mypy 0.620,
--strict
.Sample code:
Note that this error only occurs if at least one abstract method is defined on the abstract class. Removing the abstract method
c
(or making it non-abstract) makes the error go away.The text was updated successfully, but these errors were encountered: